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

pub(in crate::inspect::smp) fn parse_rt3_105_packed_profile_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    header_variant_probe: Option<&SmpHeaderVariantProbe>,
    container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpRt3105PackedProfileProbe> {
    let profile_family = if container_profile.is_some_and(|profile| {
        matches!(
            profile.profile_family.as_str(),
            "rt3-105-save-container-v1"
                | "rt3-105-scenario-save-container-v1"
                | "rt3-105-alt-save-container-v1"
        )
    }) {
        container_profile
            .expect("checked above")
            .profile_family
            .clone()
    } else if file_extension_hint == Some("gms")
        && header_variant_probe.is_some_and(|probe| {
            matches!(
                probe.variant_family.as_str(),
                "rt3-105-common-header-v1"
                    | "rt3-105-scenario-save-header-v1"
                    | "rt3-105-alt-save-header-v1"
                    | "rt3-map-header-family"
            )
        })
    {
        "rt3-105-save-analog-block-inferred".to_string()
    } else {
        return None;
    };

    if file_extension_hint != Some("gms") {
        return None;
    }

    let map_path_offset = find_c_string_with_suffix_in_range(bytes, 0x7000, 0x9000, ".gmp")?;
    let packed_profile_offset = map_path_offset.checked_sub(0x10)?;
    let packed_profile_len = 0x108usize;
    let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
    if block_end > bytes.len() {
        return None;
    }

    let packed_profile_block =
        parse_rt3_105_packed_profile_block(bytes, packed_profile_offset, packed_profile_len)?;
    let ascii_runs = collect_ascii_previews_in_range(bytes, packed_profile_offset, block_end, 4);

    Some(SmpRt3105PackedProfileProbe {
        profile_family,
        packed_profile_offset,
        packed_profile_len,
        packed_profile_len_hex: format!("0x{packed_profile_len:03x}"),
        packed_profile_block,
        ascii_runs,
    })
}

