//! The conformance corpus of `extensions/robustness-evidence.md` §13
//! (`live-conformance/robustness/`): corpus documents, the conformance record whose samples
//! the corpus holds, the records and the claim of `wai-mark:tent64` (§10), and cases refused
//! for each reason of §12. Every file is a function of fixed seeds: the pictures are the
//! crate's generators', every key is SHA-256 of a label and an index, and Ed25519 signing is
//! deterministic.

use std::collections::BTreeMap;

use ed25519_dalek::SigningKey;
use serde_json::{json, Map, Value};

use super::*;
use crate::synthesis_mark::battery::{CLIP_ATTACKS, IMAGE_ATTACKS};
use crate::synthesis_mark::corpus::battery_payload;
use crate::synthesis_mark::{clips, leaves, leaves_set, spectral, spectral_set};

const ISSUED: &str = "2026-10-22T09:00:00Z";
/// The side of the conformance corpus's pictures.
pub const SIDE: usize = 256;

/// The key of the party that measures (0), of the algorithm's registrant (1) and of another
/// party (2).
fn key(i: u8) -> SigningKey {
    SigningKey::from_bytes(&sha256(&[b"wai-robustness corpus key".as_slice(), &[i]].concat()))
}

/// The claim of synthesis §7.5.8, *claims to survive*: its picture attacks, then its clip
/// attacks, under their names in synthesis §7.5.7. `tests/robustness.rs` checks it is the
/// extension's list.
pub const TENT64_CLAIM_PICTURES: [&str; 23] = [
    "int-motion-q4",
    "int-motion-q8",
    "int-motion-q16",
    "int-motion-q32",
    "int-motion-q48",
    "staged-base-q8",
    "denoise3",
    "denoise3-x4",
    "scale-3/4",
    "scale-1/2",
    "scale-3/2",
    "upscale2",
    "aspect-3/4",
    "half-then-int-motion-q16",
    "mirror",
    "rotate-90",
    "noise-4",
    "noise-16",
    "brightness+32",
    "brightness-32",
    "contrast-1/2",
    "contrast-3/2",
    "local-blur",
];
pub const TENT64_CLAIM_CLIPS: [&str; 6] = ["clip-int-motion-q8", "clip-int-motion-q24", "frame-drop", "one-frame", "scale-1/2", "noise-16"];

/// The conformance corpus's battery (§13): thirteen attacks, the seed of each its index.
pub fn conformance_battery() -> Vec<(&'static str, Transform)> {
    vec![
        ("none", Transform::None),
        ("int-motion-q16", Transform::IntMotion(16)),
        ("scale-1/2", Transform::Scale(1, 2)),
        ("crop-2%", Transform::Crop(2)),
        ("mirror", Transform::Mirror),
        ("rotate-90", Transform::Rotate90),
        ("rotate-1", Transform::Rotate(65526, 1144, false)),
        ("noise-16", Transform::Noise(16)),
        ("gamma-4/5", Transform::Gamma(4, 5)),
        ("gamma-5/4", Transform::Gamma(5, 4)),
        ("contrast-3/2", Transform::Contrast(3, 2)),
        ("brightness-32", Transform::Brightness(32, true)),
        ("denoise3", Transform::Denoise3(1)),
    ]
}

/// The conformance corpus's pictures: two dead-leaves pictures and a spectral one, each
/// [`SIDE`] square.
pub fn conformance_pictures() -> Vec<(String, Frames)> {
    vec![
        ("leaves-200".into(), Frames::one(leaves(200, SIDE, SIDE), SIDE, SIDE)),
        ("leaves-201".into(), Frames::one(leaves(201, SIDE, SIDE), SIDE, SIDE)),
        ("spectral-200".into(), Frames::one(spectral("a lighthouse at dusk", 200, SIDE, SIDE), SIDE, SIDE)),
    ]
}

