//! Holds the codec glossary (`wai/glossary/codecs.json`) to its schema
//! (`wai/glossary/SCHEMA.md`) and to the registry. Test-only.
//!
//! Two tables here are the reviewed decisions; the test checks the JSON against
//! them and against the registry functions:
//!
//! - [`GLOSSARY_LINKS`]: which glossary entry's bitstream each linked capability
//!   carries. The `wai` link means "this capability decodes the codec this entry
//!   describes". A reviewer judges each row once.
//! - [`GLOSSARY_NO_ENTRY`]: every payload or companion capability that no entry
//!   links, with the reason.

use super::registry::Kind;
use super::*;
use serde_json::Value;

/// The `schema_version` the file declares (SCHEMA.md, *Top-level shape*).
const GLOSSARY_SCHEMA_VERSION: u64 = 2;

/// The fields every entry has, in schema order. `wai` is the one optional field.
const GLOSSARY_FIELDS: &[&str] = &["name", "year", "body", "modality", "era", "status", "lineage", "desc"];

const GLOSSARY_ENUMS: &[(&str, &[&str])] = &[
    ("modality", &["image", "video", "audio", "speech", "text", "volumetric", "haptic", "metadata"]),
    ("era", &["classical", "neural"]),
    ("status", &["success", "niche", "superseded", "failed", "academic", "emerging"]),
    ("lineage", &["industry", "standards", "academic", "opensource"]),
];

/// `(entry name, capability)`: the committed link table.
const GLOSSARY_LINKS: &[(&str, &str)] = &[
    ("JPEG", CAP_IMAGE_JPEG),
    ("JPEG 2000", CAP_IMAGE_JP2),
    ("AVIF", CAP_IMAGE_AVIF),
    ("JPEG XL", CAP_IMAGE_JXL),
    ("PNG", CAP_IMAGE_PNG),
    ("JPEG AI", CAP_IMAGE_JPEGAI),
    ("H.264 / AVC", CAP_VIDEO_AVC),
    ("HEVC / H.265", CAP_VIDEO_HEVC),
    ("MV-HEVC", CAP_VIDEO_MVHEVC),
    ("VVC / H.266", CAP_VIDEO_VVC),
    ("LCEVC", CAP_VIDEO_LCEVC),
    ("EVC", CAP_VIDEO_EVC),
    ("EVC", CAP_VIDEO_EVC_BASELINE),
    ("AVS3 video", CAP_VIDEO_AVS3),
    ("VP8", CAP_VIDEO_VP8),
    ("VP9", CAP_VIDEO_VP9),
    ("AV1", CAP_VIDEO_AV1),
    ("AV1", CAP_VIDEO_AV1_LOSSLESS),
    ("AV2", CAP_VIDEO_AV2),
    ("JPEG 2000 (DCI cinema)", CAP_VIDEO_J2K),
    ("JPEG XS", CAP_IMAGE_JXS),
    ("JPEG XS video (RFC 9134)", CAP_VIDEO_JXS),
    ("DCVC-FM", CAP_NEURAL_DCVC_FM),
    ("AAC", CAP_AUDIO_AAC),
    ("HE-AAC", CAP_AUDIO_AAC),
    ("HE-AAC v2", CAP_AUDIO_AAC),
    ("MPEG-H 3D Audio", CAP_AUDIO_MPEGH),
    ("AVS3 audio (Audio Vivid)", CAP_AUDIO_AVS3),
    ("IAMF", CAP_AUDIO_IAMF),
    ("FLAC", CAP_AUDIO_FLAC),
    ("Opus", CAP_AUDIO_OPUS),
    ("Opus", CAP_AUDIO_OPUS_PACKET),
    ("AC-4 (ETSI TS 103 190)", CAP_AUDIO_AC4),
    ("IVAS", CAP_AUDIO_IVAS),
    ("EnCodec", CAP_NEURAL_ENCODEC32),
    ("DAC", CAP_NEURAL_DAC),
    ("SNAC", CAP_NEURAL_SNAC),
    ("Mimi", CAP_NEURAL_MIMI),
    ("WavTokenizer", CAP_NEURAL_WAVTOKENIZER),
    ("Zstandard", CAP_TEXT_ZSTD),
    ("XZ (LZMA2)", CAP_TEXT_XZ),
    ("glTF 2.0 Binary (GLB)", CAP_MESH_GLB),
    ("Draco", CAP_MESH_DRACO),
    ("G-PCC", CAP_PC_GPCC),
    ("V-PCC", CAP_PC_VPCC),
    ("SPZ", CAP_SPLAT_SPZ),
    ("MPEG-I Haptics", CAP_HAPTIC_MIHS),
    ("HDR10+ (SMPTE ST 2094-40)", CAP_META_HDR10PLUS),
    ("SMPTE ST 2094-10 dynamic metadata", CAP_META_DOVI),
    ("SMPTE ST 2094-50 dynamic metadata", CAP_META_ST2094_50),
    ("ISO 21496-1 gain map", CAP_META_GAINMAP),
];

