use crate::inspect::smp::events::*;
use crate::inspect::smp::map_title::is_ascii_preview_byte;

pub(in crate::inspect::smp) fn try_parse_nondirect_event_runtime_record_summaries(
    records_payload: &[u8],
    records_payload_offset: usize,
    live_entry_ids: &[u32],
) -> Option<Vec<SmpLoadedPackedEventRecordSummary>> {
    let marker_offsets =
        find_u32_le_offsets(records_payload, PACKED_EVENT_REAL_CONDITION_MARKER as u32);
    if marker_offsets.len() != live_entry_ids.len() || marker_offsets.first().copied() != Some(0) {
        return None;
    }

    let mut record_offsets = marker_offsets;
    record_offsets.push(records_payload.len());
    let mut records = Vec::with_capacity(live_entry_ids.len());

    for (record_index, live_entry_id) in live_entry_ids.iter().copied().enumerate() {
        let start = *record_offsets.get(record_index)?;
        let end = *record_offsets.get(record_index + 1)?;
        let record_body = records_payload.get(start..end)?;
        let record = parse_nondirect_event_runtime_record_summary(
            record_body,
            records_payload_offset + start,
            record_index,
            live_entry_id,
        )
        .or_else(|| {
            build_nondirect_event_runtime_record_summary_from_signatures(
                record_body,
                records_payload_offset + start,
                record_index,
                live_entry_id,
            )
        })?;
        records.push(record);
    }

    Some(records)
}

pub(in crate::inspect::smp) fn parse_nondirect_event_runtime_record_summary(
    record_body: &[u8],
    payload_offset: usize,
    record_index: usize,
    live_entry_id: u32,
) -> Option<SmpLoadedPackedEventRecordSummary> {
    let mut cursor = 0usize;
    if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_CONDITION_MARKER as u32 {
        return None;
    }
    cursor += 4;

    let standalone_condition_row_count = usize::try_from(read_u32_at(record_body, cursor)?).ok()?;
    cursor += 4;
    let mut standalone_condition_rows = Vec::with_capacity(standalone_condition_row_count);
    for row_index in 0..standalone_condition_row_count {
        let remaining_minimum = standalone_condition_row_count
            .checked_sub(row_index + 1)?
            .checked_mul(PACKED_EVENT_NONDIRECT_CONDITION_ROW_SERIALIZED_LEN)?
            .checked_add(4)?
            .checked_add(PACKED_EVENT_REAL_GROUP_COUNT.checked_mul(4)?)?
            .checked_add(4)?;
        let (row, consumed_len) = parse_nondirect_condition_row_summary(
            record_body.get(cursor..)?,
            row_index,
            remaining_minimum,
        )?;
        standalone_condition_rows.push(row);
        cursor += consumed_len;
    }

    let grouped_marker_relative_offset = cursor;
    if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32 {
        return None;
    }
    cursor += 4;

    let mut grouped_effect_row_counts = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
    let mut grouped_effect_rows = Vec::new();
    for group_index in 0..PACKED_EVENT_REAL_GROUP_COUNT {
        let group_row_count = usize::try_from(read_u32_at(record_body, cursor)?).ok()?;
        cursor += 4;
        grouped_effect_row_counts.push(group_row_count);
        for row_index in 0..group_row_count {
            let remaining_groups_minimum = (group_row_count - row_index - 1)
                .checked_mul(PACKED_EVENT_NONDIRECT_GROUPED_EFFECT_ROW_SERIALIZED_LEN)?
                .checked_add((PACKED_EVENT_REAL_GROUP_COUNT - group_index - 1).checked_mul(4)?)?
                .checked_add(4)?;
            let (row, consumed_len) = parse_nondirect_grouped_effect_row_summary(
                record_body.get(cursor..)?,
                group_index,
                row_index,
                remaining_groups_minimum,
            )?;
            grouped_effect_rows.push(row);
            cursor += consumed_len;
        }
    }

    let end_marker_relative_offset = cursor;
    if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32 {
        return None;
    }
    cursor += 4;
    if cursor != record_body.len() {
        return None;
    }

    let head_signature_words = read_u16_window(record_body, 0, 18);
    let post_group_signature_words =
        read_u16_window(record_body, grouped_marker_relative_offset + 4, 12);
    let ascii_preview_before_grouped_marker = record_body
        .get(..grouped_marker_relative_offset)
        .map(ascii_preview);

    let mut notes = vec![
        "decoded from compact non-direct 0x4e99/0x4e9a/0x4e9b map-bundle row framing recovered from the paired 0x433060/0x430d70 writer strip and 0x433130/0x42db20 loader strip".to_string(),
        format!(
            "compact signature family = {}",
            compact_nondirect_signature_family(
                Some(grouped_marker_relative_offset),
                &head_signature_words,
                &post_group_signature_words,
            )
        ),
        format!(
            "head signature u16 words = {}",
            format_u16_word_signature(&head_signature_words)
        ),
        format!(
            "grouped-effect marker 0x4eb8 at relative offset +0x{grouped_marker_relative_offset:x}"
        ),
        format!(
            "row terminator marker 0x4eb9 at relative offset +0x{end_marker_relative_offset:x}"
        ),
    ];
    if !post_group_signature_words.is_empty() {
        notes.push(format!(
            "post-group signature u16 words = {}",
            format_u16_word_signature(&post_group_signature_words)
        ));
    }
    if let Some(preview) = ascii_preview_before_grouped_marker {
        notes.push(format!("ascii preview before grouped marker = {preview}"));
    }
    notes.push(format!(
        "compact non-direct grouped row counts by group = {:?}",
        grouped_effect_row_counts
    ));
    notes.push(
        "the compact non-direct row body reconstructs standalone/grouped rows only; the separate 0x42e050 full-event clone helper is the nearby owner that copies text bands plus control lane [event+0x7ee..0x80f] between live runtime-event rows".to_string(),
    );
    notes.push(
        "direct disassembly of 0x0042db20 now grounds that absence too: this loader allocates 0x1e-byte standalone-condition nodes and 0x28-byte grouped-row nodes from the 0x4e9a slice, but does not materialize the compact control lane [event+0x7ee..0x80f] or a trigger-kind byte for this non-direct row family".to_string(),
    );
    notes.push(
        "the adjacent deep-copy seam is bounded too: current direct caller census shows 0x0042e050 reached from editor duplication at 0x004dba23, not from the ordinary 0x00433130 load path, so this non-direct row family is not currently grounded to inherit trigger/control bytes during restore through that helper".to_string(),
    );
    notes.push(
        "the first non-editor positive control-lane writer is bounded away from ordinary restore too: 0x00430b50 allocates a fresh live runtime-effect row through 0x00432ea0 -> 0x0042d5a0, seeds [event+0x7ef] to 2 or 3 plus adjacent control bytes, and is only reached from the 0x004323a0 follow-on service strip rather than the 0x00433130 nondirect load path".to_string(),
    );

    let decoded_conditions = decode_real_condition_rows(&standalone_condition_rows, None);

    Some(SmpLoadedPackedEventRecordSummary {
        record_index,
        live_entry_id,
        payload_offset: Some(payload_offset),
        payload_len: Some(cursor),
        decode_status: "parity_only".to_string(),
        payload_family: "real_packed_nondirect_compact_v1".to_string(),
        trigger_kind: None,
        active: None,
        marks_collection_dirty: None,
        one_shot: None,
        compact_control: None,
        text_bands: Vec::new(),
        standalone_condition_row_count,
        standalone_condition_rows,
        negative_sentinel_scope: None,
        grouped_effect_row_counts,
        grouped_effect_rows,
        decoded_conditions,
        decoded_actions: Vec::new(),
        executable_import_ready: false,
        notes,
    })
}