pub(in crate::inspect::smp) fn parse_rt3_105_post_span_bridge_probe(
    runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
    runtime_post_span_probe: Option<&SmpRuntimePostSpanProbe>,
    rt3_105_packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
) -> Option<SmpRt3105PostSpanBridgeProbe> {
    let trailer = runtime_trailer_block?;
    let post_span = runtime_post_span_probe?;
    let packed_profile = rt3_105_packed_profile_probe?;
    let supported = matches!(
        trailer.profile_family.as_str(),
        "rt3-105-save-container-v1"
            | "rt3-105-scenario-save-container-v1"
            | "rt3-105-alt-save-container-v1"
            | "rt3-105-save-analog-block-inferred"
    );
    if !supported || trailer.profile_family != post_span.profile_family {
        return None;
    }

    let next_candidate_high_u16_words = post_span
        .header_candidates
        .first()
        .map(|candidate| candidate.high_u16_words.clone())
        .unwrap_or_default();
    let next_candidate_high_hex_words = next_candidate_high_u16_words
        .iter()
        .map(|word| format!("0x{word:04x}"))
        .collect::<Vec<_>>();
    let next_candidate_offset = post_span.next_aligned_candidate_offset;
    let next_candidate_delta_from_span_target =
        next_candidate_offset.and_then(|offset| offset.checked_sub(post_span.span_target_offset));
    let packed_profile_delta_from_span_target = packed_profile
        .packed_profile_offset
        .checked_sub(post_span.span_target_offset)?;
    let next_candidate_delta_from_packed_profile = next_candidate_offset
        .map(|offset| offset as i64 - packed_profile.packed_profile_offset as i64);

    let mut bridge_evidence = vec![
        format!("profile family {}", trailer.profile_family),
        format!("selector high {}", trailer.selector_high_hex),
        format!("descriptor high {}", trailer.descriptor_high_hex),
        format!(
            "packed profile sits +0x{packed_profile_delta_from_span_target:x} from span target"
        ),
    ];
    if let Some(delta) = next_candidate_delta_from_span_target {
        bridge_evidence.push(format!("next candidate sits +0x{delta:x} from span target"));
    }
    if let Some(delta) = next_candidate_delta_from_packed_profile {
        bridge_evidence.push(format!(
            "next candidate is {delta:+#x} relative to packed profile"
        ));
    }

    let bridge_family = match (
        trailer.selector_high_u16,
        trailer.descriptor_high_u16,
        next_candidate_high_u16_words.as_slice(),
    ) {
        (0x7110, 0x7801 | 0x7401, [0x6200, 0x0000, 0xfff7, 0x5515, ..]) => {
            bridge_evidence.push(format!(
                "selector/descriptor pair 0x7110 -> 0x{:04x}",
                trailer.descriptor_high_u16
            ));
            bridge_evidence.push(
                "next candidate begins with high-16 lanes 0x6200/0x0000/0xfff7/0x5515"
                    .to_string(),
            );
            "rt3-105-save-post-span-bridge-v1"
        }
        (0x54cd, 0x5901, [0x1500, 0x0100, 0x4100, 0x0200, ..]) => {
            bridge_evidence.push("selector/descriptor pair 0x54cd -> 0x5901".to_string());
            bridge_evidence.push(
                "next candidate begins with high-16 lanes 0x1500/0x0100/0x4100/0x0200"
                    .to_string(),
            );
            "rt3-105-alt-save-post-span-bridge-v1"
        }
        (0x0001, 0x0186, [0x0186, 0x0006, 0x0006, 0x0001, ..]) => {
            bridge_evidence.push("selector/descriptor pair 0x0001 -> 0x0186".to_string());
            bridge_evidence.push(
                "next candidate remains in the local cycle neighborhood with 0x0186/0x0006/0x0006/0x0001"
                    .to_string(),
            );
            "rt3-105-scenario-post-span-bridge-v1"
        }
        _ => "unknown",
    }
    .to_string();

    Some(SmpRt3105PostSpanBridgeProbe {
        profile_family: trailer.profile_family.clone(),
        bridge_family,
        bridge_evidence,
        span_target_offset: post_span.span_target_offset,
        next_candidate_offset,
        next_candidate_delta_from_span_target,
        packed_profile_offset: packed_profile.packed_profile_offset,
        packed_profile_delta_from_span_target,
        next_candidate_delta_from_packed_profile,
        selector_high_u16: trailer.selector_high_u16,
        selector_high_hex: trailer.selector_high_hex.clone(),
        descriptor_high_u16: trailer.descriptor_high_u16,
        descriptor_high_hex: trailer.descriptor_high_hex.clone(),
        next_candidate_high_u16_words,
        next_candidate_high_hex_words,
    })
}

pub(in crate::inspect::smp) fn parse_rt3_105_save_bridge_payload_probe(
    bytes: &[u8],
    bridge_probe: Option<&SmpRt3105PostSpanBridgeProbe>,
) -> Option<SmpRt3105SaveBridgePayloadProbe> {
    let bridge = bridge_probe?;
    if bridge.bridge_family != "rt3-105-save-post-span-bridge-v1" {
        return None;
    }

    let primary_block_offset = bridge.next_candidate_offset?;
    let primary_block_word_count = 8usize;
    let primary_words = read_u32_window(bytes, primary_block_offset, primary_block_word_count);
    if primary_words.len() < primary_block_word_count {
        return None;
    }

    let secondary_block_delta_from_primary = 0x1808usize;
    let secondary_block_offset = primary_block_offset + secondary_block_delta_from_primary;
    let secondary_block_end_offset = bridge.packed_profile_offset;
    let secondary_block_len = secondary_block_end_offset.checked_sub(secondary_block_offset)?;
    let secondary_preview_word_count = 32usize;
    let secondary_words =
        read_u32_window(bytes, secondary_block_offset, secondary_preview_word_count);
    if secondary_words.len() < secondary_preview_word_count {
        return None;
    }

    let primary_signature_matches = primary_words
        == [
            0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
            0x54550000,
        ];
    let secondary_prefix_matches = secondary_words.starts_with(&[
        0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
        0x00001555,
    ]);

    let mut evidence = vec![
        "bridge family rt3-105-save-post-span-bridge-v1".to_string(),
        format!("primary block offset 0x{primary_block_offset:08x}"),
        format!("secondary block offset 0x{secondary_block_offset:08x}"),
        format!("secondary block delta from primary 0x{secondary_block_delta_from_primary:x}"),
        format!("secondary block end offset 0x{secondary_block_end_offset:08x}"),
        format!("secondary block span 0x{secondary_block_len:x} bytes"),
    ];
    if primary_signature_matches {
        evidence.push(
            "primary 8-word bridge block matches the observed 0x6200/0xfff7/0x5515/0x5555 spine"
                .to_string(),
        );
    }
    if secondary_prefix_matches {
        evidence.push(
            "secondary preview matches the observed 0x0005/0xfff7/0x5454 dense block prefix"
                .to_string(),
        );
    }

    Some(SmpRt3105SaveBridgePayloadProbe {
        profile_family: bridge.profile_family.clone(),
        bridge_family: bridge.bridge_family.clone(),
        primary_block_offset,
        primary_block_len: primary_block_word_count * 4,
        primary_block_len_hex: format!("0x{:02x}", primary_block_word_count * 4),
        primary_hex_words: primary_words
            .iter()
            .map(|word| format!("0x{word:08x}"))
            .collect(),
        primary_words,
        secondary_block_offset,
        secondary_block_delta_from_primary,
        secondary_block_delta_from_primary_hex: format!("0x{secondary_block_delta_from_primary:x}"),
        secondary_block_end_offset,
        secondary_block_len,
        secondary_block_len_hex: format!("0x{secondary_block_len:x}"),
        secondary_preview_word_count,
        secondary_hex_words: secondary_words
            .iter()
            .map(|word| format!("0x{word:08x}"))
            .collect(),
        secondary_words,
        evidence,
    })
}

