//! `wai_int_payload_vectors` — writes and verifies the binary payload vectors of the
//! WAI-native integer codecs (`extensions/integer-payloads.md` §12).
//!
//!   wai_int_payload_vectors gen    <int-payload-conformance dir>
//!   wai_int_payload_vectors verify <int-payload-conformance dir>
//!
//! `gen` converts each committed JSON bitstream under `wai-rs/tests/vectors/` to its
//! pinned payload, beside the JSON (`*.wiv1`, `*.wih1`, `*.wia1`, `*.wis1`), derives the
//! refusal fixtures from those payloads (`refusals/*.bin`), and writes the index
//! `vectors.json`. Every file it writes is a function of the committed JSON alone, so
//! a second run writes the same bytes.
//!
//! `verify` regenerates every file in memory and requires it to equal the committed
//! one byte for byte; decodes each payload through `int_payload::decode_int` against
//! the parameter set the index names, and requires the output's SHA-256 to be the one
//! the vector's `sha256.json` sidecar states; and requires each refusal fixture to be
//! refused with the code the index states. Exit status 1 on any failure.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use serde_json::{json, Value};
use sha2::{Digest, Sha256};

use wai::int_payload::{declared_cost, decode_int, wire, IntDecoded, IntParams};
use wai::pin::HeldBytes;

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

fn sha256(b: &[u8]) -> String {
    hex(&Sha256::digest(b))
}

/// The tree below `wai/`, read from and written to by path relative to it.
struct Tree {
    wai: PathBuf,
    out: BTreeMap<String, Vec<u8>>,
}

impl Tree {
    fn read(&self, rel: &str) -> Vec<u8> {
        if let Some(b) = self.out.get(rel) {
            return b.clone();
        }
        let p = self.wai.join(rel);
        std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
    }
    fn json(&self, rel: &str) -> Value {
        serde_json::from_slice(&self.read(rel)).unwrap_or_else(|e| panic!("parse {rel}: {e}"))
    }
    fn put(&mut self, rel: &str, bytes: Vec<u8>) {
        self.out.insert(rel.to_string(), bytes);
    }
}

const V: &str = "wai-rs/tests/vectors";
const R: &str = "int-payload-conformance/refusals";