pub(in crate::inspect::smp) fn build_nondirect_event_runtime_record_summary_from_signatures(
    record_body: &[u8],
    payload_offset: usize,
    record_index: usize,
    live_entry_id: u32,
) -> Option<SmpLoadedPackedEventRecordSummary> {
    let grouped_marker_relative_offset =
        find_u32_le_offsets(record_body, PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32)
            .into_iter()
            .next();
    let end_marker_relative_offset = find_u32_le_offsets(
        record_body,
        PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32,
    )
    .into_iter()
    .next();
    let head_signature_words = read_u16_window(record_body, 0, 18);
    let post_group_signature_words = grouped_marker_relative_offset
        .map(|offset| offset + 4)
        .map(|offset| read_u16_window(record_body, offset, 12))
        .unwrap_or_default();
    let ascii_preview_before_grouped_marker = grouped_marker_relative_offset
        .and_then(|offset| record_body.get(..offset).map(ascii_preview));

    let mut notes = vec![
        "decoded from non-direct 0x4e99/0x4e9a/0x4e9b map-bundle row segmentation using 0x526f-delimited slices".to_string(),
        format!(
            "compact signature family = {}",
            compact_nondirect_signature_family(
                grouped_marker_relative_offset,
                &head_signature_words,
                &post_group_signature_words,
            )
        ),
        format!(
            "head signature u16 words = {}",
            format_u16_word_signature(&head_signature_words)
        ),
    ];
    if let Some(offset) = grouped_marker_relative_offset {
        notes.push(format!(
            "grouped-effect marker 0x4eb8 at relative offset +0x{offset:x}"
        ));
        if !post_group_signature_words.is_empty() {
            notes.push(format!(
                "post-group signature u16 words = {}",
                format_u16_word_signature(&post_group_signature_words)
            ));
        }
    }
    if let Some(offset) = end_marker_relative_offset {
        notes.push(format!(
            "row terminator marker 0x4eb9 at relative offset +0x{offset:x}"
        ));
    }
    if let Some(preview) = ascii_preview_before_grouped_marker {
        notes.push(format!("ascii preview before grouped marker = {preview}"));
    }

    Some(SmpLoadedPackedEventRecordSummary {
        record_index,
        live_entry_id,
        payload_offset: Some(payload_offset),
        payload_len: Some(record_body.len()),
        decode_status: "compact_nondirect_parity_only".to_string(),
        payload_family: "real_packed_nondirect_compact_v1".to_string(),
        trigger_kind: None,
        active: None,
        marks_collection_dirty: None,
        one_shot: None,
        compact_control: None,
        text_bands: Vec::new(),
        standalone_condition_row_count: 0,
        standalone_condition_rows: Vec::new(),
        negative_sentinel_scope: None,
        grouped_effect_row_counts: vec![0, 0, 0, 0],
        grouped_effect_rows: Vec::new(),
        decoded_conditions: Vec::new(),
        decoded_actions: Vec::new(),
        executable_import_ready: false,
        notes,
    })
}