pub(in crate::inspect::smp) fn parse_rt3_105_packed_profile_block(
    bytes: &[u8],
    packed_profile_offset: usize,
    packed_profile_len: usize,
) -> Option<SmpRt3105PackedProfileBlock> {
    let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
    if block_end > bytes.len() || packed_profile_len != 0x108 {
        return None;
    }

    let leading_word_0 = read_u32_at(bytes, packed_profile_offset)?;
    let trailing_zero_word_count_after_leading_word = (1..4)
        .take_while(|index| {
            read_u32_at(bytes, packed_profile_offset + (index * 4)).is_some_and(|word| word == 0)
        })
        .count();
    let header_flag_word_3 = read_u32_at(bytes, packed_profile_offset + 0x0c)?;
    let map_path_offset = 0x10usize;
    let display_name_offset = 0x43usize;
    let stable_nonzero_word_offsets = [0x00usize, 0x0c, 0x78, 0x7c, 0x80, 0x84];
    let stable_nonzero_words = stable_nonzero_word_offsets
        .iter()
        .filter_map(|relative_offset| {
            let value = read_u32_at(bytes, packed_profile_offset + relative_offset)?;
            if value == 0 {
                return None;
            }

            Some(SmpPackedProfileWordLane {
                relative_offset: *relative_offset,
                relative_offset_hex: format!("0x{relative_offset:02x}"),
                value,
                value_hex: format!("0x{value:08x}"),
            })
        })
        .collect::<Vec<_>>();

    Some(SmpRt3105PackedProfileBlock {
        relative_len: packed_profile_len,
        relative_len_hex: format!("0x{packed_profile_len:03x}"),
        leading_word_0,
        leading_word_0_hex: format!("0x{leading_word_0:08x}"),
        trailing_zero_word_count_after_leading_word,
        header_flag_word_3,
        header_flag_word_3_hex: format!("0x{header_flag_word_3:08x}"),
        map_path_offset,
        map_path: read_c_string_in_range(bytes, packed_profile_offset + map_path_offset, block_end),
        display_name_offset,
        display_name: read_c_string_in_range(
            bytes,
            packed_profile_offset + display_name_offset,
            block_end,
        ),
        profile_byte_0x77: bytes[packed_profile_offset + 0x77],
        profile_byte_0x77_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x77]),
        profile_byte_0x82: bytes[packed_profile_offset + 0x82],
        profile_byte_0x82_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x82]),
        profile_byte_0x97: bytes[packed_profile_offset + 0x97],
        profile_byte_0x97_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x97]),
        profile_byte_0xc5: bytes[packed_profile_offset + 0xc5],
        profile_byte_0xc5_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0xc5]),
        stable_nonzero_words,
    })
}