/// A corpus document over pictures, its negatives the same samples, unmarked.
fn corpus_of(name: &str, media: Media, pics: &[(String, Frames)]) -> CorpusDoc {
    let items: Vec<Item> = pics.iter().map(|(n, f)| Item::of(n, f)).collect();
    CorpusDoc { name: name.into(), media, items: items.clone(), negatives: items }
}

/// The record of `wai-mark:tent64` over a corpus, its battery and values as §10 gives them,
/// the table its harness gives.
fn tent64_record(doc: &CorpusDoc, pics: &[(String, Frames)], battery: &[(&str, Transform)]) -> Evidence {
    let frames: Vec<Frames> = pics.iter().map(|(_, f)| f.clone()).collect();
    let values: Vec<Vec<u8>> = pics.iter().map(|(n, _)| battery_payload(n).to_vec()).collect();
    let run: Vec<(String, Transform, u32)> = battery.iter().enumerate().map(|(i, (n, t))| (n.to_string(), *t, i as u32)).collect();
    let table = run_harness(&Tent64, &frames, &frames, &values, &run);
    Evidence {
        algorithm: mark::SCHEME.into(),
        algorithm_digest: mark::digest(),
        binding_type: "watermark".into(),
        media: doc.media,
        corpus: doc.digest(),
        values,
        battery: battery.iter().enumerate().map(|(i, (n, t))| BatteryAttack::of(n, *t, i as u64)).collect(),
        harness: HARNESS.into(),
        table,
        issued_at: ISSUED.into(),
    }
}

/// Synthesis §7.5.7's list, as (name, transform) by §10's table.
fn synthesis_battery(list: &[(&'static str, Attack)]) -> Vec<(&'static str, Transform)> {
    list.iter().map(|(n, a)| (*n, Transform::of_attack(*a).expect("§7.5.7's attacks are registered transforms"))).collect()
}

/// A claimed attack: `media`, and the transform of a battery's attack by its name.
fn claimed(media: Media, battery: &[(&str, Transform)], name: &str) -> Claimed {
    let t = battery.iter().find(|(n, _)| *n == name).unwrap_or_else(|| panic!("an attack named {name}")).1;
    let (term, params) = t.term_params();
    Claimed { media: media.label().into(), transform: term.into(), params }
}

fn text(v: &Value) -> String {
    jcs(v).expect("in the subset")
}

/// Re-sign a record's members after an edit, as party `k`.
fn resigned(record: &Value, k: u8, edit: impl FnOnce(&mut Map<String, Value>)) -> String {
    let mut v = record.clone();
    edit(v.as_object_mut().expect("an object"));
    text(&sign(v, &key(k)))
}

/// The first row of a record's table, to edit in place.
fn row(m: &mut Map<String, Value>, i: usize) -> &mut Map<String, Value> {
    m.get_mut("table").and_then(Value::as_array_mut).and_then(|t| t.get_mut(i)).and_then(Value::as_object_mut).expect("a row")
}

/// Everything the generator computes: the corpus documents, the pictures whose samples the
/// corpus holds, and the signed records.
pub struct World {
    pub conformance_doc: CorpusDoc,
    pub conformance_pictures: Vec<(String, Frames)>,
    pub tent64_docs: Vec<(String, CorpusDoc)>,
    pub conformance: Value,
    pub conformance_claim: Value,
    pub tent64: Vec<(String, Value)>,
    pub tent64_claim: Value,
}

/// Compute the world: this runs the harness over the measurement sets of synthesis §7.5.8.
pub fn world() -> World {
    let pics = conformance_pictures();
    let conformance_doc = corpus_of("conformance", Media::Image, &pics);
    let cb = conformance_battery();
    let conformance_ev = tent64_record(&conformance_doc, &pics, &cb);
    let conformance = sign(conformance_ev.body(), &key(0));
    // The conformance claim: every attack its record shows survival under.
    let survived: Vec<Claimed> = conformance_ev
        .table
        .iter()
        .filter(|c| c.survived == c.trials && c.wrong == 0 && c.false_positives == 0 && c.attack != "none")
        .map(|c| claimed(Media::Image, &cb, &c.attack))
        .collect();
    let conformance_claim = sign(
        Claim { algorithm: mark::SCHEME.into(), algorithm_digest: mark::digest(), survives: survived, evidence: vec![link_hash(&conformance)], issued_at: ISSUED.into() }.body(),
        &key(1),
    );

    let image = synthesis_battery(&IMAGE_ATTACKS);
    let clip = synthesis_battery(&CLIP_ATTACKS);
    // A measurement set: its name, its media, its items, and the battery run over it.
    type MeasurementSet<'a> = (&'a str, Media, Vec<(String, Frames)>, &'a Vec<(&'a str, Transform)>);
    let sets: Vec<MeasurementSet<'_>> =
        vec![("tent64-spectral", Media::Image, spectral_set(), &image), ("tent64-leaves", Media::Image, leaves_set(), &image), ("tent64-clips", Media::Video, clips(), &clip)];
    let mut tent64_docs = Vec::new();
    let mut tent64 = Vec::new();
    for (name, media, set, battery) in &sets {
        let doc = corpus_of(name, *media, set);
        tent64.push((name.to_string(), sign(tent64_record(&doc, set, battery).body(), &key(0))));
        tent64_docs.push((name.to_string(), doc));
    }
    let mut survives: Vec<Claimed> = TENT64_CLAIM_PICTURES.iter().map(|n| claimed(Media::Image, &image, n)).collect();
    survives.extend(TENT64_CLAIM_CLIPS.iter().map(|n| claimed(Media::Video, &clip, n)));
    let tent64_claim = sign(
        Claim { algorithm: mark::SCHEME.into(), algorithm_digest: mark::digest(), survives, evidence: tent64.iter().map(|(_, r)| link_hash(r)).collect(), issued_at: ISSUED.into() }.body(),
        &key(1),
    );
    World { conformance_doc, conformance_pictures: pics, tent64_docs, conformance, conformance_claim, tent64, tent64_claim }
}

