use crate::inspect::smp::bundle::{
    SmpContainerProfile, SmpEarlyContentProbe, SmpHeaderVariantProbe, SmpSecondaryVariantProbe,
    SmpSharedHeader,
};
use crate::inspect::smp::catalog::offsets::bundle::{
    EARLY_ALIGNED_WORD_WINDOW_COUNT, EARLY_PREVIEW_BYTE_LIMIT, EARLY_ZERO_RUN_THRESHOLD,
};
use crate::inspect::smp::common::{
    SmpAsciiPreview, find_next_nonzero_offset, find_zero_run, hex_encode, read_u32_window,
};

pub(in crate::inspect::smp) fn classify_header_variant_probe(
    shared_header: &SmpSharedHeader,
) -> SmpHeaderVariantProbe {
    let words = &shared_header.shared_signature_words_1_to_7;
    let root = shared_header.root_kind_word;
    let final_flag = shared_header.final_flag_word;

    let (variant_family, evidence, is_known_family) = match (root, words.as_slice(), final_flag) {
        (
            0x00002649,
            [
                0x00002ee0,
                0x00040001,
                0x00028000,
                0x00010000,
                0x00000771,
                0x00000771,
                0x00000771,
            ],
            0x00000001,
        ) => (
            "rt3-105-gmx-header-v1".to_string(),
            vec![
                "root kind word 0x00002649".to_string(),
                "1.05 common signature words 1..7".to_string(),
                "final flag 0x00000001".to_string(),
            ],
            true,
        ),
        (
            0x000025e5,
            [
                0x00002ee0,
                0x00040001,
                0x00028000,
                0x00010000,
                0x00000771,
                0x00000771,
                0x00000771,
            ],
            0x00000000,
        ) => (
            "rt3-105-common-header-v1".to_string(),
            vec![
                "root kind word 0x000025e5".to_string(),
                "1.05 common signature words 1..7".to_string(),
                "final flag 0x00000000".to_string(),
            ],
            true,
        ),
        (
            0x000025e5,
            [
                0x00002ee0,
                0x00040001,
                0x00018000,
                0x00010000,
                0x00000746,
                0x00000746,
                0x00000746,
            ],
            0x00000000,
        ) => (
            "rt3-105-scenario-save-header-v1".to_string(),
            vec![
                "root kind word 0x000025e5".to_string(),
                "1.05 scenario-save signature words 1..7".to_string(),
                "final flag 0x00000000".to_string(),
            ],
            true,
        ),
        (
            0x000025e5,
            [
                0x00002ee0,
                0x0001c001,
                0x00018000,
                0x00010000,
                0x00000754,
                0x00000754,
                0x00000754,
            ],
            0x00000000,
        ) => (
            "rt3-105-alt-save-header-v1".to_string(),
            vec![
                "root kind word 0x000025e5".to_string(),
                "1.05 alternate-save signature words 1..7".to_string(),
                "final flag 0x00000000".to_string(),
            ],
            true,
        ),
        (
            0x000026ad,
            [
                0x00002ee0,
                0x00014001,
                0x00020000,
                0x00010000,
                0x00000725,
                0x00000725,
                0x00000725,
            ],
            0x00000100,
        ) => (
            "rt3-classic-gms-header-v1".to_string(),
            vec![
                "root kind word 0x000026ad".to_string(),
                "classic save signature words 1..7".to_string(),
                "final flag 0x00000100".to_string(),
            ],
            true,
        ),
        (
            0x000026ad,
            [
                0x00002ee0,
                0x0001c001,
                0x00018000,
                0x00010000,
                0x00000765,
                0x00000765,
                0x00000765,
            ],
            0x00000001,
        ) => (
            "rt3-classic-gmx-header-v1".to_string(),
            vec![
                "root kind word 0x000026ad".to_string(),
                "classic sandbox signature words 1..7".to_string(),
                "final flag 0x00000001".to_string(),
            ],
            true,
        ),
        (0x000025e5, [0x00002ee0, _, _, 0x00010000, _, _, _], 0x00000000 | 0x00000100) => (
            "rt3-map-header-family".to_string(),
            vec![
                "root kind word 0x000025e5".to_string(),
                "map-family anchor 0x00002ee0".to_string(),
                "word4 0x00010000".to_string(),
            ],
            true,
        ),
        _ => (
            "unknown".to_string(),
            vec![format!(
                "root=0x{root:08x}, words1..7={}, final=0x{final_flag:08x}",
                words
                    .iter()
                    .map(|word| format!("0x{word:08x}"))
                    .collect::<Vec<_>>()
                    .join(", ")
            )],
            false,
        ),
    };

    SmpHeaderVariantProbe {
        variant_family,
        variant_evidence: evidence,
        is_known_family,
    }
}

