//! Does every part of a ship sit inside the envelope its siblings describe?
//!
//! `slab_screen` compared *scale* and could not see the `e106` slab. What the eye
//! used when the render exposed it was **relationship**: a blocky mass sitting
//! apart from the hull. This measures that — assemble a ship, and for each part
//! ask how far its world box protrudes beyond the box of all the OTHER parts,
//! relative to the ship's own size. A mis-anchored block sticks out; a real part,
//! however big, is part of the silhouette.
//!
//! Usage: envelope_screen <resource3d_dir> [protrusion_fraction]
use sylpheed_formats::mesh::Xbg7Model;
use sylpheed_formats::ship::{assemble_ship, ship_id_of};
use std::collections::{BTreeSet, HashSet};

fn main() {
    let dir = std::env::args().nth(1).expect("resource3d dir");
    let limit: f32 = std::env::args().nth(2).and_then(|s| s.parse().ok()).unwrap_or(0.35);
    let mut files: Vec<_> = std::fs::read_dir(&dir)
        .unwrap()
        .flatten()
        .map(|e| e.path())
        .filter(|p| p.extension().and_then(|s| s.to_str()) == Some("xpr"))
        .collect();
    files.sort();

    let mut flagged = 0usize;
    for f in &files {
        let Ok(bytes) = std::fs::read(f) else { continue };
        let ids: BTreeSet<String> = Xbg7Model::anchor_models_cancellable(&bytes, 0.0, &|| false)
            .iter()
            .filter_map(|m| ship_id_of(&m.name).map(|s| s.to_string()))
            .collect();
        for id in &ids {
            let placed = assemble_ship(&bytes, id, true);
            if placed.len() < 3 {
                continue;
            }
            let want: HashSet<String> = placed.iter().map(|p| p.resource.clone()).collect();
            let models = Xbg7Model::models_named(&bytes, &want, &|| false);
            // World box per placement.
            let mut boxes: Vec<(String, [f32; 3], [f32; 3])> = Vec::new();
            for p in &placed {
                let Some(m) = models.iter().find(|m| m.name == p.resource) else { continue };
                let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
                for s in &m.meshes {
                    for q in &s.positions {
                        let w = p.apply(*q);
                        for k in 0..3 {
                            lo[k] = lo[k].min(w[k]);
                            hi[k] = hi[k].max(w[k]);
                        }
                    }
                }
                if lo[0] != f32::MAX {
                    boxes.push((p.resource.clone(), lo, hi));
                }
            }
            if boxes.len() < 3 {
                continue;
            }
            for i in 0..boxes.len() {
                // Envelope of every OTHER part.
                let (mut lo, mut hi) = ([f32::MAX; 3], [f32::MIN; 3]);
                for (j, b) in boxes.iter().enumerate() {
                    if i == j {
                        continue;
                    }
                    for k in 0..3 {
                        lo[k] = lo[k].min(b.1[k]);
                        hi[k] = hi[k].max(b.2[k]);
                    }
                }
                // Per-AXIS: a bow legitimately extends the ship along its long
                // axis, so protrusion only means something measured against the
                // envelope's size IN THAT AXIS. The e106 slab stuck ~1 500 out in
                // Y where its siblings spanned ~800.
                let mut worst = 0.0f32;
                let mut worst_out = 0.0f32;
                for k in 0..3 {
                    let size_k = hi[k] - lo[k];
                    if size_k <= 1.0 {
                        continue;
                    }
                    let out_k = (lo[k] - boxes[i].1[k]).max(boxes[i].2[k] - hi[k]).max(0.0);
                    if out_k / size_k > worst {
                        worst = out_k / size_k;
                        worst_out = out_k;
                    }
                }
                let (out, size) = (worst_out, 1.0f32);
                let _ = size;
                if worst > limit {
                    flagged += 1;
                    println!(
                        "{:<20} {:<22} protrudes {:>7.0} beyond its siblings ({:.0}% of the ship)",
                        f.file_name().unwrap().to_string_lossy(),
                        boxes[i].0,
                        out,
                        100.0 * worst
                    );
                }
            }
        }
    }
    println!("{flagged} parts protrude more than {:.0}% of their ship's size", 100.0 * limit);
}