const WAI_NATIVE: &str = "a WAI-native capability class: its payload format is WAI's own, so no glossary codec describes it";
const NO_GLOSSARY_ENTRY: &str = "the glossary has no entry for this model yet";

/// `(capability, reason)`: every payload or companion capability no entry links.
const GLOSSARY_NO_ENTRY: &[(&str, &str)] = &[
    (CAP_AUDIO_INT_CODEC, WAI_NATIVE),
    (CAP_AUDIO_SCENE, WAI_NATIVE),
    (CAP_AUDIO_SCORE, WAI_NATIVE),
    (CAP_AVATAR_FACE, WAI_NATIVE),
    (CAP_AVATAR_POSE, WAI_NATIVE),
    (CAP_FEED_STATE, WAI_NATIVE),
    (CAP_FILM_LINEAR, WAI_NATIVE),
    (CAP_FILM_REEL, WAI_NATIVE),
    (CAP_GENERATE_SPECTRAL, WAI_NATIVE),
    (CAP_HAPTIC_SIGNAL, WAI_NATIVE),
    (CAP_NEURAL_INT_HYPER, WAI_NATIVE),
    (CAP_NEURAL_INT_MLICPP, WAI_NATIVE),
    (CAP_NEURAL_INT_SYNTH, WAI_NATIVE),
    (CAP_QUANTUM_CIRCUIT, WAI_NATIVE),
    (CAP_QUANTUM_CIRCUIT2, WAI_NATIVE),
    (CAP_SPLAT_4D, WAI_NATIVE),
    (CAP_SPLAT_INT_CODEC, WAI_NATIVE),
    (CAP_TERNARY_TT, WAI_NATIVE),
    (CAP_VIDEO_INT_HYPER, WAI_NATIVE),
    (CAP_VIDEO_INT_MOTION, WAI_NATIVE),
    (CAP_WORLD_LIVE, WAI_NATIVE),
    (CAP_WORLD_MODEL, WAI_NATIVE),
    (CAP_WORLD_REPLAY, WAI_NATIVE),
    (CAP_WORLD_SCENE, WAI_NATIVE),
    (CAP_IMAGE_INT_REFINE, WAI_NATIVE),
    (CAP_VIDEO_INT_REFINE, WAI_NATIVE),
    (CAP_AUDIO_INT_REFINE, WAI_NATIVE),
    (CAP_SPLAT_INT_REFINE, WAI_NATIVE),
    (CAP_NEURAL_BMSHJ2018, "the 2018 scale-hyperprior entry describes that paper's hyperprior model, not its factorized-prior baseline, which this capability carries"),
    (CAP_NEURAL_MBT2018_MEAN, "the entry for the 2018 joint-prior paper describes its autoregressive context model, not the mean-scale hyperprior this capability carries"),
    (CAP_NEURAL_VIDEO_BMSHJ2018, "per-frame use of the factorized-prior image model; no glossary codec describes it"),
    (CAP_NEURAL_CMIC, NO_GLOSSARY_ENTRY),
    (CAP_NEURAL_DCVC_RT, NO_GLOSSARY_ENTRY),
    (CAP_NEURAL_ELIC, NO_GLOSSARY_ENTRY),
    (CAP_NEURAL_GLC, NO_GLOSSARY_ENTRY),
    (CAP_NEURAL_MLICPP, NO_GLOSSARY_ENTRY),
    (CAP_NEURAL_MLICV2, NO_GLOSSARY_ENTRY),
    (CAP_SEMANTIC_CLIP, "a generative-semantic profile, not a codec"),
    (CAP_SEMANTIC_PERCO2, "PerCoV2 is a different model from the PerCo entry"),
    (CAP_PRIOR_NNC, "neural-network coding codes model parameters, not media"),
    (CAP_PRIOR_SET, WAI_NATIVE),
    (CAP_PRIOR_SAFETENSORS, "a tensor-file layout for parameters, not a media codec"),
    (CAP_PRIOR_GGUF, "a tensor-file layout for parameters, not a media codec"),
    (CAP_PRIOR_SEALED, WAI_NATIVE),
    (CAP_PRIOR_TREE, WAI_NATIVE),
    (CAP_TENSOR_NNC, "a candidate with no payload format pinned; neural-network coding codes tensors, not media"),
    (CAP_SPLAT_SOG, "a candidate with no payload format pinned"),
    (CAP_AUDIO_OPUS_DRED, "the glossary has no entry for the Opus deep redundancy extension yet"),
    (CAP_AUDIO_OPUS_DRED_INT, "the glossary has no entry for the Opus deep redundancy extension yet"),
    (CAP_IMAGE_IEEE1857_11, "a candidate with no payload format pinned"),
    (CAP_META_PROJECTION, WAI_NATIVE),
];

