//! Conformance corpus for the haptics extension (`haptic-conformance/`).
//! Sample-equivalence: a sink reconstructs each signal and MUST match
//! the expected BLAKE3 — hash equality, no tolerance. Signals are
//! self-contained (no sources), so vectors are just `.whap` + a hash.
//!
//!   wai_haptic_vectors gen <dir>  |  verify <dir>

use std::path::Path;
use wai::haptic::{HapticContract, HapticKey, HapticSignal};
use wai::world::{Fx, NUMERIC_FIXED64};

fn main() {
    let a: Vec<String> = std::env::args().collect();
    match (a.get(1).map(String::as_str), a.get(2)) {
        (Some("gen"), Some(d)) => generate(Path::new(d)),
        (Some("verify"), Some(d)) => verify(Path::new(d)),
        _ => {
            eprintln!("usage: wai_haptic_vectors <gen|verify> <corpus-dir>");
            std::process::exit(2);
        }
    }
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}
fn f(n: i32, d: i32) -> Fx {
    Fx::from_int(n) / Fx::from_int(d)
}

fn sig(channels: Vec<Vec<HapticKey>>, dur: u64) -> HapticSignal {
    let c = HapticContract {
        sample_rate: 1000,
        numeric: NUMERIC_FIXED64.into(),
        channels: channels.len() as u32,
        duration: dur,
    };
    HapticSignal { contract_bytes: c.to_canonical_json(), contract: c, channels }
}

fn vectors() -> Vec<(&'static str, HapticSignal)> {
    vec![
        // a rising ramp
        ("ramp", sig(vec![vec![
            HapticKey { pos_sample: 0, intensity: Fx::ZERO, sharpness: f(1, 2) },
            HapticKey { pos_sample: 300, intensity: Fx::ONE, sharpness: f(1, 2) },
        ]], 300)),
        // a sharp "thud" then decay
        ("thud", sig(vec![vec![
            HapticKey { pos_sample: 0, intensity: Fx::ONE, sharpness: Fx::ONE },
            HapticKey { pos_sample: 60, intensity: Fx::ZERO, sharpness: f(1, 4) },
        ]], 120)),
        // two-channel pulse (dual rumble): strong vs soft
        ("dual_pulse", sig(vec![
            vec![
                HapticKey { pos_sample: 0, intensity: Fx::ZERO, sharpness: Fx::ZERO },
                HapticKey { pos_sample: 50, intensity: Fx::ONE, sharpness: f(3, 4) },
                HapticKey { pos_sample: 100, intensity: Fx::ZERO, sharpness: Fx::ZERO },
            ],
            vec![
                HapticKey { pos_sample: 0, intensity: f(1, 3), sharpness: f(1, 4) },
                HapticKey { pos_sample: 100, intensity: f(1, 3), sharpness: f(1, 4) },
            ],
        ], 100)),
    ]
}

fn generate(dir: &Path) {
    let v = dir.join("vectors");
    std::fs::create_dir_all(&v).expect("mkdir");
    let mut manifest = serde_json::Map::new();
    for (name, s) in vectors() {
        std::fs::write(v.join(format!("{name}.whap")), s.to_bytes()).expect("write whap");
        manifest.insert(
            format!("{name}.whap"),
            serde_json::json!({ "channels": s.contract.channels, "signal_hash": hex(&s.signal_hash()) }),
        );
    }
    std::fs::write(
        dir.join("expected_hashes.json"),
        serde_json::to_string_pretty(&serde_json::Value::Object(manifest)).unwrap() + "\n",
    )
    .expect("manifest");
    println!("generated haptic vectors into {}", dir.display());
}

fn verify(dir: &Path) {
    let m: serde_json::Value =
        serde_json::from_slice(&std::fs::read(dir.join("expected_hashes.json")).expect("manifest")).expect("parse");
    let v = dir.join("vectors");
    let mut fails = 0;
    for (file, spec) in m.as_object().expect("obj") {
        let s = HapticSignal::from_bytes(&std::fs::read(v.join(file)).expect("whap")).expect("parse");
        let ok = hex(&s.signal_hash()) == spec["signal_hash"].as_str().unwrap();
        println!("{} {file}", if ok { "PASS" } else { "FAIL" });
        if !ok {
            fails += 1;
        }
    }
    if fails > 0 {
        eprintln!("{fails} failed — NOT conformant");
        std::process::exit(1);
    }
    println!("all vectors pass: sample-equivalence holds");
}
