//! Invert the capture↔part match: instead of asking, per ship part, "is there a
//! draw with this vertex count?", ask of the **capture's** biggest draws "which
//! decoded resource in this stage container has that vertex count?".
//!
//! This is the diagnostic for the 2026-07-31 negative result (Stage_S02 capture,
//! zero parts correlated). It separates three hypotheses:
//!   1. LOD/variant vcount not covered by the correlator's variant list
//!      → the big draws DO map to named resources, just not to the `_m`/`_l`/`_d`
//!        set the correlator tries;
//!   2. position validation over-rejects
//!      → the vcounts match the very parts we asked for (so the vcount key was
//!        fine and the rejection happened later);
//!   3. a different draw path (instanced/batched/merged buffers)
//!      → the big draws match NO resource in the container at all.
//!
//! Usage:
//!   SYLPHEED_ISO=... cargo run --release --example invert_capture -- \
//!       <capture.log> <Stage_SNN> [top_n] [--all]
//! `--all` lists every capture vcount, not just the `top_n` (default 40) largest.

use sylpheed_formats::mesh::{xbg7_resource_names, Xbg7Model};
use sylpheed_formats::ship_capture::{parse_capture, parse_drawlog};
use sylpheed_formats::xiso::open_iso;
use std::collections::{HashMap, HashSet};
use std::path::Path;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let positional: Vec<&String> = args[1..].iter().filter(|a| !a.starts_with("--")).collect();
    let all = args.iter().any(|a| a == "--all");
    if positional.len() < 2 {
        eprintln!("usage: invert_capture <capture.log> <Stage_SNN> [top_n] [--all]");
        std::process::exit(2);
    }
    let (log, stage) = (positional[0], positional[1]);
    let top_n: usize = positional.get(2).and_then(|s| s.parse().ok()).unwrap_or(40);
    let iso = std::env::var("SYLPHEED_ISO").expect("SYLPHEED_ISO");

    let text = std::fs::read_to_string(log).expect("read log");
    let mut draws = parse_capture(&text);
    if draws.is_empty() {
        draws = parse_drawlog(&text);
        println!("parsed {} draws (draw-logger format)", draws.len());
    } else {
        println!("parsed {} draws (F10 capture format)", draws.len());
    }

    // Decode EVERY geometry resource in the stage container, not just one ship's.
    let bytes = {
        let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
        rt.block_on(async {
            let mut r = open_iso(Path::new(&iso)).await.unwrap();
            r.read_file(&format!("hidden/resource3d/{stage}.xpr")).await.unwrap()
        })
    };
    let names = xbg7_resource_names(&bytes);
    println!("{stage}.xpr: {} XBG7 resources", names.len());
    let want: HashSet<String> = names.iter().cloned().collect();
    let models = Xbg7Model::models_named(&bytes, &want, &|| false);
    println!("decoded {} models", models.len());

    // vcount -> resource names with that many vertices.
    let mut by_vcount: HashMap<u32, Vec<String>> = HashMap::new();
    for m in &models {
        let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
        by_vcount.entry(v as u32).or_default().push(m.name.clone());
    }
    // Per-submesh counts too: a draw may be one sub-mesh of a multi-mesh resource.
    let mut by_sub_vcount: HashMap<u32, Vec<String>> = HashMap::new();
    for m in &models {
        for (i, s) in m.meshes.iter().enumerate() {
            if m.meshes.len() > 1 {
                by_sub_vcount
                    .entry(s.positions.len() as u32)
                    .or_default()
                    .push(format!("{}#{i}", m.name));
            }
        }
    }

    // Capture vcounts, de-duped by (vbase, vcount) so a re-drawn part counts once
    // per distinct buffer.
    let mut draw_count: HashMap<u32, usize> = HashMap::new();
    let mut bufs: HashMap<u32, HashSet<u32>> = HashMap::new();
    for d in &draws {
        *draw_count.entry(d.vcount).or_default() += 1;
        bufs.entry(d.vcount).or_default().insert(d.vbase);
    }
    let mut vcounts: Vec<u32> = draw_count.keys().copied().collect();
    vcounts.sort_unstable_by(|a, b| b.cmp(a));

    let matched_draws: usize = draws
        .iter()
        .filter(|d| by_vcount.contains_key(&d.vcount) || by_sub_vcount.contains_key(&d.vcount))
        .count();
    println!(
        "\n{} distinct vcounts; {}/{} draws have a vcount present in {stage}.xpr ({:.1}%)",
        vcounts.len(),
        matched_draws,
        draws.len(),
        100.0 * matched_draws as f64 / draws.len().max(1) as f64
    );

    let shown = if all { vcounts.len() } else { top_n.min(vcounts.len()) };
    println!("\nlargest capture vcounts (draws / distinct vbufs) → matching resources:");
    for &v in vcounts.iter().take(shown) {
        let n = draw_count[&v];
        let b = bufs[&v].len();
        let mut hit: Vec<String> = by_vcount.get(&v).cloned().unwrap_or_default();
        let sub: Vec<String> = by_sub_vcount.get(&v).cloned().unwrap_or_default();
        hit.extend(sub.into_iter().map(|s| format!("{s} (sub)")));
        let label = if hit.is_empty() {
            "— no resource".to_string()
        } else {
            let mut h = hit.clone();
            h.sort();
            h.truncate(6);
            format!("{}{}", h.join(", "), if hit.len() > 6 { ", …" } else { "" })
        };
        println!("  vcount {v:6}  draws {n:4}  bufs {b:3}   {label}");
    }

    // `--ship <id>`: every resource of one ship family, with its vertex count and
    // whether the capture drew it — this is what shows an all-`_l` (far-LOD) frame.
    if let Some(i) = args.iter().position(|a| a == "--ship") {
        if let Some(id) = args.get(i + 1) {
            let mut rows: Vec<(String, u32, usize)> = models
                .iter()
                .filter(|m| m.name.contains(id.as_str()))
                .map(|m| {
                    let v = m.meshes.iter().map(|s| s.positions.len()).sum::<usize>() as u32;
                    (m.name.clone(), v, draw_count.get(&v).copied().unwrap_or(0))
                })
                .collect();
            rows.sort_by(|a, b| a.0.cmp(&b.0));
            let drawn = rows.iter().filter(|r| r.2 > 0).count();
            println!("\n{id} resources in {stage}.xpr ({drawn}/{} with a drawn vcount):", rows.len());
            for (name, v, n) in rows {
                println!("  {name:28} vcount {v:6}  {}", if n > 0 { format!("DRAWN ×{n}") } else { "—".into() });
            }
        }
    }

    // The other direction, for orientation: the container's biggest resources and
    // whether the capture ever drew that many vertices.
    let mut sizes: Vec<(u32, String)> = models
        .iter()
        .map(|m| (m.meshes.iter().map(|s| s.positions.len()).sum::<usize>() as u32, m.name.clone()))
        .collect();
    sizes.sort_unstable_by(|a, b| b.0.cmp(&a.0));
    println!("\nlargest resources in {stage}.xpr → drawn in the capture?");
    for (v, name) in sizes.iter().take(top_n.min(sizes.len())) {
        let n = draw_count.get(v).copied().unwrap_or(0);
        println!("  {name:28} vcount {v:6}  {}", if n > 0 { format!("DRAWN ×{n}") } else { "not drawn".into() });
    }
}