// ---- the cases ----

/// A corpus document given with a record: a committed file, or a text.
enum Given {
    File(String),
    Text(String),
}

struct CaseSpec {
    name: &'static str,
    about: &'static str,
    reproduced: bool,
    implements: bool,
    records: Vec<String>,
    corpora: Vec<(usize, Given)>,
    samples: bool,
}

fn case(name: &'static str, about: &'static str, records: Vec<String>) -> CaseSpec {
    CaseSpec { name, about, reproduced: false, implements: true, records, corpora: vec![], samples: false }
}

impl CaseSpec {
    fn with_corpus(mut self, i: usize, g: Given) -> Self {
        self.corpora.push((i, g));
        self
    }
    fn held(mut self) -> Self {
        self.samples = true;
        self
    }
    fn reproduced(mut self) -> Self {
        self.reproduced = true;
        self
    }
    fn not_implemented(mut self) -> Self {
        self.implements = false;
        self
    }
}

const CONFORMANCE_CORPUS: &str = "corpus/conformance.json";

fn cases(x: &World) -> Vec<CaseSpec> {
    let conf = &x.conformance;
    let conf_t = text(conf);
    let conformance_file = || Given::File(CONFORMANCE_CORPUS.into());
    let c = |name, about, rec: String| case(name, about, vec![rec]).with_corpus(0, conformance_file()).held();
    let tent_texts: Vec<String> = x.tent64.iter().map(|(_, r)| text(r)).collect();
    let tent_case = |name, about, claim: String| {
        let mut records = tent_texts.clone();
        records.push(claim);
        let mut s = case(name, about, records);
        for (i, (n, _)) in x.tent64.iter().enumerate() {
            s = s.with_corpus(i, Given::File(format!("corpus/{n}.json")));
        }
        s
    };
    let tent_claim = |edit: &dyn Fn(&mut Map<String, Value>)| resigned(&x.tent64_claim, 1, |m| edit(m));
    let claim_entry = |media: &str, t: &str, p: &[u64]| json!({"media": media, "transform": t, "params": p});

    // A record of an algorithm this revision does not register, its table stated: three
    // items, one negative read as marked under `jpeg-q50`.
    let other_alg = "org.example.watermark.1";
    let other_digest = sha256(b"org.example.watermark.1 definition");
    let other_counts = |attack: &str, s: u64, f: u64| Counts { attack: attack.into(), trials: 3, survived: s, wrong: 0, negatives: 3, false_positives: f };
    let other = sign(
        Evidence {
            algorithm: other_alg.into(),
            algorithm_digest: other_digest,
            binding_type: "watermark".into(),
            media: Media::Image,
            corpus: x.conformance_doc.digest(),
            values: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
            battery: vec![
                BatteryAttack { name: "none".into(), transform: "wai-attack:none".into(), params: vec![], seed: 0 },
                BatteryAttack { name: "jpeg-q50".into(), transform: "org.example.attack.jpeg".into(), params: vec![50], seed: 1 },
            ],
            harness: "org.example.harness.1".into(),
            table: vec![other_counts("none", 3, 0), other_counts("jpeg-q50", 3, 1)],
            issued_at: ISSUED.into(),
        }
        .body(),
        &key(2),
    );
    let other_claim = |survives: Value| {
        text(&sign(json!({"kind": CLAIM_KIND, "algorithm": other_alg, "algorithm_digest": hex(&other_digest), "survives": survives, "evidence": [hex(&link_hash(&other))], "issued_at": ISSUED}), &key(2)))
    };

    // A corpus document of two of the three items, and one whose first item's height is off
    // by one, so its samples are not its size.
    let mut two = x.conformance_doc.clone();
    two.items.truncate(2);
    let mut off = x.conformance_doc.clone();
    off.items[0].height -= 1;
    let mut no_negatives = x.conformance_doc.value();
    no_negatives["negatives"] = json!([]);

    // The conformance record with one count changed and its rates with it.
    let mut changed = conformance_evidence(x);
    let at = changed.table.iter().position(|c| c.survived > 0).expect("a row with a survivor");
    changed.table[at].survived -= 1;
    let irreproducible = text(&sign(changed.body(), &key(0)));

    let swapped = resigned(conf, 0, |m| {
        let t = m.get_mut("table").and_then(Value::as_array_mut).expect("a table");
        t.swap(0, 1);
    });
    let v: Vec<CaseSpec> = vec![
        // ── accepted ──
        c("conformance_reproduced", "the conformance record, its corpus and every sample held, wai-mark:tent64 run: the harness gives its table bit for bit", conf_t.clone()),
        case("conformance_stated_without_samples", "the same record with its corpus document and no samples: checked for its arithmetic and signature, stated", vec![conf_t.clone()])
            .with_corpus(0, conformance_file()),
        c("conformance_stated_not_implemented", "the same record, every sample held, by a verifier that does not implement the algorithm: stated", conf_t.clone()).not_implemented(),
        case("conformance_claim_reproduced", "the conformance claim, under a policy that requires reproduced evidence, over its reproduced record", vec![conf_t.clone(), text(&x.conformance_claim)])
            .with_corpus(0, conformance_file())
            .held()
            .reproduced(),
        tent_case("tent64_records_and_claim", "wai-mark:tent64's three records of synthesis 7.5.8, stated without their samples, and its claim, exactly synthesis 7.5.8's list", text(&x.tent64_claim)),
        case("unregistered_algorithm_stated", "a record of an algorithm and a transform this revision does not register: its arithmetic and signature checked, stated, and a claim of what it shows survived", vec![text(&other), other_claim(json!([claim_entry("image", "wai-attack:none", &[])]))]),
        // ── §2: reading and the signature ──
        c("record_extra_member", "a record with a member section 5 does not list", resigned(conf, 0, |m| {
            m.insert("note".into(), json!("x"));
        })),
        c("record_kind_unknown", "a record of a kind this extension does not define", resigned(conf, 0, |m| {
            m.insert("kind".into(), json!("wai-robustness-report"));
        })),
        c("record_count_not_integer", "a count written as a string", resigned(conf, 0, |m| {
            row(m, 0).insert("survived".into(), json!("3"));
        })),
        c("rate_decimal", "a survival rate written as the decimal 0.75: a rate is an exact fraction, never floating point", tamper(&conf_t, "\"survival\":[1,1]", "\"survival\":0.75")),
        c("rate_not_reduced", "a rate written as [2,2], not in lowest terms", resigned(conf, 0, |m| {
            row(m, 0).insert("survival".into(), json!([2, 2]));
        })),
        c("rate_string", "a rate written as the string \"3/3\"", resigned(conf, 0, |m| {
            row(m, 0).insert("survival".into(), json!("3/3"));
        })),
        c("rate_above_one", "a rate of 4/3", resigned(conf, 0, |m| {
            row(m, 0).insert("balanced_accuracy".into(), json!([4, 3]));
        })),
        c("bad_key", "a record under a key of small order", {
            let mut v = conf.clone();
            v["signer_pubkey"] = json!(format!("01{}", "00".repeat(31)));
            text(&v)
        }),
        c("bad_signature", "a record whose signature was altered", {
            let mut v = conf.clone();
            let s = v["sig"].as_str().expect("a sig").to_string();
            v["sig"] = json!(format!("{}{}", if s.starts_with('0') { "1" } else { "0" }, &s[1..]));
            text(&v)
        }),
        // ── §5 checks 1 to 5 ──
        c("battery_duplicate_name", "two attacks of one name", resigned(conf, 0, |m| {
            let b = m.get_mut("battery").and_then(Value::as_array_mut).expect("a battery");
            b[1]["attack"] = json!("none");
            row(m, 1).insert("attack".into(), json!("none"));
        })),
        c("battery_seed_too_large", "a seed of 2^32, past the four bytes the noise stream reads", resigned(conf, 0, |m| {
            m.get_mut("battery").and_then(Value::as_array_mut).expect("a battery")[0]["seed"] = json!(4_294_967_296u64);
        })),
        c("transform_crop_100", "a crop of 100%: section 4 admits 0 to 99", resigned(conf, 0, |m| {
            m.get_mut("battery").and_then(Value::as_array_mut).expect("a battery")[3]["params"] = json!([100]);
        })),
        c("transform_gamma_7", "a gamma of 7/5: section 4 admits exponents of 1 to 6", resigned(conf, 0, |m| {
            m.get_mut("battery").and_then(Value::as_array_mut).expect("a battery")[8]["params"] = json!([7, 5]);
        })),
        c("battery_frame_drop_on_pictures", "frame-drop in a battery over a corpus of pictures", resigned(conf, 0, |m| {
            m.get_mut("battery").and_then(Value::as_array_mut).expect("a battery")[1] = json!({"attack": "int-motion-q16", "transform": "wai-attack:frame-drop", "params": [], "seed": 1});
        })),
        c("table_rows_swapped", "the table's first two rows in another order than the battery's", swapped),
        c("table_row_missing", "a table one row short", resigned(conf, 0, |m| {
            m.get_mut("table").and_then(Value::as_array_mut).expect("a table").pop();
        })),
        c("table_more_survived_than_trials", "four of three items survived, its rates left as they were: every rate reads, and the counts are refused", resigned(conf, 0, |m| {
            row(m, 0).insert("survived".into(), json!(4));
        })),
        c("table_negatives_differ", "a row whose negatives differ from the others'", {
            let mut e = conformance_evidence(x);
            e.table[2].negatives = 4;
            text(&sign(e.body(), &key(0)))
        }),
        c("rate_mismatch", "a balanced accuracy that is a reduced fraction and not the one its counts give", {
            let mut v = conf.clone();
            let i = conformance_evidence(x).table.iter().position(|c| c.survived < c.trials).expect("a row with a loss");
            v["table"][i]["balanced_accuracy"] = json!([1, 1]);
            text(&sign(v, &key(0)))
        }),
        c("algorithm_digest_mismatch", "wai-mark:tent64 named with another digest", resigned(conf, 0, |m| {
            m.insert("algorithm_digest".into(), json!(hex(&sha256(b"another definition"))));
        })),
        c("algorithm_type_mismatch", "wai-mark:tent64 typed a fingerprint", resigned(conf, 0, |m| {
            m.insert("binding_type".into(), json!("fingerprint"));
        })),
        c("values_not_payloads", "a value that is not a payload of synthesis 7.2: its first byte is not a class code", resigned(conf, 0, |m| {
            m.get_mut("values").and_then(Value::as_array_mut).expect("values")[0] = json!("0901020304050607");
        })),
        // ── §5 checks 6 to 8 ──
        case("corpus_digest_mismatch", "a record over the conformance corpus handed the dead-leaves set's corpus document", vec![conf_t.clone()])
            .with_corpus(0, Given::File("corpus/tent64-leaves.json".into())),
        case("corpus_malformed", "a corpus document with no negatives", vec![conf_t.clone()]).with_corpus(0, Given::Text(text(&no_negatives))),
        case("corpus_counts_differ", "a record of three values over a corpus document of two items", vec![resigned(conf, 0, |m| {
            m.insert("corpus".into(), json!(hex(&two.digest())));
        })])
        .with_corpus(0, Given::Text(two.text()))
        .held(),
        case("samples_not_their_size", "a corpus document whose first item is a row shorter than its samples", vec![resigned(conf, 0, |m| {
            m.insert("corpus".into(), json!(hex(&off.digest())));
        })])
        .with_corpus(0, Given::Text(off.text()))
        .held(),
        c("table_irreproducible", "the conformance record with one count lowered and its rates with it, signed again: every check but the rerun passes", irreproducible),
        // ── §8 ──
        case("claim_evidence_not_held", "a claim citing a record the case does not verify before it", vec![text(&x.tent64_claim)]),
        case("claim_evidence_other_algorithm", "a claim of wai-mark:tent64 citing a record of another algorithm", vec![text(&other), text(&sign(
            Claim { algorithm: mark::SCHEME.into(), algorithm_digest: mark::digest(), survives: vec![Claimed { media: "image".into(), transform: "wai-attack:none".into(), params: vec![] }], evidence: vec![link_hash(&other)], issued_at: ISSUED.into() }.body(),
            &key(1),
        ))]),
        tent_case("claim_stated_evidence_under_policy", "wai-mark:tent64's claim under a policy that requires reproduced evidence, its records stated without their samples", text(&x.tent64_claim)).reproduced(),
        tent_case("claim_crop_10_exceeds", "a claim of crop-10%, which no picture of either set survived", tent_claim(&|m| {
            m.get_mut("survives").and_then(Value::as_array_mut).expect("survives").push(claim_entry("image", "wai-attack:crop", &[10]));
        })),
        tent_case("claim_scale_1_3_exceeds", "a claim of scale-1/3: one record shows it survived, the other shows it lost", tent_claim(&|m| {
            m.get_mut("survives").and_then(Value::as_array_mut).expect("survives").push(claim_entry("image", "wai-attack:scale", &[1, 3]));
        })),
        tent_case("claim_crop_25_unevidenced", "a claim of crop-25%, which no record measures", tent_claim(&|m| {
            m.get_mut("survives").and_then(Value::as_array_mut).expect("survives").push(claim_entry("image", "wai-attack:crop", &[25]));
        })),
        tent_case("claim_frame_drop_on_pictures_unevidenced", "a claim of frame-drop for pictures: the record that measures it is of clips", tent_claim(&|m| {
            m.get_mut("survives").and_then(Value::as_array_mut).expect("survives").push(claim_entry("image", "wai-attack:frame-drop", &[]));
        })),
        tent_case("claim_without_evidence", "wai-mark:tent64's claim citing no record", tent_claim(&|m| {
            m.insert("evidence".into(), json!([]));
        })),
        case("claim_false_positive_exceeds", "a claim of an attack under which every item survived and one negative was read as marked", vec![text(&other), other_claim(json!([claim_entry("image", "org.example.attack.jpeg", &[50])]))]),
        tent_case("claim_duplicate_attack", "a claim listing one attack twice", tent_claim(&|m| {
            let s = m.get_mut("survives").and_then(Value::as_array_mut).expect("survives");
            let first = s[0].clone();
            s.push(first);
        })),
    ];
    v
}

