mod common;

use sqpack_rs::phyb::{PHYB_VERSION_FULL, Phyb, PhybVersion};
use sqpack_rs::{DatFile, Index, SqPackPayload};

fn looks_like_phyb(bytes: &[u8]) -> bool {
    if bytes.len() < 4 {
        return false;
    }
    let v = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
    v == 0 || v == PHYB_VERSION_FULL
}

fn assert_phyb_structurally_valid(phyb: &Phyb<'_>, entry_hash: u64) {
    assert!(
        matches!(phyb.version(), PhybVersion::V0 | PhybVersion::V1),
        "PHYB version must be V0 or V1, got {:?}",
        phyb.version(),
    );

    let collision = phyb.collision();
    let simulator = phyb.simulator();

    let total_collision = collision.total_primitive_count();
    assert!(
        total_collision <= 4096,
        "implausible collision-primitive total {total_collision} for hash {entry_hash:#018x}",
    );
    assert!(
        simulator.simulators.len() <= 4096,
        "implausible simulator count {} for hash {entry_hash:#018x}",
        simulator.simulators.len(),
    );

    for sim in &simulator.simulators {
        for chain in &sim.chains {
            if chain.nodes.is_empty() {
                continue;
            }
            assert!(
                chain.nodes.iter().any(|n| !n.bone_name.is_empty()),
                "chain in hash {entry_hash:#018x} has nodes but no bone names",
            );
        }
        for pin in &sim.pins {
            assert!(
                !pin.bone_name.is_empty(),
                "pin in hash {entry_hash:#018x} has empty bone_name",
            );
        }
        for att in &sim.attractors {
            assert!(
                !att.bone_name.is_empty(),
                "attractor in hash {entry_hash:#018x} has empty bone_name",
            );
        }
    }
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn scanned_phyb_files_parse_and_expose_typed_sections() {
    let Some(data_dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };

    let dat_path = data_dir.join("040000.win32.dat0");
    let idx_path = data_dir.join("040000.win32.index");

    if !dat_path.exists() || !idx_path.exists() {
        eprintln!("skipping: 040000.win32.dat0 or .index not found");
        return;
    }

    let idx = Index::open(&idx_path).expect("Index::open");
    let mut dat = DatFile::open(&dat_path).expect("DatFile::open");

    let mut checked = 0usize;
    let target = 3usize;
    let mut phyb_count = 0usize;

    'outer: for entry in idx
        .entries()
        .iter()
        .filter(|e| !e.is_synonym() && e.data_file_id() == 0)
        .take(50_000)
    {
        let Ok(SqPackPayload::Standard(payload)) = dat.read_file_typed(entry.byte_offset()) else {
            continue;
        };

        if !looks_like_phyb(&payload) {
            continue;
        }

        let Ok(phyb) = Phyb::parse(&payload) else {
            continue;
        };
        phyb_count += 1;

        assert_phyb_structurally_valid(&phyb, entry.hash);

        let collision = phyb.collision();
        let simulator = phyb.simulator();
        checked += 1;
        eprintln!(
            "  phyb {checked}: hash={:#018x} version={:?} data_type={:#010x} \
             collision_offset={:#x} simulator_offset={:#x} \
             capsules={} ellipsoids={} normal_planes={} three_point_planes={} spheres={} \
             simulators={}",
            entry.hash,
            phyb.version(),
            phyb.data_type(),
            phyb.collision_offset(),
            phyb.simulator_offset(),
            collision.capsules.len(),
            collision.ellipsoids.len(),
            collision.normal_planes.len(),
            collision.three_point_planes.len(),
            collision.spheres.len(),
            simulator.simulators.len(),
        );
        if checked >= target {
            break 'outer;
        }
    }

    eprintln!(
        "scanned_phyb: found {phyb_count} phyb-version-magic entry/entries in first 50000 entries of \
         040000.win32 ({checked} structurally validated)"
    );
}