pub(in crate::inspect::smp) fn parse_nondirect_condition_row_summary(
    record_body: &[u8],
    row_index: usize,
    remaining_minimum: usize,
) -> Option<(SmpLoadedPackedEventConditionRowSummary, usize)> {
    let mut cursor = 0usize;
    let mut row_bytes = vec![0u8; PACKED_EVENT_REAL_CONDITION_ROW_LEN];
    row_bytes
        .get_mut(0..4)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes[4] = read_u8_at(record_body, cursor)?;
    cursor += 1;
    row_bytes
        .get_mut(5..9)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(9..13)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes[0x0d] = read_u8_at(record_body, cursor)?;
    cursor += 1;
    row_bytes
        .get_mut(0x0e..0x12)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x12..0x16)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    let candidate_name =
        maybe_parse_nondirect_optional_name_block(record_body, &mut cursor, remaining_minimum)?;
    let mut row = parse_real_condition_row_summary(&row_bytes, row_index, candidate_name)?;
    row.notes.push(
        "condition row reconstructed from the compact non-direct serializer fields under 0x430e80"
            .to_string(),
    );
    Some((row, cursor))
}

pub(in crate::inspect::smp) fn parse_nondirect_grouped_effect_row_summary(
    record_body: &[u8],
    group_index: usize,
    row_index: usize,
    remaining_minimum: usize,
) -> Option<(SmpLoadedPackedEventGroupedEffectRowSummary, usize)> {
    let mut cursor = 0usize;
    let mut row_bytes = vec![0u8; PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN];
    row_bytes
        .get_mut(0..4)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(4..8)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes[8] = read_u8_at(record_body, cursor)?;
    cursor += 1;
    row_bytes
        .get_mut(9..13)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x0d..0x11)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x11..0x15)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x12..0x16)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x14..0x18)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x16..0x1a)?
        .copy_from_slice(record_body.get(cursor..cursor + 4)?);
    cursor += 4;
    row_bytes
        .get_mut(0x18..0x24)?
        .copy_from_slice(record_body.get(cursor..cursor + 12)?);
    cursor += 12;
    let locomotive_name =
        maybe_parse_nondirect_optional_name_block(record_body, &mut cursor, remaining_minimum)?;
    let mut row =
        parse_real_grouped_effect_row_summary(&row_bytes, group_index, row_index, locomotive_name)?;
    row.notes.push(
        "grouped effect row reconstructed from the compact non-direct serializer fields under 0x430f68"
            .to_string(),
    );
    Some((row, cursor))
}

pub(in crate::inspect::smp) fn maybe_parse_nondirect_optional_name_block(
    record_body: &[u8],
    cursor: &mut usize,
    remaining_minimum: usize,
) -> Option<Option<String>> {
    if record_body.len() < *cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN {
        return Some(None);
    }
    if record_body.len()
        < *cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN + remaining_minimum
    {
        return Some(None);
    }
    let block =
        record_body.get(*cursor..*cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN)?;
    let name = read_ascii_c_string_at(block, 0, PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN);
    let Some(name) = name.filter(|name| {
        !name.is_empty()
            && block
                .iter()
                .copied()
                .all(|byte| byte == 0 || is_ascii_preview_byte(byte))
    }) else {
        return Some(None);
    };
    *cursor += PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN;
    Some(Some(name))
}