/// `s` with its first `from` replaced by `to`; `from` must be there.
fn tamper(s: &str, from: &str, to: &str) -> String {
    assert!(s.contains(from), "tamper target {from} not in the record");
    s.replacen(from, to, 1)
}

/// The conformance record's members, as computed (its table the harness's).
fn conformance_evidence(x: &World) -> Evidence {
    let o = x.conformance.as_object().expect("a record");
    let cb = conformance_battery();
    let values: Vec<Vec<u8>> = o["values"].as_array().expect("values").iter().map(|v| unhex(v.as_str().expect("hex")).expect("hex")).collect();
    let table = o["table"]
        .as_array()
        .expect("a table")
        .iter()
        .map(|r| Counts {
            attack: r["attack"].as_str().expect("a name").into(),
            trials: r["trials"].as_u64().expect("n"),
            survived: r["survived"].as_u64().expect("n"),
            wrong: r["wrong"].as_u64().expect("n"),
            negatives: r["negatives"].as_u64().expect("n"),
            false_positives: r["false_positives"].as_u64().expect("n"),
        })
        .collect();
    Evidence {
        algorithm: mark::SCHEME.into(),
        algorithm_digest: mark::digest(),
        binding_type: "watermark".into(),
        media: Media::Image,
        corpus: x.conformance_doc.digest(),
        values,
        battery: cb.iter().enumerate().map(|(i, (n, t))| BatteryAttack::of(n, *t, i as u64)).collect(),
        harness: HARNESS.into(),
        table,
        issued_at: ISSUED.into(),
    }
}

