use crate::inspect::smp::profiles::*;

pub(in crate::inspect::smp) fn parse_rt3_105_save_name_table_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
    bridge_payload_probe: Option<&SmpRt3105SaveBridgePayloadProbe>,
) -> Option<SmpRt3105SaveNameTableProbe> {
    let (
        profile_family,
        source_kind,
        header_offset,
        entries_offset,
        block_end_offset,
        mut evidence,
    ) = if let Some(payload) = bridge_payload_probe {
        (
            payload.profile_family.clone(),
            "save-bridge-secondary-block".to_string(),
            payload.secondary_block_offset + 0x354,
            payload.secondary_block_offset + 0x3b5,
            payload.secondary_block_end_offset,
            vec![
                "common-save bridge payload branch".to_string(),
                format!(
                    "secondary block span 0x{:x}..0x{:x}",
                    payload.secondary_block_offset, payload.secondary_block_end_offset
                ),
            ],
        )
    } else {
        let profile_family = container_profile
            .map(|profile| profile.profile_family.clone())
            .unwrap_or_else(|| "unknown".to_string());
        let extension = file_extension_hint.unwrap_or("");
        let source_kind = match extension {
            "gmp" => "map-fixed-catalog-range",
            "gms" => "save-fixed-catalog-range",
            "gmx" => "sandbox-fixed-catalog-range",
            _ => "fixed-catalog-range",
        }
        .to_string();
        (
            profile_family,
            source_kind,
            0x6a70,
            0x6ad1,
            0x73c0,
            vec![
                "fixed catalog range branch".to_string(),
                "using observed shared 1.05 candidate-availability table offsets".to_string(),
            ],
        )
    };
    let entry_stride = 0x22usize;
    if block_end_offset > bytes.len() {
        return None;
    }
    if !matches_candidate_availability_table_header(bytes, header_offset) {
        return None;
    }
    let observed_entry_capacity = read_u32_at(bytes, header_offset + 0x1c)? as usize;
    let observed_entry_count = read_u32_at(bytes, header_offset + 0x20)? as usize;
    let entries_len = observed_entry_count.checked_mul(entry_stride)?;
    let entries_end_offset = entries_offset.checked_add(entries_len)?;
    if observed_entry_count == 0 || observed_entry_capacity < observed_entry_count {
        return None;
    }
    if entries_end_offset > block_end_offset || entries_end_offset > bytes.len() {
        return None;
    }

    let mut entries = Vec::with_capacity(observed_entry_count);
    for index in 0..observed_entry_count {
        let offset = entries_offset + index * entry_stride;
        let chunk = &bytes[offset..offset + entry_stride];
        let nul_index = chunk
            .iter()
            .position(|byte| *byte == 0)
            .unwrap_or(entry_stride);
        let text = std::str::from_utf8(&chunk[..nul_index]).ok()?.to_string();
        let trailer_word = read_u32_at(bytes, offset + entry_stride - 4)?;
        entries.push(SmpRt3105SaveNameTableEntry {
            index,
            offset,
            text,
            availability_dword: trailer_word,
            availability_dword_hex: format!("0x{trailer_word:08x}"),
            trailer_word,
            trailer_word_hex: format!("0x{trailer_word:08x}"),
        });
    }

    let zero_trailer_entry_names = entries
        .iter()
        .filter(|entry| entry.trailer_word == 0)
        .map(|entry| entry.text.clone())
        .collect::<Vec<_>>();
    let zero_trailer_entry_count = zero_trailer_entry_names.len();
    let nonzero_trailer_entry_count = entries.len().saturating_sub(zero_trailer_entry_count);
    let mut distinct_trailer_words = entries
        .iter()
        .map(|entry| entry.trailer_word)
        .collect::<Vec<_>>();
    distinct_trailer_words.sort_unstable();
    distinct_trailer_words.dedup();
    let distinct_trailer_hex_words = distinct_trailer_words
        .iter()
        .map(|word| format!("0x{word:08x}"))
        .collect::<Vec<_>>();
    let trailing_footer_hex = hex_encode(&bytes[entries_end_offset..block_end_offset]);
    let footer = &bytes[entries_end_offset..block_end_offset];
    if footer.len() != 9 {
        return None;
    }
    let footer_progress_word_0 = u32::from_le_bytes([footer[0], footer[1], footer[2], footer[3]]);
    let footer_progress_word_1 = u32::from_le_bytes([footer[4], footer[5], footer[6], footer[7]]);
    let footer_trailing_byte = footer[8];
    let mut footer_grounded_alignments = Vec::new();
    for value in [footer_progress_word_0, footer_progress_word_1] {
        if let Some(alignment) = classify_name_table_footer_progress_alignment(value) {
            footer_grounded_alignments.push(alignment.to_string());
        }
    }
    evidence.extend([
        format!("header offset 0x{header_offset:08x}"),
        format!("entries offset 0x{entries_offset:08x}"),
        format!("entry stride 0x{entry_stride:x}"),
        format!("observed entry capacity {}", observed_entry_capacity),
        format!("observed entry count {}", observed_entry_count),
        format!("zero-trailer entries {}", zero_trailer_entry_count),
        format!(
            "trailing footer {} bytes after last entry",
            block_end_offset - entries_end_offset
        ),
    ]);
    let semantic_alignment = vec![
        "Matches the grounded scenario-side named candidate-availability table shape under 0x00437743.".to_string(),
        "Entry layout matches 0x00434ea0/0x00434f20: name slot at +0x00..+0x1d and availability dword at +0x1e.".to_string(),
        "The shared map/save range suggests this catalog is bundled in source map content and later mirrored into scenario state [state+0x66b2].".to_string(),
    ];

    Some(SmpRt3105SaveNameTableProbe {
        profile_family,
        source_kind,
        semantic_family: "scenario-named-candidate-availability-table".to_string(),
        semantic_alignment,
        header_offset,
        header_word_0: read_u32_at(bytes, header_offset)?,
        header_word_0_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset)?),
        header_word_1: read_u32_at(bytes, header_offset + 4)?,
        header_word_1_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 4)?),
        header_word_2: read_u32_at(bytes, header_offset + 8)?,
        header_word_2_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 8)?),
        entry_stride,
        entry_stride_hex: format!("0x{entry_stride:x}"),
        header_prefix_word_count: 11,
        observed_entry_capacity,
        observed_entry_count,
        zero_trailer_entry_count,
        nonzero_trailer_entry_count,
        distinct_trailer_words,
        distinct_trailer_hex_words,
        zero_trailer_entry_names,
        entries_offset,
        entries_end_offset,
        trailing_footer_hex,
        footer_progress_word_0,
        footer_progress_word_0_hex: format!("0x{footer_progress_word_0:08x}"),
        footer_progress_word_1,
        footer_progress_word_1_hex: format!("0x{footer_progress_word_1:08x}"),
        footer_trailing_byte,
        footer_trailing_byte_hex: format!("0x{footer_trailing_byte:02x}"),
        footer_grounded_alignments,
        entries,
        evidence,
    })
}
