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

pub(in crate::inspect::smp) fn parse_save_region_fixed_row_run_candidate_probe(
    bytes: &[u8],
    file_extension_hint: Option<&str>,
    container_profile: Option<&SmpContainerProfile>,
    region_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpSaveRegionFixedRowRunCandidateProbe> {
    if file_extension_hint != Some("gms") {
        return None;
    }
    let profile = container_profile?;
    let region_header_probe = region_header_probe?;
    let target_row_count = region_header_probe.live_record_count as usize;
    if target_row_count == 0 {
        return None;
    }
    let scan_end_offset = region_header_probe.metadata_tag_offset;
    let row_span_len = target_row_count.checked_mul(SAVE_REGION_FIXED_ROW_STRIDE)?;
    let scan_bytes = bytes.get(..scan_end_offset)?;
    let mut candidates = find_u32_le_offsets(scan_bytes, region_header_probe.live_record_count)
        .into_iter()
        .filter_map(|count_offset| {
            let rows_offset = count_offset.checked_add(4)?;
            let rows_end_offset = rows_offset.checked_add(row_span_len)?;
            if rows_end_offset > scan_end_offset {
                return None;
            }
            let rows_bytes = bytes.get(rows_offset..rows_end_offset)?;
            let mut dword_lane_summaries =
                Vec::with_capacity(SAVE_REGION_FIXED_ROW_DWORD_LANE_COUNT);
            let mut best_probable_density_lane = None::<(usize, usize)>;
            for lane_index in 0..SAVE_REGION_FIXED_ROW_DWORD_LANE_COUNT {
                let relative_offset = lane_index * 4;
                let mut zero_count = 0usize;
                let mut nonzero_count = 0usize;
                let mut probable_normal_f32_count = 0usize;
                let mut small_unsigned_count = 0usize;
                let mut distinct_values = BTreeSet::new();
                let mut sample_values_hex = Vec::new();
                for row_index in 0..target_row_count {
                    let row_offset = row_index * SAVE_REGION_FIXED_ROW_STRIDE + relative_offset;
                    let raw_u32 = read_u32_at(rows_bytes, row_offset)?;
                    if raw_u32 == 0 {
                        zero_count += 1;
                    } else {
                        nonzero_count += 1;
                    }
                    if probable_normal_f32_string(raw_u32).is_some() {
                        probable_normal_f32_count += 1;
                    }
                    if raw_u32 <= 1024 {
                        small_unsigned_count += 1;
                    }
                    if distinct_values.insert(raw_u32) && sample_values_hex.len() < 6 {
                        sample_values_hex.push(format!("0x{raw_u32:08x}"));
                    }
                }
                if best_probable_density_lane
                    .is_none_or(|(_, best_count)| probable_normal_f32_count > best_count)
                {
                    best_probable_density_lane = Some((relative_offset, probable_normal_f32_count));
                }
                dword_lane_summaries.push(SmpSaveFixedRowRunDwordLaneSummary {
                    relative_offset,
                    relative_offset_hex: format!("0x{relative_offset:x}"),
                    zero_count,
                    nonzero_count,
                    distinct_value_count: distinct_values.len(),
                    probable_normal_f32_count,
                    small_unsigned_count,
                    sample_values_hex,
                });
            }
            let mut trailing_values = BTreeSet::new();
            let mut trailing_byte_zero_count = 0usize;
            let mut trailing_byte_nonzero_count = 0usize;
            let mut trailing_byte_sample_values_hex = Vec::new();
            for row_index in 0..target_row_count {
                let value = *rows_bytes.get(row_index * SAVE_REGION_FIXED_ROW_STRIDE + 0x28)?;
                if value == 0 {
                    trailing_byte_zero_count += 1;
                } else {
                    trailing_byte_nonzero_count += 1;
                }
                if trailing_values.insert(value) && trailing_byte_sample_values_hex.len() < 8 {
                    trailing_byte_sample_values_hex.push(format!("0x{value:02x}"));
                }
            }
            let shape_signature = build_save_region_fixed_row_run_candidate_shape_signature(
                &dword_lane_summaries,
                trailing_byte_zero_count,
                trailing_values.len(),
                target_row_count,
            );
            let shape_family_signature =
                build_save_region_fixed_row_run_candidate_shape_family_signature(
                    &dword_lane_summaries,
                    trailing_byte_zero_count,
                    trailing_values.len(),
                    target_row_count,
                );
            Some(SmpSaveRegionFixedRowRunCandidate {
                count_offset,
                count_offset_hex: format!("0x{count_offset:x}"),
                row_count: target_row_count,
                row_stride: SAVE_REGION_FIXED_ROW_STRIDE,
                row_stride_hex: format!("0x{:x}", SAVE_REGION_FIXED_ROW_STRIDE),
                rows_offset,
                rows_offset_hex: format!("0x{rows_offset:x}"),
                rows_end_offset,
                rows_end_offset_hex: format!("0x{rows_end_offset:x}"),
                distance_to_region_metadata_tag: scan_end_offset.saturating_sub(rows_end_offset),
                distance_to_region_metadata_tag_hex: format!(
                    "0x{:x}",
                    scan_end_offset.saturating_sub(rows_end_offset)
                ),
                dword_lane_summaries,
                shape_signature,
                shape_family_signature,
                trailing_byte_zero_count,
                trailing_byte_nonzero_count,
                trailing_byte_distinct_value_count: trailing_values.len(),
                trailing_byte_sample_values_hex,
                best_probable_density_lane_relative_offset_hex: best_probable_density_lane
                    .filter(|(_, count)| *count != 0)
                    .map(|(relative_offset, _)| format!("0x{relative_offset:x}")),
            })
        })
        .collect::<Vec<_>>();
    candidates.sort_by_key(|candidate| {
        (
            Reverse(
                candidate
                    .dword_lane_summaries
                    .iter()
                    .map(|summary| summary.probable_normal_f32_count)
                    .max()
                    .unwrap_or_default(),
            ),
            candidate.distance_to_region_metadata_tag,
            candidate.count_offset,
        )
    });
    candidates.truncate(SAVE_REGION_FIXED_ROW_CANDIDATE_PROBE_LIMIT);
    let candidate_count = candidates.len();
    let best_candidate_offset_hex = candidates
        .first()
        .map(|candidate| candidate.rows_offset_hex.clone());
    Some(SmpSaveRegionFixedRowRunCandidateProbe {
        profile_family: profile.profile_family.clone(),
        source_kind: "save-region-fixed-row-run-candidates".to_string(),
        semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
        target_row_count,
        target_row_stride: SAVE_REGION_FIXED_ROW_STRIDE,
        target_row_stride_hex: format!("0x{:x}", SAVE_REGION_FIXED_ROW_STRIDE),
        scan_start_offset: 0,
        scan_start_offset_hex: "0x0".to_string(),
        scan_end_offset,
        scan_end_offset_hex: format!("0x{scan_end_offset:x}"),
        candidates,
        evidence: vec![
            format!(
                "candidate scan looks for pre-region-header counted runs keyed to the grounded live region count {} with fixed row stride 0x{:x}",
                target_row_count, SAVE_REGION_FIXED_ROW_STRIDE
            ),
            format!(
                "current scan range ends at region metadata tag offset 0x{:x}, because the atlas restore order places the fixed rows before the tagged 0x5209/0x520a/0x520b region collection",
                scan_end_offset
            ),
            format!(
                "kept {} highest-signal candidates after sorting by probable-f32 lane density and proximity to the region metadata tag; best candidate rows offset is {:?}",
                candidate_count, best_candidate_offset_hex
            ),
        ],
    })
}

pub(in crate::inspect::smp) fn build_save_region_fixed_row_run_candidate_shape_signature(
    dword_lane_summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
    trailing_byte_zero_count: usize,
    trailing_byte_distinct_value_count: usize,
    row_count: usize,
) -> String {
    fn pick_lane_terms<F, P>(
        summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
        score: F,
        include: P,
        row_count: usize,
        max_terms: usize,
    ) -> Vec<String>
    where
        F: Fn(&SmpSaveFixedRowRunDwordLaneSummary) -> usize,
        P: Fn(&SmpSaveFixedRowRunDwordLaneSummary) -> bool,
    {
        let high_signal_threshold = row_count.saturating_mul(3) / 4;
        let mut picked = summaries
            .iter()
            .filter(|summary| include(summary))
            .filter(|summary| score(summary) >= high_signal_threshold)
            .map(|summary| {
                (
                    summary.relative_offset,
                    format!("{}:{}", summary.relative_offset_hex, score(summary)),
                )
            })
            .collect::<Vec<_>>();
        if picked.is_empty() {
            picked = summaries
                .iter()
                .filter(|summary| include(summary))
                .filter_map(|summary| {
                    let value = score(summary);
                    (value != 0).then(|| {
                        (
                            summary.relative_offset,
                            format!("{}:{}", summary.relative_offset_hex, value),
                        )
                    })
                })
                .collect::<Vec<_>>();
            picked.sort_by_key(|(relative_offset, term)| {
                let value = term
                    .split(':')
                    .nth(1)
                    .and_then(|part| part.parse::<usize>().ok())
                    .unwrap_or_default();
                (Reverse(value), *relative_offset)
            });
            picked.truncate(max_terms);
        }
        picked.into_iter().map(|(_, term)| term).collect()
    }

    let probable_terms = pick_lane_terms(
        dword_lane_summaries,
        |summary| summary.probable_normal_f32_count,
        |_| true,
        row_count,
        3,
    );
    let small_terms = pick_lane_terms(
        dword_lane_summaries,
        |summary| summary.small_unsigned_count,
        |summary| summary.nonzero_count != 0,
        row_count,
        2,
    );
    let zero_terms = pick_lane_terms(
        dword_lane_summaries,
        |summary| summary.zero_count,
        |summary| summary.zero_count != row_count,
        row_count,
        2,
    );

    format!(
        "pf32=[{}]|small=[{}]|zero=[{}]|trail={}/{}",
        probable_terms.join(","),
        small_terms.join(","),
        zero_terms.join(","),
        trailing_byte_zero_count,
        trailing_byte_distinct_value_count
    )
}

pub(in crate::inspect::smp) fn build_save_region_fixed_row_run_candidate_shape_family_signature(
    dword_lane_summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
    trailing_byte_zero_count: usize,
    trailing_byte_distinct_value_count: usize,
    row_count: usize,
) -> String {
    let dense_pf32_offsets = dword_lane_summaries
        .iter()
        .filter(|summary| summary.probable_normal_f32_count >= row_count.saturating_mul(3) / 4)
        .map(|summary| summary.relative_offset_hex.clone())
        .collect::<Vec<_>>();
    let partial_zero_offsets = dword_lane_summaries
        .iter()
        .filter(|summary| {
            summary.zero_count != 0
                && summary.zero_count != row_count
                && summary.zero_count >= row_count.saturating_mul(5) / 100
        })
        .map(|summary| summary.relative_offset_hex.clone())
        .collect::<Vec<_>>();
    let small_nonzero_offsets = dword_lane_summaries
        .iter()
        .filter(|summary| {
            summary.nonzero_count != 0
                && summary.small_unsigned_count >= row_count.saturating_mul(8) / 100
        })
        .map(|summary| summary.relative_offset_hex.clone())
        .collect::<Vec<_>>();

    format!(
        "dense_pf32=[{}]|small_nonzero=[{}]|partial_zero=[{}]|trail_bucket={}/{}",
        dense_pf32_offsets.join(","),
        small_nonzero_offsets.join(","),
        partial_zero_offsets.join(","),
        trailing_byte_zero_count / 8,
        trailing_byte_distinct_value_count / 8
    )
}