fn frame_name(b: &[u8]) -> String {
    format!("frames/{}.rgb", hex(&sha256(b)))
}

/// The case as the verifier is given it, from its spec and the corpus's files.
fn to_case(s: &CaseSpec, files: &BTreeMap<String, Vec<u8>>) -> Case {
    let corpora = s
        .corpora
        .iter()
        .map(|(i, g)| {
            let t = match g {
                Given::File(f) => String::from_utf8(files[f].clone()).expect("UTF-8"),
                Given::Text(t) => t.clone(),
            };
            (*i, t)
        })
        .collect();
    let samples = if s.samples {
        files.iter().filter(|(k, _)| k.starts_with("frames/")).map(|(_, b)| (sha256(b), b.clone())).collect()
    } else {
        BTreeMap::new()
    };
    Case {
        records: s.records.clone(),
        corpora,
        samples,
        implements: if s.implements { vec![mark::SCHEME.into()] } else { vec![] },
        require_reproduced: s.reproduced,
    }
}

fn expected(r: &Result<Vec<Statement>, (Reason, usize)>) -> Value {
    match r {
        Ok(st) => json!({"outcome": "accept", "statements": st.iter().map(Statement::value).collect::<Vec<_>>()}),
        Err((reason, at)) => json!({"outcome": "refuse", "reason": reason.label(), "record": at}),
    }
}