/// Pairs SPEC §5 excludes from a capability: such an entry must not link it.
const GLOSSARY_EXCLUDED: &[(&str, &str, &str)] = &[
    ("xHE-AAC", CAP_AUDIO_AAC, "ADTS cannot carry Extended HE-AAC"),
    ("MPEG-D USAC", CAP_AUDIO_AAC, "ADTS cannot carry Extended HE-AAC"),
];

/// Phrases no `desc` may contain: the glossary's no-comparison rule (SCHEMA.md),
/// the same forms `scripts/check-comparative-prose.sh` rejects.
const COMPARATIVE_PHRASES: &[&str] = &[
    "SOTA", "win vs", "moat", "nobody signs", "best-in-class", "superior", "% better", // allow-phrase: the rejected phrases
    "strictly worse", "toll road", "outperform", "underperform", "fiasco", "market chose", // allow-phrase: the rejected phrases
];

/// The `body` allowlist, read from the bulleted list under the heading
/// "### `body` allowlist" in SCHEMA.md (each bullet one backticked value).
fn bodies_from_schema(schema: &str) -> Vec<String> {
    let mut out = Vec::new();
    let mut inside = false;
    for line in schema.lines() {
        if line.starts_with("### ") {
            inside = line.contains("`body` allowlist");
            continue;
        }
        if inside {
            if let Some(rest) = line.strip_prefix("- `") {
                if let Some(end) = rest.find('`') {
                    out.push(rest[..end].to_string());
                }
            }
        }
    }
    out
}

fn media_of_modality(m: &str) -> &str {
    if m == "speech" { "audio" } else { m }
}

