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

pub(in crate::inspect::smp) fn parse_save_region_record_triplet_probe(
    bytes: &[u8],
    header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpSaveRegionRecordTripletProbe> {
    let header_probe = header_probe?;
    if header_probe.source_kind != "save-region-tagged-header-counts" {
        return None;
    }
    let records_payload =
        bytes.get(header_probe.records_tag_offset + 4..header_probe.close_tag_offset)?;
    let name_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
    let policy_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_POLICY_TAG);
    let profile_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_PROFILE_TAG);
    let record_count = header_probe.live_record_count as usize;
    if name_offsets.len() != record_count
        || policy_offsets.len() != record_count
        || profile_offsets.len() != record_count
    {
        return None;
    }
    let region_payload_start_offsets = bytes
        .get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)
        .and_then(|metadata_payload| {
            let directory_root_byte_offset =
                SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
            let directory_len_dwords =
                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 records_payload_absolute_offset = header_probe.records_tag_offset.checked_add(4)?;
            (0..record_count)
                .map(|index| {
                    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)? as usize;
                    let payload_absolute_offset = header_probe
                        .metadata_tag_offset
                        .checked_add(4)?
                        .checked_add(payload_relative_offset)?;
                    let payload_start =
                        payload_absolute_offset.checked_sub(records_payload_absolute_offset)?;
                    (payload_start <= records_payload.len()).then_some(payload_start)
                })
                .collect::<Option<Vec<_>>>()
        })
        .unwrap_or_else(|| name_offsets.clone());
    let mut entries = Vec::with_capacity(record_count);
    for index in 0..record_count {
        let record_payload_relative_offset = region_payload_start_offsets[index];
        let name_tag_relative_offset = name_offsets[index];
        let policy_tag_relative_offset = policy_offsets[index];
        let profile_tag_relative_offset = profile_offsets[index];
        let next_record_relative_offset = name_offsets
            .get(index + 1)
            .copied()
            .unwrap_or(records_payload.len());
        if record_payload_relative_offset > name_tag_relative_offset {
            return None;
        }
        if !(name_tag_relative_offset < policy_tag_relative_offset
            && policy_tag_relative_offset < profile_tag_relative_offset
            && profile_tag_relative_offset < next_record_relative_offset)
        {
            return None;
        }
        let pre_name_prefix =
            records_payload.get(record_payload_relative_offset..name_tag_relative_offset)?;
        let pre_name_prefix_dword_candidates = build_region_record_prefix_dword_candidates(
            record_payload_relative_offset,
            pre_name_prefix,
        );
        let name_payload =
            records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?;
        let name = parse_save_len_prefixed_ascii_name(name_payload)?;
        let policy_chunk_len =
            profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
        if policy_chunk_len != 0x1a {
            return None;
        }
        let policy_payload =
            records_payload.get(policy_tag_relative_offset + 4..profile_tag_relative_offset)?;
        let policy_leading_f32_0 = f32::from_bits(read_u32_at(policy_payload, 0)?);
        let policy_leading_f32_1 = f32::from_bits(read_u32_at(policy_payload, 4)?);
        let policy_leading_f32_2 = f32::from_bits(read_u32_at(policy_payload, 8)?);
        let mut policy_reserved_dwords = Vec::with_capacity(3);
        let mut policy_reserved_dword_candidates = Vec::with_capacity(3);
        for dword_index in 0..3 {
            let reserved_relative_offset = 12 + dword_index * 4;
            let raw_u32 = read_u32_at(policy_payload, reserved_relative_offset)?;
            policy_reserved_dwords.push(raw_u32);
            let relative_offset = record_payload_relative_offset
                + policy_tag_relative_offset
                + 4
                + reserved_relative_offset;
            policy_reserved_dword_candidates.push(SmpSaveDwordCandidate {
                label: format!("policy_reserved_word_{}", dword_index + 1),
                relative_offset,
                relative_offset_hex: format!("0x{relative_offset:x}"),
                raw_u32,
                raw_u32_hex: format!("0x{raw_u32:08x}"),
                value_i32: raw_u32 as i32,
                value_f32: f32::from_bits(raw_u32),
            });
        }
        let policy_trailing_word = read_u16_at(policy_payload, 24)?;
        let profile_chunk_len =
            next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
        let profile_payload =
            records_payload.get(profile_tag_relative_offset + 4..next_record_relative_offset)?;
        let profile_collection = parse_save_region_profile_collection_probe(profile_payload);
        entries.push(SmpSaveRegionRecordTripletEntryProbe {
            record_index: index,
            name,
            record_payload_relative_offset,
            record_payload_relative_offset_hex: format!("0x{record_payload_relative_offset:x}"),
            name_tag_relative_offset,
            policy_tag_relative_offset,
            profile_tag_relative_offset,
            pre_name_prefix_len: pre_name_prefix.len(),
            pre_name_prefix_hex_bytes: pre_name_prefix
                .iter()
                .map(|byte| format!("0x{byte:02x}"))
                .collect(),
            pre_name_prefix_dword_candidates,
            policy_chunk_len,
            profile_chunk_len,
            policy_leading_f32_0,
            policy_leading_f32_1,
            policy_leading_f32_2,
            policy_reserved_dwords,
            policy_reserved_dword_candidates,
            policy_trailing_word,
            policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
            profile_collection,
        });
    }
    let zero_trailing_padding_record_count = entries
        .iter()
        .filter(|entry| {
            entry
                .profile_collection
                .as_ref()
                .is_some_and(|collection| collection.trailing_padding_len == 0)
        })
        .count();
    let records_with_nonzero_pre_name_prefix = entries
        .iter()
        .filter(|entry| entry.pre_name_prefix_len != 0)
        .count();
    let records_with_prefix_dword_candidates = entries
        .iter()
        .filter(|entry| !entry.pre_name_prefix_dword_candidates.is_empty())
        .count();
    let records_with_any_nonzero_policy_reserved_dword = entries
        .iter()
        .filter(|entry| {
            entry
                .policy_reserved_dwords
                .iter()
                .any(|raw_u32| *raw_u32 != 0)
        })
        .count();
    let policy_reserved_nonzero_counts = (0..3)
        .map(|dword_index| {
            entries
                .iter()
                .filter(|entry| entry.policy_reserved_dwords[dword_index] != 0)
                .count()
        })
        .collect::<Vec<_>>();
    let unique_nonzero_policy_reserved_triplets = entries
        .iter()
        .filter_map(|entry| {
            let triplet = [
                entry.policy_reserved_dwords[0],
                entry.policy_reserved_dwords[1],
                entry.policy_reserved_dwords[2],
            ];
            triplet
                .iter()
                .any(|raw_u32| *raw_u32 != 0)
                .then_some(triplet)
        })
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect::<Vec<_>>();
    let unique_pre_name_prefix_lens = entries
        .iter()
        .map(|entry| entry.pre_name_prefix_len)
        .collect::<BTreeSet<_>>()
        .into_iter()
        .collect::<Vec<_>>();
    Some(SmpSaveRegionRecordTripletProbe {
        profile_family: header_probe.profile_family.clone(),
        source_kind: "save-region-record-triplets".to_string(),
        semantic_family: "scenario-save-region-record-triplets".to_string(),
        records_tag_offset: header_probe.records_tag_offset,
        close_tag_offset: header_probe.close_tag_offset,
        record_count,
        entries,
        evidence: vec![
            "save-side region records in the non-direct Marker09 family are serialized as repeated 0x55f1/0x55f2/0x55f3 triplets inside the records span".to_string(),
            format!(
                "decoded {} region record triplets with one len-prefixed name chunk, one fixed policy chunk, and one trailing profile payload chunk per record",
                record_count
            ),
            "each fixed 0x55f2 policy chunk currently decodes as three leading f32 lanes, three reserved dwords, and one trailing u16 word".to_string(),
            "the trailing 0x55f3 payload also carries an embedded direct profile collection with fixed 0x22-byte rows on grounded saves".to_string(),
            format!(
                "live-entry directory now also grounds the actual 0x520a payload starts: {} of {} records currently have nonzero bytes before the first 0x55f1 tag, with unique pre-name prefix lengths {:?}",
                records_with_nonzero_pre_name_prefix,
                record_count,
                unique_pre_name_prefix_lens
            ),
            format!(
                "structured pre-name prefix dword candidates are currently present on {} of {} decoded region records",
                records_with_prefix_dword_candidates,
                record_count
            ),
            format!(
                "fixed 0x55f2 policy reserved dwords are nonzero on {} of {} decoded region records, with per-word nonzero counts {:?} and unique nonzero triplets {:?}",
                records_with_any_nonzero_policy_reserved_dword,
                record_count,
                policy_reserved_nonzero_counts,
                unique_nonzero_policy_reserved_triplets
            ),
            format!(
                "on grounded saves the 0x55f3 payload is fully consumed by that embedded profile collection: all {} decoded records currently have zero trailing padding beyond the direct profile rows",
                zero_trailing_padding_record_count
            ),
        ],
    })
}

pub(in crate::inspect::smp) fn build_region_record_prefix_dword_candidates(
    record_payload_relative_offset: usize,
    prefix_bytes: &[u8],
) -> Vec<SmpSaveDwordCandidate> {
    prefix_bytes
        .chunks_exact(4)
        .enumerate()
        .map(|(index, chunk)| {
            let raw_u32 = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
            let relative_offset = record_payload_relative_offset + index * 4;
            SmpSaveDwordCandidate {
                label: format!("pre_name_prefix_word_{}", index + 1),
                relative_offset,
                relative_offset_hex: format!("0x{relative_offset:x}"),
                raw_u32,
                raw_u32_hex: format!("0x{raw_u32:08x}"),
                value_i32: raw_u32 as i32,
                value_f32: f32::from_bits(raw_u32),
            }
        })
        .collect()
}
