//! RE diagnostic: raw-dump a composite scene graph's node records — name, table
//! pointers, and the FULL joint-table slot values (not just the 8 the decoder
//! currently reads) — to derive the true TRS slot layout against captured
//! ground truth.
//!   cargo run --release --example node_dump -- <Stage.xpr path> <composite name> [slots]
use sylpheed_formats::mesh::xbg7_descriptor_range;

fn be32(d: &[u8], o: usize) -> u32 {
    if o + 4 > d.len() {
        return 0;
    }
    u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
}
fn be_f64(d: &[u8], o: usize) -> f64 {
    if o + 8 > d.len() {
        return f64::NAN;
    }
    f64::from_be_bytes(d[o..o + 8].try_into().unwrap())
}

fn main() {
    let a: Vec<String> = std::env::args().collect();
    let bytes = std::fs::read(&a[1]).unwrap();
    let comp = &a[2];
    let nslots: usize = a.get(3).and_then(|s| s.parse().ok()).unwrap_or(12);
    let (desc, desc_end) = xbg7_descriptor_range(&bytes, comp).expect("composite found");
    let d = &bytes[desc..desc_end];
    println!("descriptor [{desc:#x}..{desc_end:#x}) len {:#x}", d.len());

    let mut i = 0usize;
    while i + 3 <= d.len() {
        let starts = (i + 4 <= d.len() && &d[i..i + 4] == b"rou_")
            || (i + 3 <= d.len() && &d[i..i + 3] == b"GN_");
        if !starts {
            i += 1;
            continue;
        }
        let mut j = i;
        while j < d.len() && d[j] != 0 && d[j].is_ascii_graphic() {
            j += 1;
        }
        let name = std::str::from_utf8(&d[i..j]).unwrap_or("?").to_string();
        if name.ends_with("_col") || name.ends_with("_spc") || name.ends_with("_gls") || name.ends_with("_lum") {
            i = j.max(i + 1);
            continue;
        }
        // find the 0xFFFFFFFF end marker
        let mut ff = i;
        let scan_end = (i + 0x90).min(d.len().saturating_sub(4));
        while ff < scan_end && be32(d, ff) != 0xFFFF_FFFF {
            ff += 4;
        }
        if ff >= scan_end || ff < 0x10 {
            i = j.max(i + 1);
            continue;
        }
        let t44 = be32(d, ff - 0x10) as usize;
        let t48 = be32(d, ff - 0x0C) as usize;
        let child = be32(d, ff - 0x08) as usize;
        let sib = be32(d, ff - 0x04) as usize;
        // Dump the record's u32s between name end and marker for structure context.
        let rec_u32: Vec<String> = ((j + 1)..ff)
            .step_by(4)
            .map(|o| format!("{:08X}", be32(d, o)))
            .collect();
        println!("\nNODE {name} @{i:#x}  ff@{ff:#x} t44={t44:#x} t48={t48:#x} child={child:#x} sib={sib:#x}");
        println!("  rec: {}", rec_u32.join(" "));
        if t44 != 0 && t44 < d.len() {
            // Joint table: nslots pointer slots. Each slot record appears to be a
            // keyframe track: a value ALSO sits 0x48 BEFORE the pointed address
            // (the rest-pose key A), while the decoder currently reads ptr+8 (B).
            for k in 0..nslots {
                let p = be32(d, t44 + k * 4) as usize;
                if p == 0 || p + 24 > d.len() {
                    continue;
                }
                let a = if p >= 0x48 { be_f64(d, p - 0x48) } else { f64::NAN };
                let b = be_f64(d, p + 8);
                let head = if p >= 0x50 { be32(d, p - 0x50) } else { 0 };
                let t_a = if p >= 0x50 { be_f64(d, p - 0x50) } else { f64::NAN };
                let t_b = be_f64(d, p);
                println!(
                    "    slot{k:2} @{p:#x} head={head:08X}: A={a:12.5} (t={t_a:8.3})  B={b:12.5} (t={t_b:8.3})"
                );
            }
        }
        i = j.max(i + 1);
    }
}