/// Every problem with the glossary `doc`, one line each, against the given link
/// and no-entry tables, the body allowlist and the registry.
fn glossary_problems(doc: &Value, links: &[(&str, &str)], no_entry: &[(&str, &str)], bodies: &[String]) -> Vec<String> {
    let mut p = Vec::new();
    let Some(obj) = doc.as_object() else { return vec!["glossary: not a JSON object".into()] };
    for k in obj.keys() {
        if k != "schema" && k != "schema_version" && k != "entries" {
            p.push(format!("glossary: top-level key `{k}` is not defined in SCHEMA.md"));
        }
    }
    if obj.get("schema_version").and_then(Value::as_u64) != Some(GLOSSARY_SCHEMA_VERSION) {
        p.push(format!("glossary: `schema_version` is not {GLOSSARY_SCHEMA_VERSION}"));
    }
    let Some(entries) = obj.get("entries").and_then(Value::as_array) else {
        p.push("glossary: `entries` is missing or not an array".into());
        return p;
    };
    let mut names = std::collections::HashSet::new();
    let mut found_links: Vec<(String, String)> = Vec::new();
    for e in entries {
        let name = e.get("name").and_then(Value::as_str).unwrap_or("<unnamed>").to_string();
        let Some(eo) = e.as_object() else { p.push(format!("glossary entry {name}: not an object")); continue };
        // R2: fields.
        for f in GLOSSARY_FIELDS {
            if !eo.contains_key(*f) { p.push(format!("glossary entry {name}: missing field `{f}`")); }
        }
        for k in eo.keys() {
            if !GLOSSARY_FIELDS.contains(&k.as_str()) && k != "wai" {
                p.push(format!("glossary entry {name}: field `{k}` is not defined in SCHEMA.md"));
            }
        }
        if eo.get("year").is_some_and(|y| !y.is_u64()) {
            p.push(format!("glossary entry {name}: `year` is not a whole number"));
        }
        for f in ["name", "desc"] {
            if eo.get(f).is_some_and(|v| v.as_str().is_none_or(str::is_empty)) {
                p.push(format!("glossary entry {name}: `{f}` is not a non-empty string"));
            }
        }
        // R13: body from the allowlist, or null.
        match eo.get("body") {
            Some(Value::Null) | None => {}
            Some(Value::String(b)) if bodies.iter().any(|x| x == b) => {}
            Some(b) => p.push(format!("glossary entry {name}: body {b} is not in the SCHEMA.md allowlist")),
        }
        // R3: enums.
        for (f, allowed) in GLOSSARY_ENUMS {
            if let Some(v) = eo.get(*f) {
                if !v.as_str().is_some_and(|s| allowed.contains(&s)) {
                    p.push(format!("glossary entry {name}: {f} {v} is not a SCHEMA.md value"));
                }
            }
        }
        // R4: unique names.
        if !names.insert(name.clone()) {
            p.push(format!("glossary: two entries are named {name}"));
        }
        // R14: no comparative phrasing.
        if let Some(desc) = eo.get("desc").and_then(Value::as_str) {
            for ph in COMPARATIVE_PHRASES {
                if desc.contains(ph) {
                    p.push(format!("glossary entry {name}: desc contains the comparative phrase `{ph}`"));
                }
            }
        }
        // R5–R10, R12: links.
        let Some(w) = eo.get("wai") else { continue };
        let Some(arr) = w.as_array().filter(|a| !a.is_empty()) else {
            p.push(format!("glossary entry {name}: `wai` must be a non-empty array of links (SCHEMA.md)"));
            continue;
        };
        let modality = eo.get("modality").and_then(Value::as_str).unwrap_or("");
        let mut seen = std::collections::HashSet::new();
        for l in arr {
            let ok_shape = l.as_object().is_some_and(|o| {
                o.len() == 3 && ["capability", "licence", "replicate"].iter().all(|k| o.get(*k).is_some_and(Value::is_string))
            });
            if !ok_shape {
                p.push(format!("glossary entry {name}: a `wai` link must have exactly the string fields capability, licence and replicate"));
                continue;
            }
            let cap = l["capability"].as_str().unwrap();
            if !seen.insert(cap.to_string()) {
                p.push(format!("glossary entry {name} links {cap} twice"));
            }
            found_links.push((name.clone(), cap.to_string()));
            if !matches!(capability_kind(cap), Some(Kind::Payload | Kind::Companion)) {
                p.push(format!("glossary entry {name} links {cap}, which SPEC §5 does not register as a payload or companion capability"));
                continue;
            }
            let lic = capability_license(cap).map_or("?", LicenseClass::label);
            if l["licence"].as_str() != Some(lic) {
                p.push(format!("glossary entry {name}: the registry's licence class for {cap} is {lic}, not {}", l["licence"]));
            }
            let rep = capability_replicate_class(cap).map_or("?", ReplicateClass::label);
            if l["replicate"].as_str() != Some(rep) {
                p.push(format!("glossary entry {name}: the registry's replicate class for {cap} is {rep}, not {}", l["replicate"]));
            }
            let media = capability_media(cap).map_or("?", Media::label);
            if media_of_modality(modality) != media {
                p.push(format!("glossary entry {name} ({modality}) links {cap}, whose media class ({media}) it is not"));
            }
            for (ex_name, ex_cap, why) in GLOSSARY_EXCLUDED {
                if *ex_name == name && *ex_cap == cap {
                    p.push(format!("glossary entry {name} links {cap}, which SPEC §5 excludes: {why}"));
                }
            }
        }
    }
    // R15: the links are exactly the committed table.
    for (n, c) in &found_links {
        if !links.iter().any(|(ln, lc)| ln == n && lc == c) {
            p.push(format!("glossary entry {n} links {c}, which GLOSSARY_LINKS does not list"));
        }
    }
    for (n, c) in links {
        if !found_links.iter().any(|(fn_, fc)| fn_ == n && fc == c) {
            p.push(format!("GLOSSARY_LINKS lists {n} → {c}, which the glossary does not link"));
        }
    }
    // R11: every payload or companion capability is linked or explained.
    for &cap in known_capabilities() {
        let linked = found_links.iter().any(|(_, c)| c == cap);
        let listed = no_entry.iter().any(|(c, _)| *c == cap);
        if !linked && !listed {
            p.push(format!("{cap} is registered (SPEC §5) but no glossary entry links it and GLOSSARY_NO_ENTRY does not list it"));
        }
        if linked && listed {
            p.push(format!("{cap} is linked and also listed in GLOSSARY_NO_ENTRY"));
        }
    }
    p
}