/// The corpus's files, by path: the corpus documents, the conformance pictures, the records
/// and `vectors.json`.
pub fn generate() -> Vec<(String, Vec<u8>)> {
    generate_from(&world())
}

/// The files of a world already computed.
pub fn generate_from(x: &World) -> Vec<(String, Vec<u8>)> {
    let mut files: BTreeMap<String, Vec<u8>> = BTreeMap::new();
    files.insert(CONFORMANCE_CORPUS.into(), x.conformance_doc.text().into_bytes());
    for (n, d) in &x.tent64_docs {
        files.insert(format!("corpus/{n}.json"), d.text().into_bytes());
    }
    for (_, f) in &x.conformance_pictures {
        files.insert(frame_name(&f.data), f.data.clone());
    }
    files.insert("records/conformance.json".into(), text(&x.conformance).into_bytes());
    files.insert("records/conformance-claim.json".into(), text(&x.conformance_claim).into_bytes());
    for (n, r) in &x.tent64 {
        files.insert(format!("records/{n}.json"), text(r).into_bytes());
    }
    files.insert("records/tent64-claim.json".into(), text(&x.tent64_claim).into_bytes());

    let mut out = Vec::new();
    for s in cases(x) {
        let c = to_case(&s, &files);
        let got = verify_case(&c);
        let corpora: Vec<Value> = s
            .corpora
            .iter()
            .map(|(i, g)| match g {
                Given::File(f) => json!({"record": i, "file": f}),
                Given::Text(t) => json!({"record": i, "text": t}),
            })
            .collect();
        let samples: Vec<String> = c.samples.keys().map(|k| hex(k)).collect();
        out.push(json!({
            "name": s.name,
            "about": s.about,
            "policy": {"evidence": if s.reproduced { "reproduced" } else { "stated" }},
            "implements": c.implements,
            "records": s.records,
            "corpora": corpora,
            "samples": samples,
            "expected": expected(&got),
        }));
    }
    let doc = json!({
        "about": "the conformance corpus of extensions/robustness-evidence.md, written by wai_robustness_vectors gen",
        "algorithm": {"term": mark::SCHEME, "digest": hex(&mark::digest())},
        "harness": HARNESS,
        "cases": out,
    });
    files.insert("vectors.json".into(), (serde_json::to_string_pretty(&doc).expect("JSON") + "\n").into_bytes());
    files.into_iter().collect()
}

