//! Verify static ship assembly against multi-snapshot, multi-instance F10
//! captures: cluster every part's ship-relative transform across ALL ships in
//! ALL snapshots, and compare the dominant clusters with `assemble_ship`.
//!
//!   cargo run --release --example capture_verify -- <stage.xpr> <ship_id> <log> [log…]
//!
//! Every draw is position-validated against the ship's parts (any LOD, set
//! based). Each `bdy_04`(-LOD) draw acts as a ship reference; every validated
//! part draw within ship range contributes `rel = WV_ref⁻¹ ∘ WV_part`. The true
//! per-part placements appear as tight clusters repeated once PER SHIP per
//! snapshot; cross-ship pairings scatter and stay below the cluster threshold.
use std::collections::HashSet;
use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
use sylpheed_formats::ship::{assemble_ship, is_base_part, ship_id_of};
use sylpheed_formats::ship_capture::{parse_capture, CapturedDraw};

type M3 = [[f64; 3]; 3];

fn transpose(m: &M3) -> M3 {
    [
        [m[0][0], m[1][0], m[2][0]],
        [m[0][1], m[1][1], m[2][1]],
        [m[0][2], m[1][2], m[2][2]],
    ]
}
fn mmul(a: &M3, b: &M3) -> M3 {
    let mut o = [[0.0; 3]; 3];
    for i in 0..3 {
        for j in 0..3 {
            o[i][j] = (0..3).map(|k| a[i][k] * b[k][j]).sum();
        }
    }
    o
}
fn mv(m: &M3, v: [f64; 3]) -> [f64; 3] {
    [
        m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
        m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
        m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
    ]
}

fn main() {
    let a: Vec<String> = std::env::args().collect();
    let bytes = std::fs::read(&a[1]).unwrap();
    let id = &a[2];

    // Part position sets (base + LODs share the local frame) + per-variant vcounts.
    let names = xbg7_resource_names(&bytes);
    let base_parts: Vec<String> = names
        .iter()
        .filter(|n| is_base_part(n) && ship_id_of(n) == Some(id.as_str()))
        .cloned()
        .collect();
    let mut want: HashSet<String> = base_parts.iter().cloned().collect();
    for p in &base_parts {
        for suf in ["_m", "_l", "_d"] {
            let c = format!("{p}{suf}");
            if names.contains(&c) {
                want.insert(c);
            }
        }
    }
    let models = Xbg7Model::models_named(&bytes, &want, &|| false);
    struct Part {
        base: String,
        vcounts: Vec<u32>,
        pos: Vec<[f32; 3]>, // union of variants
    }
    let mut parts: Vec<Part> = Vec::new();
    for p in &base_parts {
        let mut vcounts = Vec::new();
        let mut pos = Vec::new();
        for cand in [p.clone(), format!("{p}_m"), format!("{p}_l"), format!("{p}_d")] {
            if let Some(m) = models.iter().find(|m| m.name == cand) {
                let mp: Vec<[f32; 3]> =
                    m.meshes.iter().flat_map(|s| s.positions.iter().copied()).collect();
                vcounts.push(mp.len() as u32);
                pos.extend(mp);
            }
        }
        parts.push(Part { base: p.clone(), vcounts, pos });
    }

    // Validate a draw against a part (direct or X-mirrored), like ship_capture.
    let validate = |d: &CapturedDraw, p: &Part| -> Option<bool> {
        if !p.vcounts.contains(&d.vcount) || d.pos.is_empty() {
            return None;
        }
        let near = |a: &[f32; 3], b: &[f32; 3]| {
            (a[0] - b[0]).abs() <= 1e-2 && (a[1] - b[1]).abs() <= 1e-2 && (a[2] - b[2]).abs() <= 1e-2
        };
        let (mut direct, mut mirror) = (0usize, 0usize);
        for q in &d.pos {
            if p.pos.iter().any(|v| near(q, v)) {
                direct += 1;
            }
            let qm = [-q[0], q[1], q[2]];
            if p.pos.iter().any(|v| near(&qm, v)) {
                mirror += 1;
            }
        }
        let need = d.pos.len().div_ceil(2);
        if direct >= need && direct >= mirror {
            Some(false)
        } else if mirror >= need {
            Some(true)
        } else {
            None
        }
    };

    // Collect rel samples per part across all logs.
    let mut samples: std::collections::BTreeMap<String, Vec<[f64; 3]>> = Default::default();
    for log in &a[3..] {
        let text = std::fs::read_to_string(log).unwrap();
        let draws = parse_capture(&text);
        // label validated draws
        let mut labeled: Vec<(usize, usize, bool)> = Vec::new(); // (draw, part, mirrored)
        for (di, d) in draws.iter().enumerate() {
            for (pi, p) in parts.iter().enumerate() {
                if let Some(mir) = validate(d, p) {
                    labeled.push((di, pi, mir));
                    break;
                }
            }
        }
        let refs: Vec<usize> = labeled
            .iter()
            .filter(|(_, pi, _)| parts[*pi].base.ends_with("bdy_04"))
            .map(|(di, ..)| *di)
            .collect();
        for &ri in &refs {
            let rf = &draws[ri];
            let rt = transpose(&rf.r);
            for &(di, pi, _mir) in &labeled {
                let d = &draws[di];
                let dt = [d.t[0] - rf.t[0], d.t[1] - rf.t[1], d.t[2] - rf.t[2]];
                let rel = mv(&rt, dt);
                if rel.iter().map(|v| v * v).sum::<f64>().sqrt() < 2600.0 {
                    // also require the rel rotation be finite-sane
                    let _rm = mmul(&rt, &d.r);
                    samples.entry(parts[pi].base.clone()).or_default().push(rel);
                }
            }
        }
        eprintln!("{log}: {} validated draws, {} bdy_04 refs", labeled.len(), refs.len());
    }

    // Cluster per part (greedy, 8-unit radius), print clusters with ≥3 samples.
    println!("\n== dominant ship-relative placements (clusters ≥3 samples) ==");
    for (part, mut v) in samples {
        v.sort_by(|x, y| x[0].partial_cmp(&y[0]).unwrap());
        let mut clusters: Vec<([f64; 3], usize)> = Vec::new();
        for s in &v {
            match clusters.iter_mut().find(|(c, _)| {
                (c[0] - s[0]).abs() < 8.0 && (c[1] - s[1]).abs() < 8.0 && (c[2] - s[2]).abs() < 8.0
            }) {
                Some((c, n)) => {
                    for k in 0..3 {
                        c[k] = (c[k] * *n as f64 + s[k]) / (*n as f64 + 1.0);
                    }
                    *n += 1;
                }
                None => clusters.push((*s, 1)),
            }
        }
        clusters.sort_by(|x, y| y.1.cmp(&x.1));
        let tops: Vec<String> = clusters
            .iter()
            .filter(|(_, n)| *n >= 3)
            .take(6)
            .map(|(c, n)| format!("[{:7.1} {:7.1} {:7.1}]×{n}", c[0], c[1], c[2]))
            .collect();
        println!("  {part:20} {}", tops.join("  "));
    }

    // Static assembly rel bdy_04 for comparison.
    println!("\n== static assemble_ship rel bdy_04 ==");
    let placed = assemble_ship(&bytes, id, true);
    if let Some(r4) = placed.iter().find(|p| p.resource == format!("{id}_bdy_04")) {
        for p in &placed {
            println!(
                "  {:20} [{:7.1} {:7.1} {:7.1}]",
                p.resource,
                p.t[0] - r4.t[0],
                p.t[1] - r4.t[1],
                p.t[2] - r4.t[2]
            );
        }
    }
}