pub(in crate::inspect::smp) fn probe_early_content_layout(
    bytes: &[u8],
    ascii_run: &SmpAsciiPreview,
) -> Option<SmpEarlyContentProbe> {
    let search_start = ascii_run.offset + ascii_run.byte_len;
    let first_post_text_nonzero_offset = find_next_nonzero_offset(bytes, search_start)?;
    let zero_pad_after_text_len = first_post_text_nonzero_offset.saturating_sub(search_start);
    let first_zero_run_after_block = find_zero_run(
        bytes,
        first_post_text_nonzero_offset,
        EARLY_ZERO_RUN_THRESHOLD,
    )
    .unwrap_or(bytes.len());
    let first_post_text_block = &bytes[first_post_text_nonzero_offset..first_zero_run_after_block];
    let secondary_nonzero_offset = find_next_nonzero_offset(bytes, first_zero_run_after_block);
    let trailing_zero_pad_after_first_block_len = secondary_nonzero_offset
        .map(|offset| offset.saturating_sub(first_zero_run_after_block))
        .unwrap_or_else(|| bytes.len().saturating_sub(first_zero_run_after_block));
    let secondary_aligned_word_window_offset = secondary_nonzero_offset.map(|offset| offset & !0x3);
    let secondary_aligned_word_window_words = secondary_aligned_word_window_offset
        .map(|offset| read_u32_window(bytes, offset, EARLY_ALIGNED_WORD_WINDOW_COUNT))
        .unwrap_or_default();
    let secondary_preview_hex = secondary_nonzero_offset
        .map(|offset| {
            hex_encode(&bytes[offset..bytes.len().min(offset + EARLY_PREVIEW_BYTE_LIMIT)])
        })
        .unwrap_or_default();

    Some(SmpEarlyContentProbe {
        first_post_text_nonzero_offset,
        zero_pad_after_text_len,
        first_post_text_block_len: first_post_text_block.len(),
        first_post_text_block_hex: hex_encode(first_post_text_block),
        trailing_zero_pad_after_first_block_len,
        secondary_nonzero_offset,
        secondary_aligned_word_window_offset,
        secondary_aligned_word_window_hex_words: secondary_aligned_word_window_words
            .iter()
            .map(|word| format!("0x{word:08x}"))
            .collect(),
        secondary_aligned_word_window_words,
        secondary_preview_hex,
    })
}

pub(in crate::inspect::smp) fn classify_secondary_variant_probe(
    probe: &SmpEarlyContentProbe,
) -> Option<SmpSecondaryVariantProbe> {
    let aligned_window_offset = probe.secondary_aligned_word_window_offset?;
    let words = probe.secondary_aligned_word_window_words.clone();
    if words.is_empty() {
        return None;
    }

    let mut evidence = Vec::new();
    let variant_family = match words.as_slice() {
        [0x001e0000, 0x86a00100, 0x03000001, 0xf0000100, ..] => {
            evidence.push("leading word 0x001e0000".to_string());
            evidence.push("anchor word 0x86a00100".to_string());
            evidence.push("third/fourth words 0x03000001 and 0xf0000100".to_string());
            "rt3-gms-family-v1".to_string()
        }
        [0x000a0000, 0x49f00100, 0x00000002, 0xa0000000, ..] => {
            evidence.push("leading word 0x000a0000".to_string());
            evidence.push("anchor word 0x49f00100".to_string());
            evidence.push("third/fourth words 0x00000002 and 0xa0000000".to_string());
            "rt3-gmx-family-v1".to_string()
        }
        [0x001c0000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
            evidence.push("leading word 0x001c0000".to_string());
            evidence.push("anchor word 0x86a00100".to_string());
            evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
            "rt3-105-gms-family-v1".to_string()
        }
        [0x00190000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
            evidence.push("leading word 0x00190000".to_string());
            evidence.push("anchor word 0x86a00100".to_string());
            evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
            "rt3-105-gmx-family-v1".to_string()
        }
        [0x00130000, 0x86a00100, 0x21000001, 0xa0000100, ..] => {
            evidence.push("leading word 0x00130000".to_string());
            evidence.push("anchor word 0x86a00100".to_string());
            evidence.push("third/fourth words 0x21000001 and 0xa0000100".to_string());
            "rt3-105-gms-scenario-family-v1".to_string()
        }
        [0x00140000, 0x93e00100, 0x00000004, 0xa0000000, ..] => {
            evidence.push("leading word 0x00140000".to_string());
            evidence.push("anchor word 0x93e00100".to_string());
            evidence.push("third/fourth words 0x00000004 and 0xa0000000".to_string());
            "rt3-map-secondary-family-v1".to_string()
        }
        [0x00010000, 0x49f00100, 0x00000002, 0xa0000000, ..] => {
            evidence.push("leading word 0x00010000".to_string());
            evidence.push("anchor word 0x49f00100".to_string());
            evidence.push("third/fourth words 0x00000002 and 0xa0000000".to_string());
            "rt3-105-gms-alt-family-v1".to_string()
        }
        [0x86a00100, 0x00000001, 0xa0000000, 0x00000186, ..] => {
            evidence.push("window starts directly on 0x86a00100".to_string());
            evidence.push("likely same family with missing leading unaligned word".to_string());
            "rt3-family-unaligned-anchor".to_string()
        }
        _ => {
            evidence.push(format!(
                "unrecognized leading words: {}",
                words
                    .iter()
                    .take(4)
                    .map(|word| format!("0x{word:08x}"))
                    .collect::<Vec<_>>()
                    .join(", ")
            ));
            "unknown".to_string()
        }
    };

    Some(SmpSecondaryVariantProbe {
        aligned_window_offset,
        hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
        words,
        variant_family,
        variant_evidence: evidence,
    })
}

