mod common;

use sqpack_rs::pap::{PAP_VERSION_V1, Pap};
use sqpack_rs::{DatFile, Index, SqPackPayload};

const HAVOK_MAGIC_PATTERNS: [[u8; 4]; 4] = [
    0xCAB0_0D1Eu32.to_le_bytes(),
    0xCAB0_0D1Eu32.to_be_bytes(),
    0xD011_FACEu32.to_le_bytes(),
    0xD011_FACEu32.to_be_bytes(),
];

fn looks_like_pap(bytes: &[u8]) -> bool {
    bytes.len() >= 4 && &bytes[0..4] == b"pap "
}

fn looks_like_havok_tagfile(bytes: &[u8]) -> bool {
    if bytes.len() < 4 {
        return false;
    }
    HAVOK_MAGIC_PATTERNS
        .iter()
        .any(|pat| &bytes[0..4] == pat.as_slice())
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn scanned_pap_files_parse_and_expose_havok_and_tmb_payloads() {
    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 pap_count = 0usize;

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

        if !looks_like_pap(&payload) {
            continue;
        }
        pap_count += 1;

        let pap = match Pap::parse(&payload) {
            Ok(p) => p,
            Err(e) => panic!("Pap::parse failed for hash {:#018x}: {e}", entry.hash),
        };

        assert_eq!(pap.version(), PAP_VERSION_V1, "PAP version must be V1");
        assert_eq!(
            pap.animations().len(),
            pap.animation_count() as usize,
            "animations().len() must equal animation_count"
        );

        let havok = pap.havok_bytes();
        if havok.is_empty() || !looks_like_havok_tagfile(havok) {
            eprintln!(
                "  pap skip: hash={:#018x} havok_len={} magic={:02X?} (not a known Havok tagfile)",
                entry.hash,
                havok.len(),
                &havok[..havok.len().min(4)],
            );
            continue;
        }

        let tmb = pap.tmb_footer_bytes();
        if !tmb.is_empty() {
            assert!(
                tmb.len() >= 4 && &tmb[..4] == b"TMLB",
                "non-empty tmb_footer_bytes for hash {:#018x} must start with b\"TMLB\", \
                 got {:02X?}",
                entry.hash,
                &tmb[..tmb.len().min(4)],
            );
        }

        checked += 1;
        eprintln!(
            "  pap {checked}: hash={:#018x} version={:#010x} animations={} \
             info_offset={:#x} havok_offset={:#x} havok_len={} \
             tmb_offset={:#x} tmb_len={}",
            entry.hash,
            pap.version(),
            pap.animation_count(),
            pap.info_offset(),
            pap.havok_offset(),
            havok.len(),
            pap.tmb_offset(),
            tmb.len(),
        );
        if checked >= target {
            break 'outer;
        }
    }

    assert!(
        checked > 0,
        "expected at least one PAP with a valid Havok tagfile header in the first \
         20000 entries of 040000.win32; found {pap_count} PAP(s) but none with \
         a recognised Havok magic",
    );
    eprintln!(
        "scanned_pap: found {pap_count} pap-magic entry/entries in first 20000 entries of \
         040000.win32 ({checked} structurally validated)"
    );
}
