mod common;

use sqpack_rs::tmb::{TMB_MAGIC, Tmb, TmbChunk, TmbEntry};
use sqpack_rs::{DatFile, Index, SqPackPayload};

fn looks_like_tmb(bytes: &[u8]) -> bool {
    bytes.len() >= 4 && bytes[..4] == TMB_MAGIC
}

fn assert_tmb_structurally_valid(tmb: &Tmb<'_>, entry_hash: u64) {
    let chunks = tmb.chunks();
    assert!(
        !chunks.is_empty(),
        "TMB at hash {entry_hash:#018x} parsed with zero chunks; \
         the format requires at least TMDH + TMAL",
    );

    let mut tmdh_count = 0usize;
    let mut tmal_count = 0usize;
    let mut typed_entries = 0usize;
    let mut opaque_chunks = 0usize;
    let mut opaque_entries = 0usize;

    for (idx, chunk) in chunks.iter().enumerate() {
        match chunk {
            TmbChunk::Tmdh(_) => tmdh_count += 1,
            TmbChunk::Tmal(_) => tmal_count += 1,
            TmbChunk::Entry(entry) => match entry {
                TmbEntry::Opaque { tag, .. } => {
                    assert_eq!(
                        tag[0], b'C',
                        "TmbEntry::Opaque at chunk {idx} in hash {entry_hash:#018x} \
                         carries non-C tag {tag:?}",
                    );
                    opaque_entries += 1;
                }
                _ => typed_entries += 1,
            },
            TmbChunk::Opaque { tag, .. } => {
                assert!(
                    !matches!(tag, b"TMDH" | b"TMPP" | b"TMAL" | b"TMAC" | b"TMTR"),
                    "Opaque chunk at idx {idx} in hash {entry_hash:#018x} carries typed tag {tag:?}",
                );
                assert!(
                    tag[0] != b'C',
                    "Opaque chunk at idx {idx} in hash {entry_hash:#018x} carries C0xx tag {tag:?} \
                     — should route through Entry",
                );
                opaque_chunks += 1;
            }
            _ => {}
        }
    }

    assert_eq!(
        tmdh_count, 1,
        "expected exactly one TMDH chunk in hash {entry_hash:#018x}, got {tmdh_count}",
    );
    assert_eq!(
        tmal_count, 1,
        "expected exactly one TMAL chunk in hash {entry_hash:#018x}, got {tmal_count}",
    );

    let _ = (typed_entries, opaque_chunks, opaque_entries);
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn scanned_tmb_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 tmb_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_tmb(&payload) {
            continue;
        }

        let Ok(tmb) = Tmb::parse(&payload) else {
            continue;
        };
        tmb_count += 1;

        assert_tmb_structurally_valid(&tmb, entry.hash);

        checked += 1;
        eprintln!(
            "  tmb {checked}: hash={:#018x} file_size={} chunk_count={} chunks_parsed={}",
            entry.hash,
            tmb.file_size(),
            tmb.chunk_count(),
            tmb.chunks().len(),
        );
        if checked >= target {
            break 'outer;
        }
    }

    eprintln!(
        "scanned_tmb: found {tmb_count} tmb-magic entry/entries in first 50000 entries of \
         040000.win32 ({checked} structurally validated)"
    );
}
