//! Cross-container consistency for XBG7 geometry.
//!
//! A geometry resource shared by several stage containers must decode to the
//! same bounds in each. This needs no ground truth, and on 2026-08-11 it found
//! **125 of 681** shared resources decoding to different bounds while reporting
//! identical vertex and triangle counts — the anchor scan locating a different
//! buffer of the same size (see `docs/re/structures/xbg7-mesh.md`).
//!
//! The test is `#[ignore]`d because the decoder does not satisfy it yet. It is
//! written as the *target* state so that fixing the anchor scan makes it pass,
//! rather than as a snapshot of the bug.

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

use sylpheed_formats::mesh::Xbg7Model;

fn disc_root() -> Option<PathBuf> {
    if let Ok(p) = std::env::var("SYLPHEED_DISC") {
        let p = PathBuf::from(p);
        if p.join("dat").is_dir() {
            return Some(p);
        }
    }
    let default = Path::new(
        "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja)",
    );
    if default.join("dat").is_dir() {
        return Some(default.to_path_buf());
    }
    None
}

/// Rounded (w, h, d) of a model's own geometry.
fn span(m: &Xbg7Model) -> Option<[i64; 3]> {
    let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
    for s in &m.meshes {
        for q in &s.positions {
            for k in 0..3 {
                lo[k] = lo[k].min(q[k]);
                hi[k] = hi[k].max(q[k]);
            }
        }
    }
    if lo[0] == f32::MAX {
        return None;
    }
    Some([
        (hi[0] - lo[0]).round() as i64,
        (hi[1] - lo[1]).round() as i64,
        (hi[2] - lo[2]).round() as i64,
    ])
}

#[test]
#[ignore = "known-failing: 51 of 681 shared resources still decode inconsistently (125 before the neighbourhood anchor, 63 before refining it — 2026-08-12)"]
fn shared_resources_decode_identically_in_every_container() {
    let Some(root) = disc_root() else {
        eprintln!("SKIP: extracted disc not found (set SYLPHEED_DISC to enable)");
        return;
    };
    let dir = root.join("hidden/resource3d");
    let mut files: Vec<PathBuf> = std::fs::read_dir(&dir)
        .expect("resource3d/")
        .flatten()
        .map(|e| e.path())
        .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
        .collect();
    files.sort();

    // name -> (verts, tris) -> set of spans seen
    let mut seen: BTreeMap<String, Vec<([i64; 3], usize, usize, String)>> = BTreeMap::new();
    for f in &files {
        let Ok(bytes) = std::fs::read(f) else { continue };
        let where_ = f.file_name().unwrap().to_string_lossy().to_string();
        for m in Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false) {
            let Some(sp) = span(&m) else { continue };
            let v: usize = m.meshes.iter().map(|s| s.positions.len()).sum();
            let t: usize = m.meshes.iter().map(|s| s.indices.len() / 3).sum();
            seen.entry(m.name.clone()).or_default().push((sp, v, t, where_.clone()));
        }
    }

    let mut bad: Vec<String> = Vec::new();
    for (name, list) in &seen {
        if list.len() < 2 {
            continue;
        }
        // Only compare decodes that agree on how much geometry they found;
        // a differing vertex/triangle count is a different question.
        if !list.iter().all(|e| e.1 == list[0].1 && e.2 == list[0].2) {
            continue;
        }
        let spans: std::collections::BTreeSet<[i64; 3]> = list.iter().map(|e| e.0).collect();
        if spans.len() > 1 && bad.len() < 10 {
            bad.push(format!("{name}: {spans:?}"));
        }
    }
    assert!(
        bad.is_empty(),
        "{} shared resources decode to different bounds; first: {bad:?}",
        bad.len()
    );
}
