//! Conformance corpus for scene carriage (`live-conformance/scene/`,
//! extensions/scene-carriage.md §9).
//!
//!   wai_scene_vectors gen    <corpus-dir>   write vectors.json and the corpus's files from the
//!                                           worlds and documents built in code and the inputs
//!                                           other corpora commit, which must match their pins
//!   wai_scene_vectors verify <corpus-dir>   the inputs match their pins, every file regenerates
//!                                           byte for byte, no other file but README.md and
//!                                           .gitattributes is there, and every vector gets the
//!                                           outcome it states
//!
//! The generator and this verifier are one implementation. The corpus's second implementation
//! is `live-conformance/scene_verify.py`, written from the extension's text and sharing no code
//! with this binary.

use std::collections::BTreeMap;
use std::path::Path;
use wai::scene_map::corpus;

fn walk(root: &Path, rel: &str, out: &mut BTreeMap<String, Vec<u8>>) -> Result<(), String> {
    let d = root.join(rel);
    let entries = std::fs::read_dir(&d).map_err(|e| format!("read {}: {e}", d.display()))?;
    for e in entries.flatten() {
        let name = e.file_name().to_string_lossy().into_owned();
        let key = if rel.is_empty() { name.clone() } else { format!("{rel}/{name}") };
        if e.path().is_dir() {
            walk(root, &key, out)?;
        } else if key != "README.md" && key != ".gitattributes" {
            out.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?);
        }
    }
    Ok(())
}

fn inputs(dir: &Path) -> BTreeMap<String, Vec<u8>> {
    corpus::INPUT_FILES.iter().filter_map(|(p, _)| std::fs::read(dir.join(p)).ok().map(|b| (p.to_string(), b))).collect()
}

fn die(e: impl std::fmt::Display) -> ! {
    eprintln!("{e}");
    std::process::exit(1)
}

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)) => {
            let dir = Path::new(d);
            let inp = inputs(dir);
            let bad = corpus::check_inputs(&inp);
            if !bad.is_empty() {
                die(format!("the inputs {bad:?} are missing or do not match their pins in corpus::INPUT_FILES"));
            }
            for (name, body) in corpus::generate(&inp) {
                let p = dir.join(&name);
                if let Some(parent) = p.parent() {
                    std::fs::create_dir_all(parent).unwrap_or_else(|e| die(format!("mkdir {}: {e}", parent.display())));
                }
                std::fs::write(&p, body).unwrap_or_else(|e| die(format!("write {}: {e}", p.display())));
            }
            println!("wrote the scene corpus to {d}");
        }
        (Some("verify"), Some(d)) => {
            let dir = Path::new(d);
            let mut files = BTreeMap::new();
            walk(dir, "", &mut files).unwrap_or_else(|e| die(e));
            let bad = corpus::verify(&files, &inputs(dir));
            if !bad.is_empty() {
                for b in &bad {
                    eprintln!("FAIL {b}");
                }
                std::process::exit(1);
            }
            println!("scene corpus: every file regenerates byte for byte and every vector reaches its outcome");
        }
        _ => die("usage: wai_scene_vectors gen|verify <corpus-dir>"),
    }
}
