use std::collections::BTreeMap;

use crate::inspect::smp::structures::*;

pub(super) fn build_live_entry_prelude_summary(
    payload: &[u8],
    records_payload: &[u8],
    live_id_bound: u32,
    live_record_count: u32,
) -> Option<SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary> {
    let bitset_len = ((usize::try_from(live_id_bound).ok()?).saturating_add(8)) / 8;
    payload
        .get(
            INDEXED_COLLECTION_SERIALIZED_HEADER_LEN
                ..INDEXED_COLLECTION_SERIALIZED_HEADER_LEN + bitset_len,
        )
        .and_then(|bitset| {
            let live_entry_ids =
                decode_live_entry_ids_from_tombstone_bitset(bitset, live_id_bound)?;
            if live_entry_ids.len() != usize::try_from(live_record_count).ok()? {
                return None;
            }
            #[derive(Clone)]
            struct LiveEntryPreludeRow {
                live_entry_id: u32,
                payload_relative_offset: usize,
                child_count: u16,
                saved_primary_child_byte: u8,
                first_payload_dword: u32,
                first_name_tag_relative_offset: Option<usize>,
                first_primary_name: Option<String>,
                first_secondary_name: Option<String>,
                first_tertiary_name: Option<String>,
            }
            let mut rows = Vec::new();
            let mut cursor = 0usize;
            for live_entry_id in live_entry_ids.iter().copied() {
                let payload_len = usize::from(read_u16_at(records_payload, cursor)?);
                let payload_relative_offset = cursor.checked_add(2)?;
                let payload_end = payload_relative_offset.checked_add(payload_len)?;
                let payload_bytes = records_payload.get(payload_relative_offset..payload_end)?;
                let child_count = read_u16_at(payload_bytes, 0)?;
                let saved_primary_child_byte = read_u8_at(payload_bytes, 2)?;
                let first_payload_dword = read_u32_at(payload_bytes, 0)?;
                let first_name_tag_relative_offset = (0usize..=16usize).find(|offset| {
                    read_u16_at(payload_bytes, *offset) == Some(SAVE_REGION_RECORD_NAME_TAG)
                });
                let (first_primary_name, first_secondary_name, first_tertiary_name) =
                    first_name_tag_relative_offset
                        .and_then(|relative_offset| {
                            let name_payload =
                                payload_bytes.get(relative_offset.checked_add(4)?..)?;
                            parse_save_len_prefixed_ascii_name_triplet(name_payload)
                        })
                        .unwrap_or_default();
                rows.push(LiveEntryPreludeRow {
                    live_entry_id,
                    payload_relative_offset,
                    child_count,
                    saved_primary_child_byte,
                    first_payload_dword,
                    first_name_tag_relative_offset,
                    first_primary_name: (!first_primary_name.is_empty())
                        .then_some(first_primary_name),
                    first_secondary_name: (!first_secondary_name.is_empty())
                        .then_some(first_secondary_name),
                    first_tertiary_name,
                });
                cursor = payload_end;
            }
            let payload_relative_offset_monotonic = rows.windows(2).all(|window| {
                window[0].payload_relative_offset < window[1].payload_relative_offset
            });
            let mut child_count_counts = BTreeMap::<u16, usize>::new();
            let mut saved_primary_child_byte_counts = BTreeMap::<u8, usize>::new();
            let mut first_name_tag_relative_offset_counts = BTreeMap::<usize, usize>::new();
            for row in &rows {
                *child_count_counts.entry(row.child_count).or_default() += 1;
                *saved_primary_child_byte_counts
                    .entry(row.saved_primary_child_byte)
                    .or_default() += 1;
                if let Some(first_name_tag_relative_offset) = row.first_name_tag_relative_offset {
                    *first_name_tag_relative_offset_counts
                        .entry(first_name_tag_relative_offset)
                        .or_default() += 1;
                }
            }
            let dominant_child_count = child_count_counts
                .iter()
                .max_by(|(left_key, left_count), (right_key, right_count)| {
                    left_count
                        .cmp(right_count)
                        .then_with(|| right_key.cmp(left_key))
                })
                .map(|(value, count)| (*value, *count));
            let dominant_saved_primary_child_byte = saved_primary_child_byte_counts
                .iter()
                .max_by(|(left_key, left_count), (right_key, right_count)| {
                    left_count
                        .cmp(right_count)
                        .then_with(|| right_key.cmp(left_key))
                })
                .map(|(value, count)| (*value, *count));
            let dominant_first_name_tag_relative_offset = first_name_tag_relative_offset_counts
                .iter()
                .max_by(|(left_key, left_count), (right_key, right_count)| {
                    left_count
                        .cmp(right_count)
                        .then_with(|| right_key.cmp(left_key))
                })
                .map(|(value, count)| (*value, *count));
            Some(
                SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary {
                    live_entry_directory_row_count: rows.len(),
                    decoded_live_entry_id_count: live_entry_ids.len(),
                    payload_relative_offset_monotonic,
                    rows_with_payload_pointer_inside_records_span: rows.len(),
                    rows_with_zero_child_count: rows
                        .iter()
                        .filter(|row| row.child_count == 0)
                        .count(),
                    rows_with_nonzero_child_count: rows
                        .iter()
                        .filter(|row| row.child_count != 0)
                        .count(),
                    rows_with_first_name_tag_after_prelude: rows
                        .iter()
                        .filter(|row| row.first_name_tag_relative_offset.is_some())
                        .count(),
                    rows_with_first_name_tag_at_offset_3: rows
                        .iter()
                        .filter(|row| row.first_name_tag_relative_offset == Some(3))
                        .count(),
                    unique_child_count_values: child_count_counts.keys().copied().collect(),
                    unique_first_name_tag_relative_offsets: first_name_tag_relative_offset_counts
                        .keys()
                        .copied()
                        .collect(),
                    dominant_child_count: dominant_child_count.map(|(value, _)| value),
                    dominant_child_count_count: dominant_child_count
                        .map(|(_, count)| count)
                        .unwrap_or_default(),
                    dominant_saved_primary_child_byte: dominant_saved_primary_child_byte
                        .map(|(value, _)| value),
                    dominant_saved_primary_child_byte_hex: dominant_saved_primary_child_byte
                        .map(|(value, _)| format!("0x{value:02x}")),
                    dominant_saved_primary_child_byte_count: dominant_saved_primary_child_byte
                        .map(|(_, count)| count)
                        .unwrap_or_default(),
                    dominant_first_name_tag_relative_offset:
                        dominant_first_name_tag_relative_offset.map(|(value, _)| value),
                    dominant_first_name_tag_relative_offset_count:
                        dominant_first_name_tag_relative_offset
                            .map(|(_, count)| count)
                            .unwrap_or_default(),
                    sample_rows: rows
                        .iter()
                        .take(8)
                        .enumerate()
                        .map(|(sample_index, row)| {
                            SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSample {
                                sample_index,
                                live_entry_id: row.live_entry_id,
                                payload_relative_offset: row.payload_relative_offset as u32,
                                payload_relative_offset_hex: format!(
                                    "0x{:08x}",
                                    row.payload_relative_offset
                                ),
                                payload_relative_to_records: row.payload_relative_offset,
                                child_count: row.child_count,
                                child_count_hex: format!("0x{:04x}", row.child_count),
                                saved_primary_child_byte: row.saved_primary_child_byte,
                                saved_primary_child_byte_hex: format!(
                                    "0x{:02x}",
                                    row.saved_primary_child_byte
                                ),
                                first_payload_dword_hex: format!(
                                    "0x{:08x}",
                                    row.first_payload_dword
                                ),
                                first_name_tag_relative_offset: row.first_name_tag_relative_offset,
                                first_primary_name: row.first_primary_name.clone(),
                                first_secondary_name: row.first_secondary_name.clone(),
                                first_tertiary_name: row.first_tertiary_name.clone(),
                            }
                        })
                        .collect(),
                },
            )
        })
}