/// Check a corpus's files: every case gets the outcome it states. Returns the failures.
pub fn verify(files: &BTreeMap<String, Vec<u8>>) -> Vec<String> {
    let mut fails = Vec::new();
    let Some(doc) = files.get("vectors.json").and_then(|b| serde_json::from_slice::<Value>(b).ok()) else {
        return vec!["vectors.json does not read".into()];
    };
    for v in doc["cases"].as_array().map(Vec::as_slice).unwrap_or(&[]) {
        let name = v["name"].as_str().unwrap_or("?");
        let corpora = v["corpora"]
            .as_array()
            .map(Vec::as_slice)
            .unwrap_or(&[])
            .iter()
            .filter_map(|g| {
                let i = g["record"].as_u64()? as usize;
                let t = match (g.get("file").and_then(Value::as_str), g.get("text").and_then(Value::as_str)) {
                    (Some(f), _) => String::from_utf8(files.get(f)?.clone()).ok()?,
                    (_, Some(t)) => t.to_string(),
                    _ => return None,
                };
                Some((i, t))
            })
            .collect();
        let samples = v["samples"]
            .as_array()
            .map(Vec::as_slice)
            .unwrap_or(&[])
            .iter()
            .filter_map(|h| {
                let b = files.get(&format!("frames/{}.rgb", h.as_str()?))?;
                Some((sha256(b), b.clone()))
            })
            .collect();
        let case = Case {
            records: v["records"].as_array().map(Vec::as_slice).unwrap_or(&[]).iter().filter_map(|r| r.as_str().map(String::from)).collect(),
            corpora,
            samples,
            implements: v["implements"].as_array().map(Vec::as_slice).unwrap_or(&[]).iter().filter_map(|t| t.as_str().map(String::from)).collect(),
            require_reproduced: v["policy"]["evidence"] == "reproduced",
        };
        let got = expected(&verify_case(&case));
        if got != v["expected"] {
            fails.push(format!("{name}: expected {} got {got}", v["expected"]));
        }
    }
    fails
}