fn read(rel: &str) -> String {
    let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..").join(rel);
    std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()))
}

#[test]
fn glossary_links_agree_with_the_registry() {
    let doc: Value = serde_json::from_str(&read("glossary/codecs.json")).expect("codecs.json parses");
    let bodies = bodies_from_schema(&read("glossary/SCHEMA.md"));
    assert!(!bodies.is_empty(), "SCHEMA.md has no `body` allowlist");
    let p = glossary_problems(&doc, GLOSSARY_LINKS, GLOSSARY_NO_ENTRY, &bodies);
    assert!(p.is_empty(), "the codec glossary disagrees with its schema or the registry:\n{}", p.join("\n"));
    for (_, why) in GLOSSARY_NO_ENTRY {
        assert!(!why.is_empty());
    }
}

/// `is_classical` is the glossary's classical era: exactly the capabilities an entry of
/// era `classical` links, which a session claim's classical rendition is read from
/// (jwp-receipts §2.4.12).
#[test]
fn classical_is_the_glossary_era() {
    let doc: Value = serde_json::from_str(&read("glossary/codecs.json")).expect("codecs.json parses");
    let mut linked: Vec<String> = doc["entries"]
        .as_array()
        .expect("entries")
        .iter()
        .filter(|e| e["era"] == "classical")
        .flat_map(|e| e["wai"].as_array().cloned().unwrap_or_default())
        .map(|l| l["capability"].as_str().expect("a linked capability").to_owned())
        .collect();
    linked.sort();
    linked.dedup();
    let mut ours: Vec<String> = known_capabilities().iter().filter(|c| is_classical(c)).map(|c| c.to_string()).collect();
    ours.sort();
    assert_eq!(ours, linked);
    assert!(CLASSICAL.iter().all(|c| known_capabilities().contains(c)), "every classical capability is registered");
}

