use crate::inspect::smp::regions::*;
use crate::inspect::smp::*;

pub(in crate::inspect::smp) fn parse_save_company_collection_header_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
    parse_save_tagged_collection_header_probe(
        bytes,
        file_extension_hint,
        container_profile,
        0x000061a9,
        0x000061aa,
        0x000061ab,
        "save-company-tagged-header-counts",
        "scenario-save-company-header-counts",
        |header| {
            header.direct_collection_flag == 1
                && header.live_id_bound >= 1
                && header.live_id_bound <= 0x20
                && header.live_record_count <= header.live_id_bound
                && header.direct_record_stride >= 0x1000
        },
        vec![
            "save-side company collection uses tagged header family 0x61a9/0x61aa/0x61ab".to_string(),
            "package-save per-company callback is currently grounded as a no-op stub, so this probe only claims header-level collection counts, not per-company payload".to_string(),
        ],
    )
}

pub(in crate::inspect::smp) fn parse_save_chairman_profile_collection_header_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
    parse_save_tagged_collection_header_probe(
        bytes,
        file_extension_hint,
        container_profile,
        0x00005209,
        0x0000520a,
        0x0000520b,
        "save-chairman-profile-tagged-header-counts",
        "scenario-save-chairman-profile-header-counts",
        |header| {
            header.direct_collection_flag == 1
                && header.live_id_bound >= 1
                && header.live_id_bound <= 0x20
                && header.live_record_count <= header.live_id_bound
                && header.direct_record_stride >= 0x800
                && header.direct_record_stride <= 0x2000
        },
        vec![
            "save-side chairman/profile collection uses tagged header family 0x5209/0x520a/0x520b".to_string(),
            "the direct-record chairman/profile family is the large-stride tagged collection with embedded name and biography payload, not the smaller train-side 0x5209 family".to_string(),
        ],
    )
}

pub(in crate::inspect::smp) fn parse_save_train_collection_header_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
    parse_save_tagged_collection_header_probe(
        bytes,
        file_extension_hint,
        container_profile,
        0x00005209,
        0x0000520a,
        0x0000520b,
        "save-train-tagged-header-counts",
        "scenario-save-train-header-counts",
        |header| {
            header.direct_collection_flag == 1
                && header.direct_record_stride >= 0x100
                && header.direct_record_stride <= 0x400
                && header.live_id_bound >= 0x10
                && header.live_id_bound <= 0x100
                && header.live_record_count >= 1
                && header.live_record_count <= header.live_id_bound
        },
        vec![
            "save-side live train collection shares tagged header family 0x5209/0x520a/0x520b with other indexed direct-record bundles".to_string(),
            "the grounded train-side candidate is the smaller direct-record family with stride 0x1d5 whose metadata payload carries Train N labels, distinct from the larger chairman/profile family and the non-direct region family".to_string(),
        ],
    )
}

pub(in crate::inspect::smp) fn parse_save_train_collection_directory_probe(
    bytes: &[u8],
    header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpSaveTrainCollectionDirectoryProbe> {
    let header_probe = header_probe?;
    if header_probe.source_kind != "save-train-tagged-header-counts" {
        return None;
    }
    let metadata_payload =
        bytes.get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)?;
    let directory_root_byte_offset =
        SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
    let live_record_count = header_probe.live_record_count as usize;
    let directory_len_dwords =
        live_record_count.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT)?;
    let directory_len_bytes = directory_len_dwords.checked_mul(4)?;
    let directory_bytes = metadata_payload.get(
        directory_root_byte_offset..directory_root_byte_offset.checked_add(directory_len_bytes)?,
    )?;
    let mut entries = Vec::with_capacity(live_record_count);
    for index in 0..live_record_count {
        let entry_offset =
            index.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT * 4)?;
        let payload_relative_offset = read_u32_at(directory_bytes, entry_offset)?;
        let previous_live_entry_id = read_u32_at(directory_bytes, entry_offset + 4)?;
        let next_live_entry_id = read_u32_at(directory_bytes, entry_offset + 8)?;
        entries.push(SmpSaveTrainCollectionDirectoryEntryProbe {
            live_entry_id: (index + 1) as u32,
            payload_relative_offset,
            payload_relative_offset_hex: format!("0x{payload_relative_offset:08x}"),
            payload_absolute_offset: header_probe
                .metadata_tag_offset
                .checked_add(4)?
                .checked_add(payload_relative_offset as usize)?,
            previous_live_entry_id,
            previous_live_entry_id_hex: format!("0x{previous_live_entry_id:08x}"),
            next_live_entry_id,
            next_live_entry_id_hex: format!("0x{next_live_entry_id:08x}"),
        });
    }
    let chain_head_live_entry_id = entries
        .iter()
        .find(|entry| entry.previous_live_entry_id == 0)
        .map(|entry| entry.live_entry_id);
    let chain_tail_live_entry_id = entries
        .iter()
        .find(|entry| entry.next_live_entry_id == 0)
        .map(|entry| entry.live_entry_id);
    let monotonic_offsets = entries
        .windows(2)
        .all(|window| window[0].payload_relative_offset < window[1].payload_relative_offset);
    Some(SmpSaveTrainCollectionDirectoryProbe {
        profile_family: header_probe.profile_family.clone(),
        source_kind: "save-train-live-directory".to_string(),
        semantic_family: "scenario-save-train-live-directory".to_string(),
        metadata_tag_offset: header_probe.metadata_tag_offset,
        records_tag_offset: header_probe.records_tag_offset,
        close_tag_offset: header_probe.close_tag_offset,
        directory_root_dword_index: SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX,
        directory_entry_dword_count: SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT,
        live_record_count: header_probe.live_record_count,
        live_id_bound: header_probe.live_id_bound,
        chain_head_live_entry_id,
        chain_tail_live_entry_id,
        entries,
        evidence: vec![
            "save-side train metadata payload exposes a live-entry directory immediately after the first 16 dwords, with payload-relative offsets pointing into the later records span".to_string(),
            format!(
                "train live directory decodes {} triplets of (payload_relative_offset, prev_live_entry_id, next_live_entry_id) from metadata dword index {}",
                header_probe.live_record_count,
                SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX
            ),
            format!(
                "decoded directory preserves a head/tail chain {:?}->{:?} and monotonic payload offsets={monotonic_offsets}",
                chain_head_live_entry_id, chain_tail_live_entry_id
            ),
        ],
    })
}

pub(in crate::inspect::smp) fn parse_save_region_collection_header_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
    let probe = parse_save_tagged_collection_header_probe(
        bytes,
        file_extension_hint,
        container_profile,
        0x00005209,
        0x0000520a,
        0x0000520b,
        "save-region-tagged-header-counts",
        "scenario-save-region-header-counts",
        |header| {
            header.direct_collection_flag == 0
                && header.direct_record_stride == 0x06
                && header.live_id_bound >= 0x80
                && header.live_id_bound <= 0x200
                && header.live_record_count >= 0x80
                && header.live_record_count <= header.live_id_bound
        },
        vec![
            "save-side live region collection shares tagged header family 0x5209/0x520a/0x520b with trains and chairman profiles, but uses the larger non-direct indexed family".to_string(),
            "the grounded region-side candidate is the non-direct 0x5209 family with live_id_bound/count in the 0x96/0x91 range and Marker09-style default stems in the records span, distinct from the smaller direct train family".to_string(),
        ],
    )?;
    let records_preview = bytes
        .get(probe.records_tag_offset + 4..probe.close_tag_offset)
        .unwrap_or(&[]);
    records_preview
        .windows("Marker09".len())
        .any(|window| window == b"Marker09")
        .then_some(probe)
}