pub(in crate::inspect::smp) fn classify_container_profile(
    file_extension_hint: Option<&str>,
    header_variant_probe: Option<&SmpHeaderVariantProbe>,
    secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
) -> Option<SmpContainerProfile> {
    let header_family = header_variant_probe.map(|probe| probe.variant_family.as_str())?;
    let secondary_family = secondary_variant_probe.map(|probe| probe.variant_family.as_str())?;
    let extension = file_extension_hint.unwrap_or("");

    let (profile_family, profile_evidence, is_known_profile) =
        match (extension, header_family, secondary_family) {
            ("gms", "rt3-classic-gms-header-v1", "rt3-gms-family-v1") => (
                "rt3-classic-save-container-v1".to_string(),
                vec![
                    "extension .gms".to_string(),
                    "classic save header family".to_string(),
                    "classic save secondary window family".to_string(),
                ],
                true,
            ),
            ("gmx", "rt3-classic-gmx-header-v1", "rt3-gmx-family-v1") => (
                "rt3-classic-sandbox-container-v1".to_string(),
                vec![
                    "extension .gmx".to_string(),
                    "classic sandbox header family".to_string(),
                    "classic sandbox secondary window family".to_string(),
                ],
                true,
            ),
            ("gms", "rt3-105-common-header-v1", "rt3-105-gms-family-v1") => (
                "rt3-105-save-container-v1".to_string(),
                vec![
                    "extension .gms".to_string(),
                    "1.05 common header family".to_string(),
                    "1.05 save secondary window family".to_string(),
                ],
                true,
            ),
            ("gms", "rt3-105-scenario-save-header-v1", "rt3-105-gms-scenario-family-v1") => (
                "rt3-105-scenario-save-container-v1".to_string(),
                vec![
                    "extension .gms".to_string(),
                    "1.05 scenario-save header family".to_string(),
                    "1.05 scenario-save secondary window family".to_string(),
                ],
                true,
            ),
            ("gms", "rt3-105-alt-save-header-v1", "rt3-105-gms-alt-family-v1") => (
                "rt3-105-alt-save-container-v1".to_string(),
                vec![
                    "extension .gms".to_string(),
                    "1.05 alternate-save header family".to_string(),
                    "1.05 alternate-save secondary window family".to_string(),
                ],
                true,
            ),
            ("gmx", "rt3-105-gmx-header-v1", "rt3-105-gmx-family-v1") => (
                "rt3-105-sandbox-container-v1".to_string(),
                vec![
                    "extension .gmx".to_string(),
                    "1.05 sandbox header family".to_string(),
                    "1.05 sandbox secondary window family".to_string(),
                ],
                true,
            ),
            ("gmp", "rt3-105-common-header-v1", "rt3-family-unaligned-anchor") => (
                "rt3-105-map-container-v1".to_string(),
                vec![
                    "extension .gmp".to_string(),
                    "1.05 common header family".to_string(),
                    "map-style secondary unaligned anchor".to_string(),
                ],
                true,
            ),
            ("gmp", "rt3-105-scenario-save-header-v1", "unknown") => (
                "rt3-105-scenario-map-container-v1".to_string(),
                vec![
                    "extension .gmp".to_string(),
                    "1.05 scenario-map header family".to_string(),
                    "fixed candidate-availability table range present despite unknown early secondary window".to_string(),
                ],
                true,
            ),
            ("gmp", "rt3-105-alt-save-header-v1", "unknown") => (
                "rt3-105-alt-map-container-v1".to_string(),
                vec![
                    "extension .gmp".to_string(),
                    "1.05 alternate-map header family".to_string(),
                    "fixed candidate-availability table range present despite unknown early secondary window".to_string(),
                ],
                true,
            ),
            ("gmp", "rt3-map-header-family", "rt3-family-unaligned-anchor") => (
                "rt3-map-container-family".to_string(),
                vec![
                    "extension .gmp".to_string(),
                    "map header family".to_string(),
                    "map-style secondary unaligned anchor".to_string(),
                ],
                true,
            ),
            ("gmp", "rt3-map-header-family", "rt3-map-secondary-family-v1") => (
                "rt3-map-container-family".to_string(),
                vec![
                    "extension .gmp".to_string(),
                    "map header family".to_string(),
                    "observed map secondary window family".to_string(),
                ],
                true,
            ),
            (_, header_family, secondary_family) => (
                "unknown".to_string(),
                vec![
                    format!(
                        "extension {}",
                        if extension.is_empty() { "<none>" } else { extension }
                    ),
                    format!("header family {header_family}"),
                    format!("secondary family {secondary_family}"),
                ],
                false,
            ),
        };

    Some(SmpContainerProfile {
        profile_family,
        profile_evidence,
        is_known_profile,
    })
}