/// A valid fixture, then one mutation per rule: removing any rule from
/// `glossary_problems` fails its row.
#[test]
fn glossary_check_names_each_broken_rule() {
    use serde_json::json;
    let bodies = vec!["ITU-T".to_string()];
    // The fixture links the payload and companion capabilities a codec could
    // describe; a refinement is never linked (its payload format is WAI's own).
    let caps: Vec<&str> = known_capabilities().iter().copied()
        .filter(|c| !matches!(capability_media(c), Some(Media::Interactive | Media::Binary)) && !is_refinement_only(c))
        .collect();
    let links: Vec<(&str, &str)> = caps.iter().map(|c| (*c, *c)).collect();
    let no_entry: Vec<(&str, &str)> = known_capabilities().iter().copied()
        .filter(|c| !caps.contains(c)).map(|c| (c, "fixture")).collect();
    let entry = |cap: &str| json!({
        "name": cap, "year": 2020, "body": null, "modality": capability_media(cap).unwrap().label(),
        "era": "classical", "status": "niche", "lineage": "standards", "desc": "A codec.",
        "wai": [{ "capability": cap, "licence": capability_license(cap).unwrap().label(),
                  "replicate": capability_replicate_class(cap).unwrap().label() }]
    });
    let good = json!({ "schema": "x", "schema_version": GLOSSARY_SCHEMA_VERSION, "entries": caps.iter().map(|c| entry(c)).collect::<Vec<_>>() });
    let ok = glossary_problems(&good, &links, &no_entry, &bodies);
    assert!(ok.is_empty(), "{ok:?}");
    let idx = |cap: &str| caps.iter().position(|c| *c == cap).unwrap();
    let hevc = idx(CAP_VIDEO_HEVC);
    let aac = idx(CAP_AUDIO_AAC);
    type Mutation = Box<dyn Fn(&mut Value)>;
    let cases: Vec<(Mutation, &str)> = vec![
        (Box::new(|d| { d["extra"] = json!(1); }), "top-level key `extra`"),
        (Box::new(|d| { d.as_object_mut().unwrap().remove("schema_version"); }), "`schema_version` is not 2"),
        (Box::new(|d| { d["schema_version"] = json!(1); }), "`schema_version` is not 2"),
        (Box::new(|d| { d["entries"][0].as_object_mut().unwrap().remove("desc"); }), "missing field `desc`"),
        (Box::new(|d| { d["entries"][0]["codec"] = json!("x"); }), "field `codec` is not defined"),
        (Box::new(|d| { d["entries"][0]["year"] = json!("2020"); }), "`year` is not a whole number"),
        (Box::new(|d| { d["entries"][0]["status"] = json!("popular"); }), "is not a SCHEMA.md value"),
        (Box::new(|d| { let n = d["entries"][0]["name"].clone(); d["entries"][1]["name"] = n; }), "two entries are named"),
        (Box::new(|d| { d["entries"][0]["wai"] = json!([]); }), "non-empty array of links"),
        (Box::new(|d| { let l = d["entries"][0]["wai"][0].as_object_mut().unwrap(); let v = l.remove("licence").unwrap(); l.insert("license".into(), v); }), "exactly the string fields"),
        (Box::new(|d| { d["entries"][0]["wai"][0]["capability"] = json!("wai.video.h266"); }), "does not register"),
        (Box::new(move |d| { d["entries"][hevc]["wai"][0]["licence"] = json!("royalty-free"); }), "licence class for wai.video.hevc is patent-pool"),
        (Box::new(move |d| { d["entries"][aac]["wai"][0]["replicate"] = json!("standard-defined"); }), "replicate class for wai.audio.aac is standard-unclassified"),
        (Box::new(move |d| { d["entries"][hevc]["modality"] = json!("audio"); }), "whose media class"),
        (Box::new(move |d| { let l = d["entries"][hevc]["wai"][0].clone(); d["entries"][hevc]["wai"].as_array_mut().unwrap().push(l); }), "links wai.video.hevc twice"),
        (Box::new(move |d| { d["entries"][hevc].as_object_mut().unwrap().remove("wai"); }), "wai.video.hevc is registered (SPEC §5) but no glossary entry links it"),
        (Box::new(move |d| { d["entries"][aac]["name"] = json!("xHE-AAC"); }), "which SPEC §5 excludes"),
        (Box::new(|d| { d["entries"][0]["body"] = json!("A Company Inc."); }), "not in the SCHEMA.md allowlist"),
        (Box::new(|d| { d["entries"][0]["desc"] = json!("Roughly 50% better than its predecessor."); }), "comparative phrase"), // allow-phrase: test fixture
    ];
    for (mutate, want) in cases {
        let mut d = good.clone();
        mutate(&mut d);
        let p = glossary_problems(&d, &links, &no_entry, &bodies);
        assert!(p.iter().any(|m| m.contains(want)), "expected a problem containing {want:?}, got {p:?}");
    }
    // The link table is binding in both directions.
    let mut fewer = links.clone();
    fewer.pop();
    assert!(glossary_problems(&good, &fewer, &no_entry, &bodies).iter().any(|m| m.contains("GLOSSARY_LINKS does not list")));
    let mut more = links.clone();
    more.push(("nonexistent entry", CAP_IMAGE_PNG));
    assert!(glossary_problems(&good, &more, &no_entry, &bodies).iter().any(|m| m.contains("which the glossary does not link")));
}

#[test]
fn the_body_allowlist_is_read_from_the_schema() {
    let md = "intro\n### `body` allowlist\n\n- `ITU-T` — x\n- `IETF`\n\n### next\n- `not this`\n";
    assert_eq!(bodies_from_schema(md), vec!["ITU-T".to_string(), "IETF".to_string()]);
}