/// One decodable vector: its name, capability, payload path, the JSON it is converted
/// from, the set members it decodes against (member name → path) and its sidecar entry.
struct Vector {
    name: &'static str,
    cap: &'static str,
    payload: String,
    from: Value,
    set: Vec<(&'static str, String)>,
    sidecar: String,
}

fn int_hyper_set() -> Vec<(&'static str, String)> {
    vec![
        ("entropy.json", format!("{V}/int_hyper/tiny_entropy.json")),
        ("g_s.whm", format!("{V}/int_hyper/tiny_g_s.bin")),
        ("h_s.whm", format!("{V}/int_hyper/tiny_h_s.bin")),
    ]
}

/// Convert every committed JSON bitstream to its payload.
fn vectors(t: &mut Tree) -> Vec<Vector> {
    use wai::int_payload::{wia1_from_json, wih1_from_json, wis1_from_json, wiv1_from_json};
    let mut out = Vec::new();
    for (name, json_rel, wiv1) in [
        ("int_video", "int_video/int_video_bitstream.json", "int_video/int_video_bitstream.wiv1"),
        ("video_real", "int_video/video_real_bitstream.json", "int_video/video_real_bitstream.wiv1"),
    ] {
        let p = wiv1_from_json(&t.json(&format!("{V}/{json_rel}")), None).unwrap_or_else(|e| panic!("{name}: {e}"));
        t.put(&format!("{V}/{wiv1}"), p);
        out.push(Vector {
            name,
            cap: wai::codecs::CAP_VIDEO_INT_MOTION,
            payload: format!("{V}/{wiv1}"),
            from: json!({ "json": format!("{V}/{json_rel}") }),
            set: vec![],
            sidecar: format!("{V}/int_video/sha256.json"),
        });
    }
    let wih1 = wih1_from_json(&t.json(&format!("{V}/int_hyper/tiny_bitstream.json"))).expect("tiny WIH1");
    t.put(&format!("{V}/int_hyper/tiny_bitstream.wih1"), wih1.clone());
    out.push(Vector {
        name: "tiny",
        cap: wai::codecs::CAP_NEURAL_INT_HYPER,
        payload: format!("{V}/int_hyper/tiny_bitstream.wih1"),
        from: json!({ "json": format!("{V}/int_hyper/tiny_bitstream.json") }),
        set: int_hyper_set(),
        sidecar: format!("{V}/int_hyper/sha256.json"),
    });
    let nk = wiv1_from_json(&t.json(&format!("{V}/int_video/video_nk_bitstream.json")), Some(&wih1)).expect("video_nk WIV1");
    t.put(&format!("{V}/int_video/video_nk_bitstream.wiv1"), nk);
    out.push(Vector {
        name: "video_nk",
        cap: wai::codecs::CAP_VIDEO_INT_HYPER,
        payload: format!("{V}/int_video/video_nk_bitstream.wiv1"),
        from: json!({ "json": format!("{V}/int_video/video_nk_bitstream.json"), "keyframe": format!("{V}/int_hyper/tiny_bitstream.wih1") }),
        set: int_hyper_set(),
        sidecar: format!("{V}/int_video/sha256.json"),
    });
    let sr = t.json(&format!("{V}/int_audio/audio.json"))["sample_rate"].as_u64().expect("sample_rate") as u32;
    let wia1 = wia1_from_json(&t.json(&format!("{V}/int_audio/audio_bitstream.json")), sr).expect("audio WIA1");
    t.put(&format!("{V}/int_audio/audio_bitstream.wia1"), wia1);
    out.push(Vector {
        name: "audio",
        cap: wai::codecs::CAP_AUDIO_INT_CODEC,
        payload: format!("{V}/int_audio/audio_bitstream.wia1"),
        from: json!({ "json": format!("{V}/int_audio/audio_bitstream.json"), "sample_rate": format!("{V}/int_audio/audio.json") }),
        set: vec![("model.wam", format!("{V}/int_audio/audio_model.bin"))],
        sidecar: format!("{V}/int_audio/sha256.json"),
    });
    let wis1 = wis1_from_json(&t.read(&format!("{V}/int_splat/splat.json"))).expect("splat WIS1");
    t.put(&format!("{V}/int_splat/splat.wis1"), wis1);
    out.push(Vector {
        name: "splat",
        cap: wai::codecs::CAP_SPLAT_INT_CODEC,
        payload: format!("{V}/int_splat/splat.wis1"),
        from: json!({ "json": format!("{V}/int_splat/splat.json") }),
        set: vec![("g_s.whm", format!("{V}/int_splat/splat_gs.bin"))],
        sidecar: format!("{V}/int_splat/sha256.json"),
    });
    out
}

/// One refusal fixture: its name, capability, payload path, the code it is refused
/// with, whether parsing alone refuses it (`parse`) or the decode does (`decode`), and
/// the set its decode reads.
struct Fixture {
    name: &'static str,
    cap: &'static str,
    payload: String,
    code: &'static str,
    layer: &'static str,
    set: Vec<(&'static str, String)>,
}

fn set_u32(b: &mut [u8], off: usize, v: u32) {
    b[off..off + 4].copy_from_slice(&v.to_le_bytes());
}
fn get_u32(b: &[u8], off: usize) -> u32 {
    u32::from_le_bytes(b[off..off + 4].try_into().unwrap())
}

/// The refusal fixtures, each the committed payload changed in one place.
fn fixtures(t: &mut Tree) -> Vec<Fixture> {
    use wai::codecs::{CAP_AUDIO_INT_CODEC as AUD, CAP_NEURAL_INT_HYPER as HYP, CAP_SPLAT_INT_CODEC as SPL,
                      CAP_VIDEO_INT_HYPER as VNK, CAP_VIDEO_INT_MOTION as MOT};
    let iv = t.read(&format!("{V}/int_video/int_video_bitstream.wiv1"));
    let nk = t.read(&format!("{V}/int_video/video_nk_bitstream.wiv1"));
    let ih = t.read(&format!("{V}/int_hyper/tiny_bitstream.wih1"));
    let au = t.read(&format!("{V}/int_audio/audio_bitstream.wia1"));
    let sp = t.read(&format!("{V}/int_splat/splat.wis1"));
    let mut out = Vec::new();
    let mut fx = |t: &mut Tree, name: &'static str, cap: &'static str, bytes: Option<Vec<u8>>, payload: Option<String>, code: &'static str, layer: &'static str| {
        let payload = match bytes {
            Some(b) => {
                let rel = format!("{R}/{name}.bin");
                t.put(&rel, b);
                rel
            }
            None => payload.expect("a fixture names its payload"),
        };
        let set = match cap {
            c if c == HYP || c == VNK => int_hyper_set(),
            c if c == AUD => vec![("model.wam", format!("{V}/int_audio/audio_model.bin"))],
            c if c == SPL => vec![("g_s.whm", format!("{V}/int_splat/splat_gs.bin"))],
            _ => vec![],
        };
        out.push(Fixture { name, cap, payload, code, layer, set });
    };
    let edit = |base: &[u8], f: &dyn Fn(&mut Vec<u8>)| {
        let mut b = base.to_vec();
        f(&mut b);
        Some(b)
    };

    // ── WIV1 ── (keyframe 1 is video_nk: 16×16, the nested tiny WIH1)
    let parsed = wire::parse_wiv1(&nk, Some(1)).expect("video_nk parses");
    let nk_l = at(&nk, parsed.rans) - 4; // offset of L
    let nk_k = at(&nk, parsed.wih1.expect("keyframe 1")) - 4; // offset of K
    fx(t, "wiv1_json_form", MOT, None, Some(format!("{V}/int_video/int_video_bitstream.json")), "bad_magic", "parse");
    fx(t, "wiv1_under_audio", AUD, Some(nk.clone()), None, "format_capability_mismatch", "parse");
    fx(t, "wiv1_keyframe_mismatch", MOT, Some(nk.clone()), None, "keyframe_capability_mismatch", "parse");
    fx(t, "wiv1_version", VNK, edit(&nk, &|b| b[4] = 2), None, "version", "parse");
    fx(t, "wiv1_reserved", VNK, edit(&nk, &|b| b[7] = 1), None, "reserved_nonzero", "parse");
    fx(t, "wiv1_mv_precision", VNK, edit(&nk, &|b| b[5] = 3), None, "field_range", "parse");
    fx(t, "wiv1_q_zero", VNK, edit(&nk, &|b| set_u32(b, 24, 0)), None, "field_range", "parse");
    fx(t, "wiv1_too_large", VNK, edit(&nk, &|b| { set_u32(b, 12, 8192); set_u32(b, 16, 8192) }), None, "too_large", "parse");
    // 2^20 + 1 frames of 16 × 16: within the clip's 2^30 bytes, past the frame count.
    fx(t, "wiv1_too_many_frames", VNK, edit(&nk, &|b| set_u32(b, 8, (1 << 20) + 1)), None, "too_large", "parse");
    // Block 1 over 131074 frames: 2·256·131073 motion-vector elements, past 2^26.
    fx(t, "wiv1_mv_field", VNK, edit(&nk, &|b| { set_u32(b, 8, 131_074); set_u32(b, 20, 1) }), None, "too_large", "parse");
    fx(t, "wiv1_cdf_malformed", VNK, edit(&nk, &|b| { let v = get_u32(b, 40); set_u32(b, 44, v) }), None, "cdf_malformed", "parse");
    fx(t, "wiv1_stream_length", VNK, edit(&nk, &|b| { let l = get_u32(b, nk_l); set_u32(b, nk_l, l - 2) }), None, "stream_length", "parse");
    fx(t, "wiv1_truncated", VNK, edit(&nk, &|b| b.truncate(nk_k + 2)), None, "stream_length", "parse");
    fx(t, "wiv1_nested_length", VNK, edit(&nk, &|b| { let k = get_u32(b, nk_k); set_u32(b, nk_k, k + 4) }), None, "nested_length", "parse");
    fx(t, "wiv1_nested_version", VNK, edit(&nk, &|b| b[nk_k + 4 + 4] = 2), None, "version", "parse");
    fx(t, "wiv1_trailing", MOT, edit(&iv, &|b| b.push(0)), None, "trailing", "parse");
    let iv_l = at(&iv, wire::parse_wiv1(&iv, Some(0)).expect("int_video parses").rans) - 4;
    fx(t, "wiv1_exhausted", MOT, edit(&iv, &|b| { set_u32(b, iv_l, 8); b.truncate(iv_l + 4 + 8) }), None, "stream_exhausted", "decode");
    // A word appended to the residual stream (the last section), which the decode
    // never reads.
    fx(t, "wiv1_unread_word", MOT, edit(&iv, &|b| { let l = get_u32(b, iv_l); set_u32(b, iv_l, l + 4); b.extend_from_slice(&[0; 4]) }), None, "stream_noncanonical", "decode");
    // A one-pixel clip whose three symbols are 0 (frequency 2^15 of 2^16), encoded
    // from the state 2^31 + 1: the stream is the words [1, 4], and the decode ends at
    // 2^31 + 1. Encoded from 2^31, the canonical stream is [0, 4] (`grey_pixel`).
    fx(t, "wiv1_end_state", MOT, Some(grey_pixel(1)), None, "stream_noncanonical", "decode");
    // A one-frame clip's header fixes mv_precision 1 and block 1 (§3): the one-pixel
    // clip with block 4, or half-pel, is refused at parse.
    fx(t, "wiv1_one_frame_block", MOT, edit(&grey_pixel(0), &|b| set_u32(b, 20, 4)), None, "field_range", "parse");
    fx(t, "wiv1_one_frame_mv_precision", MOT, edit(&grey_pixel(0), &|b| b[5] = 2), None, "field_range", "parse");
    // One frame, the nested keyframe: the residual stream codes no symbol, so it must be
    // the state 2^31 alone, and 2^31 + 1 is refused.
    let one = |state: u32| {
        let mut v = wire::parse_wiv1(&nk, Some(1)).expect("video_nk parses");
        let rans: Vec<u8> = [state, 0].iter().flat_map(|w| w.to_le_bytes()).collect();
        (v.n_frames, v.mv_precision, v.block) = (1, 1, 1);
        v.mvs.clear();
        wire::write_wiv1(&wire::Wiv1 { rans: &rans, ..v })
    };
    fx(t, "wiv1_one_frame_end_state", VNK, Some(one((1 << 31) + 1)), None, "stream_noncanonical", "decode");
    let geo = {
        let mut v = wire::parse_wiv1(&nk, Some(1)).expect("video_nk parses");
        (v.h, v.w) = (12, 12); // the same 2 × 2 block grid; the keyframe decodes 16 × 16
        wire::write_wiv1(&v)
    };
    fx(t, "wiv1_geometry", VNK, Some(geo.clone()), None, "geometry_mismatch", "decode");
    // The keyframe's size is checked against the clip's h × w before any symbol of the
    // keyframe is decoded (§7): with one byte of the keyframe's last stream words
    // changed, so that its stream no longer ends canonically, each is still
    // geometry_mismatch. Named by the byte changed and the value XORed into it.
    for (name, back, x) in [
        ("wiv1_geometry_flip_2146_4a", 2, 0x4a), ("wiv1_geometry_flip_2138_be", 10, 0xbe),
        ("wiv1_geometry_flip_2136_96", 12, 0x96), ("wiv1_geometry_flip_2139_03", 9, 0x03),
        ("wiv1_geometry_flip_2133_fe", 15, 0xfe), ("wiv1_geometry_flip_2136_9b", 12, 0x9b),
        ("wiv1_geometry_flip_2137_8c", 11, 0x8c),
    ] {
        fx(t, name, VNK, edit(&geo, &|b| { let at = b.len() - back; b[at] ^= x }), None, "geometry_mismatch", "decode");
    }

    // ── WIH1 ── (the tiny conditional codec: m 4, 4×4; n_z 2, 2×2)
    fx(t, "wih1_json_form", HYP, None, Some(format!("{V}/int_hyper/tiny_bitstream.json")), "bad_magic", "parse");
    fx(t, "wih1_under_video", MOT, Some(ih.clone()), None, "format_capability_mismatch", "parse");
    fx(t, "wih1_reserved", HYP, edit(&ih, &|b| b[6] = 1), None, "reserved_nonzero", "parse");
    fx(t, "wih1_zero_dim", HYP, edit(&ih, &|b| set_u32(b, 8, 0)), None, "field_range", "parse");
    fx(t, "wih1_too_large", HYP, edit(&ih, &|b| { set_u32(b, 24, 65_536); set_u32(b, 28, 65_536) }), None, "too_large", "parse");
    fx(t, "wih1_stream_length", HYP, edit(&ih, &|b| set_u32(b, 32, 6)), None, "stream_length", "parse");
    fx(t, "wih1_trailing", HYP, edit(&ih, &|b| b.push(0)), None, "trailing", "parse");
    fx(t, "wih1_model_mismatch", HYP, edit(&ih, &|b| set_u32(b, 20, 3)), None, "model_mismatch", "decode");
    fx(t, "wih1_geometry", HYP, edit(&ih, &|b| { set_u32(b, 12, 8); set_u32(b, 16, 8) }), None, "geometry_mismatch", "decode");
    let ih_ly = at(&ih, wire::parse_wih1(&ih).expect("tiny parses").rans) - 4;
    fx(t, "wih1_exhausted", HYP, edit(&ih, &|b| { set_u32(b, ih_ly, 8); b.truncate(ih_ly + 4 + 8) }), None, "stream_exhausted", "decode");
    // The order of the decode's checks (§7): every network is planned, caps included,
    // before a shape is compared, so a 2100 × 2100 latent, whose g_s would form a
    // buffer past the cap, is too_large rather than h_s's geometry_mismatch; and a
    // symbol that runs its stream out is refused before its value is rebuilt, so a z
    // stream of 8 zero bytes, whose first symbol rebuilt against a median of i64::MAX
    // would leave i64, is stream_exhausted (the set's entropy.json is
    // sets/entropy_median_max.json).
    fx(t, "wih1_order_caps_before_geometry", HYP, edit(&ih, &|b| { set_u32(b, 12, 2100); set_u32(b, 16, 2100) }), None, "too_large", "decode");
    let short_z = {
        let mut k = wire::parse_wih1(&ih).expect("tiny parses");
        k.z_rans = &[0; 8];
        wire::write_wih1(&k)
    };
    fx(t, "wih1_order_exhausted_before_value", HYP, Some(short_z), None, "stream_exhausted", "decode");
    fx(t, "wih1_unread_word", HYP, edit(&ih, &|b| { let l = get_u32(b, ih_ly); set_u32(b, ih_ly, l + 4); b.extend_from_slice(&[0; 4]) }), None, "stream_noncanonical", "decode");

    // ── WIA1 ── (the header's n_ch at 12 and cdf_len at 28; the first table follows the
    // n_ch medians, at 32 + 8·n_ch, and its last entry is the one that must be 65536)
    let au_n = at(&au, wire::parse_wia1(&au).expect("audio parses").rans) - 4; // offset of L
    let au_t0 = 32 + 8 * get_u32(&au, 12) as usize; // the first table
    let au_cl = u16::from_le_bytes([au[28], au[29]]) as usize; // cdf_len
    let au_last = au_t0 + (au_cl - 1) * 4; // the first table's last entry
    fx(t, "wia1_json_form", AUD, None, Some(format!("{V}/int_audio/audio_bitstream.json")), "bad_magic", "parse");
    fx(t, "wia1_under_splat", SPL, Some(au.clone()), None, "format_capability_mismatch", "parse");
    fx(t, "wia1_afrac_zero", AUD, edit(&au, &|b| b[5] = 0), None, "field_range", "parse");
    fx(t, "wia1_sr_zero", AUD, edit(&au, &|b| set_u32(b, 8, 0)), None, "field_range", "parse");
    fx(t, "wia1_median_range", AUD, edit(&au, &|b| b[32..40].copy_from_slice(&((1i64 << 47) + 1).to_le_bytes())), None, "field_range", "parse");
    fx(t, "wia1_cdf_malformed", AUD, edit(&au, &|b| set_u32(b, au_last, 65_535)), None, "cdf_malformed", "parse");
    // §2's order: the tables are read one at a time, each present and then checked, so
    // a malformed first table is cdf_malformed though the payload ends inside the second.
    fx(t, "wia1_order_table_before_length", AUD, edit(&au, &|b| { set_u32(b, au_last, 65_535); b.truncate(au_t0 + au_cl * 4 + 10) }), None, "cdf_malformed", "parse");
    fx(t, "wia1_too_large", AUD, edit(&au, &|b| { set_u32(b, 12, 65_536); set_u32(b, 16, 2_048) }), None, "too_large", "parse");
    fx(t, "wia1_trailing", AUD, edit(&au, &|b| b.push(0)), None, "trailing", "parse");
    fx(t, "wia1_afrac_model", AUD, edit(&au, &|b| b[5] = 11), None, "model_mismatch", "decode");
    fx(t, "wia1_t_out", AUD, edit(&au, &|b| { let n = get_u32(b, 20); set_u32(b, 20, n + 1) }), None, "geometry_mismatch", "decode");
    // A latent twice as long as t_out needs: one sample fewer still gives t_out (§7).
    fx(t, "wia1_latent_too_long", AUD, edit(&au, &|b| { let n = get_u32(b, 16); set_u32(b, 16, 2 * n) }), None, "geometry_mismatch", "decode");
    fx(t, "wia1_exhausted", AUD, edit(&au, &|b| { set_u32(b, au_n, 8); b.truncate(au_n + 4 + 8) }), None, "stream_exhausted", "decode");
    fx(t, "wia1_unread_word", AUD, edit(&au, &|b| { let l = get_u32(b, au_n); set_u32(b, au_n, l + 4); b.extend_from_slice(&[0; 4]) }), None, "stream_noncanonical", "decode");

    // ── WIS1 ── (32 channels of 8×8 → 14 planes of 32×32)
    let wis = |f: &dyn Fn(&mut wire::Wis1<'_>)| {
        let mut v = wire::parse_wis1(&sp).expect("splat parses");
        f(&mut v);
        Some(wire::write_wis1(&v))
    };
    fx(t, "wis1_json_form", SPL, None, Some(format!("{V}/int_splat/splat.json")), "bad_magic", "parse");
    fx(t, "wis1_range_order", SPL, wis(&|v| v.ranges[0] = (wire::Binary32(1f32.to_bits()), wire::Binary32((-1f32).to_bits()))), None, "field_range", "parse");
    fx(t, "wis1_range_nan", SPL, wis(&|v| v.ranges[0].0 = wire::Binary32(f32::NAN.to_bits())), None, "field_range", "parse");
    fx(t, "wis1_n_gaussians", SPL, wis(&|v| v.n_gaussians = v.s * v.s + 1), None, "field_range", "parse");
    fx(t, "wis1_cdf_len", SPL, edit(&sp, &|b| b[32 + 14 * 8..32 + 14 * 8 + 2].copy_from_slice(&2u16.to_le_bytes())), None, "field_range", "parse");
    fx(t, "wis1_trailing", SPL, edit(&sp, &|b| b.push(0)), None, "trailing", "parse");
    fx(t, "wis1_geometry", SPL, wis(&|v| { v.attr = 13; v.ranges.truncate(13) }), None, "geometry_mismatch", "decode");
    fx(t, "wis1_afrac_model", SPL, edit(&sp, &|b| b[5] = 11), None, "model_mismatch", "decode");
    let sp_l = at(&sp, wire::parse_wis1(&sp).expect("splat parses").rans) - 4;
    fx(t, "wis1_exhausted", SPL, edit(&sp, &|b| { set_u32(b, sp_l, 8); b.truncate(sp_l + 4 + 8) }), None, "stream_exhausted", "decode");
    fx(t, "wis1_unread_word", SPL, edit(&sp, &|b| { let l = get_u32(b, sp_l); set_u32(b, sp_l, l + 4); b.extend_from_slice(&[0; 4]) }), None, "stream_noncanonical", "decode");
    let sets = "int-payload-conformance/sets";
    t.put(&format!("{sets}/entropy_median_max.json"), entropy_median_max(t));
    for f in &mut out {
        if f.name == "wih1_order_exhausted_before_value" {
            f.set = vec![
                ("entropy.json", format!("{sets}/entropy_median_max.json")),
                ("g_s.whm", format!("{V}/int_hyper/tiny_g_s.bin")),
                ("h_s.whm", format!("{V}/int_hyper/tiny_h_s.bin")),
            ];
        }
    }
    out
}

/// A `WIV1` keyframe-0 clip of one 1 × 1 frame whose three residual symbols are 0
/// against the table `[0, 32768, 49152, 65536]`, with the stream `[w0, 4]`: `w0 = 0` is
/// the canonical coding of those symbols, which decodes to a grey pixel; `w0 = 1`
/// codes the same symbols from the state `2^31 + 1`.
fn grey_pixel(w0: u32) -> Vec<u8> {
    let rans: Vec<u8> = [w0, 4].iter().flat_map(|w| w.to_le_bytes()).collect();
    wire::write_wiv1(&wire::Wiv1 {
        mv_precision: 1, keyframe: 0, n_frames: 1, h: 1, w: 1, block: 1, q: 1, offset: 0,
        cdf: vec![0, 32_768, 49_152, 65_536], mvs: vec![], rans: &rans, wih1: None,
    })
}

/// The tiny conditional codec's entropy tables with the first hyper-latent channel's
/// median at `i64::MAX` and its symbol offset at 1, so that channel's first symbol, if
/// its value were used, would rebuild past `i64`.
fn entropy_median_max(t: &Tree) -> Vec<u8> {
    let mut e = t.json(&format!("{V}/int_hyper/tiny_entropy.json"));
    e["z_median_q"][0] = json!(i64::MAX);
    e["z_offsets"][0] = json!(1);
    let mut text = serde_json::to_string(&e).expect("entropy");
    text.push('\n');
    text.into_bytes()
}

/// The offset in `base` at which `part`, a slice of it, starts.
fn at(base: &[u8], part: &[u8]) -> usize {
    part.as_ptr() as usize - base.as_ptr() as usize
}

/// Build every file: the payloads, the fixtures, the index.
fn build(wai: &Path) -> Tree {
    let mut t = Tree { wai: wai.to_path_buf(), out: BTreeMap::new() };
    let vs = vectors(&mut t);
    let fs = fixtures(&mut t);
    let set = |s: &[(&str, String)]| -> Value { s.iter().map(|(k, v)| (k.to_string(), json!(v))).collect::<serde_json::Map<_, _>>().into() };
    let index = json!({
        "format": "wai-int-payload-vectors/1",
        "about": "Binary payload vectors of the WAI-native integer codecs and their refusal fixtures (extensions/integer-payloads.md §12). Paths are relative to wai/. Written by wai_int_payload_vectors gen.",
        "vectors": vs.iter().map(|v| json!({
            "name": v.name,
            "capability": v.cap,
            "payload": v.payload,
            "payload_sha256": sha256(&t.read(&v.payload)),
            "converted_from": v.from,
            "set": set(&v.set),
            "sidecar": v.sidecar,
        })).collect::<Vec<_>>(),
        "refusals": fs.iter().map(|f| json!({
            "name": f.name,
            "capability": f.cap,
            "payload": f.payload,
            "code": f.code,
            "layer": f.layer,
            "set": set(&f.set),
        })).collect::<Vec<_>>(),
    });
    let mut text = serde_json::to_string_pretty(&index).expect("index");
    text.push('\n');
    t.put("int-payload-conformance/vectors.json", text.into_bytes());
    t
}

/// The SHA-256 digests of a decode's output, in the form its sidecar entry states.
fn digests(d: &IntDecoded) -> Value {
    match d {
        IntDecoded::Video { frames, .. } => {
            let per: Vec<[u8; 32]> = frames.iter().map(|f| Sha256::digest(f).into()).collect();
            let clip = Sha256::digest(per.concat());
            json!({ "frames_sha256": per.iter().map(|d| hex(d)).collect::<Vec<_>>(), "clip_sha256": hex(&clip) })
        }
        IntDecoded::Image { rgb, .. } => json!({ "sha256": sha256(rgb) }),
        IntDecoded::Pcm { samples, .. } => {
            let le: Vec<u8> = samples.iter().flat_map(|s| s.to_le_bytes()).collect();
            json!({ "sha256": sha256(&le) })
        }
        IntDecoded::Attr { bytes, .. } => json!({ "sha256": sha256(bytes) }),
        _ => json!(null),
    }
}

/// `f` with the parameters of the set `set` names (member → path) for `cap`.
fn with_set<R>(t: &Tree, cap: &str, set: &Value, f: impl FnOnce(&IntParams<'_>) -> R) -> R {
    let owned: BTreeMap<String, Vec<u8>> = set.as_object().into_iter().flatten()
        .map(|(k, v)| (k.clone(), t.read(v.as_str().expect("a set path")))).collect();
    let view: BTreeMap<String, &[u8]> = owned.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect();
    let params = wai::int_payload::resolve(cap, None, &[HeldBytes::Set { listing: b"", files: &view }], None)
        .unwrap_or_else(|_| IntParams::none());
    f(&params)
}

/// Decode `payload` through `cap` against the set `set` names (member → path).
fn decode(t: &Tree, cap: &str, payload: &[u8], set: &Value) -> Result<IntDecoded, wai::int_payload::IntPayloadError> {
    with_set(t, cap, set, |p| decode_int(cap, payload, p))
}

fn verify(wai: &Path) -> bool {
    let t = build(wai);
    let mut ok = true;
    for (rel, bytes) in &t.out {
        match std::fs::read(wai.join(rel)) {
            Ok(have) if &have == bytes => {}
            Ok(_) => { println!("FAIL {rel}: differs from its regeneration"); ok = false }
            Err(e) => { println!("FAIL {rel}: {e}"); ok = false }
        }
    }
    let index: Value = serde_json::from_slice(&t.out["int-payload-conformance/vectors.json"]).expect("index");
    for v in index["vectors"].as_array().expect("vectors") {
        let (name, cap) = (v["name"].as_str().unwrap(), v["capability"].as_str().unwrap());
        let payload = t.read(v["payload"].as_str().unwrap());
        match decode(&t, cap, &payload, &v["set"]) {
            Ok(d) => {
                let want = &t.json(v["sidecar"].as_str().unwrap())[name];
                let got = digests(&d);
                let same = got.as_object().unwrap().iter().all(|(k, g)| &want[k] == g);
                if same {
                    println!("ok   {name} ({cap}): decodes to its sidecar's SHA-256");
                } else {
                    println!("FAIL {name} ({cap}): decoded {got} ≠ sidecar");
                    ok = false;
                }
            }
            Err(e) => { println!("FAIL {name} ({cap}): {e}"); ok = false }
        }
    }
    let mut seen = std::collections::BTreeSet::new();
    for f in index["refusals"].as_array().expect("refusals") {
        let (name, cap, code) = (f["name"].as_str().unwrap(), f["capability"].as_str().unwrap(), f["code"].as_str().unwrap());
        let payload = t.read(f["payload"].as_str().unwrap());
        let parsed = match wai::codecs::pinned_int_payload(cap) {
            Some(wire::MAGIC_WIV1) => wire::parse_wiv1(&payload, Some((cap == wai::codecs::CAP_VIDEO_INT_HYPER) as u8)).err(),
            Some(wire::MAGIC_WIH1) => wire::parse_wih1(&payload).err(),
            Some(wire::MAGIC_WIA1) => wire::parse_wia1(&payload).err(),
            Some(wire::MAGIC_WIS1) => wire::parse_wis1(&payload).err(),
            _ => None,
        };
        let layer_ok = (f["layer"] == "parse") == parsed.is_some();
        // The declared cost reads what a decode reads before its first symbol, so it
        // refuses with the decode's code, or admits the payload when the decode's
        // refusal comes from a stream or a network run (§7, steps 5 and 6).
        let declared = with_set(&t, cap, &f["set"], |p| declared_cost(cap, &payload, p).map(|_| ()).map_err(|e| e.code()));
        let declared_ok = match declared {
            Err(c) => c == code,
            Ok(()) => matches!(code, "stream_exhausted" | "stream_noncanonical" | "field_range"),
        };
        match decode(&t, cap, &payload, &f["set"]) {
            Err(e) if e.code() == code && layer_ok && declared_ok => { seen.insert(code); println!("ok   {name}: {code}") }
            Err(e) if e.code() == code && layer_ok => {
                println!("FAIL {name}: the decode gives {code}, the declared cost {declared:?}");
                ok = false
            }
            Err(e) => { println!("FAIL {name}: refused with {e}, want {code} at {}", f["layer"]); ok = false }
            Ok(_) => { println!("FAIL {name}: decoded, want {code}"); ok = false }
        }
    }
    println!("{} refusal codes covered by fixtures", seen.len());
    // The end-state fixture's canonical twin, the same symbols coded from 2^31, decodes.
    match decode(&t, wai::codecs::CAP_VIDEO_INT_MOTION, &grey_pixel(0), &Value::Null) {
        Ok(IntDecoded::Video { frames, .. }) if frames == [vec![128u8; 3]] => {
            println!("ok   grey_pixel: wiv1_end_state's canonical twin decodes to one grey pixel")
        }
        other => { println!("FAIL grey_pixel: {other:?}"); ok = false }
    }
    ok
}

fn main() -> ExitCode {
    let args: Vec<String> = std::env::args().collect();
    let (Some(cmd), Some(dir)) = (args.get(1), args.get(2)) else {
        eprintln!("usage: wai_int_payload_vectors gen|verify <int-payload-conformance dir>");
        return ExitCode::from(2);
    };
    let wai = Path::new(dir).join("..");
    match cmd.as_str() {
        "gen" => {
            let t = build(&wai);
            for (rel, bytes) in &t.out {
                let p = wai.join(rel);
                if let Some(d) = p.parent() {
                    std::fs::create_dir_all(d).expect("create directory");
                }
                std::fs::write(&p, bytes).unwrap_or_else(|e| panic!("write {}: {e}", p.display()));
            }
            println!("wrote {} files", t.out.len());
            ExitCode::SUCCESS
        }
        "verify" => {
            if verify(&wai) { ExitCode::SUCCESS } else { ExitCode::FAILURE }
        }
        _ => {
            eprintln!("usage: wai_int_payload_vectors gen|verify <int-payload-conformance dir>");
            ExitCode::from(2)
        }
    }
}
