use std::collections::BTreeSet;

use super::super::blockers::classify_real_grouped_company_targets;
use super::decode::{
    lowered_record_decoded_actions, lowered_record_decoded_conditions,
    smp_packed_record_to_runtime_event_record,
};
use super::import_outcome::determine_packed_event_import_outcome;
use crate::documents::RuntimeImportContext;
use crate::event::packed::{
    RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
    RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
    RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
    RuntimePackedEventTextBandSummary,
};
use crate::event::records::RuntimeEventRecord;
use crate::inspect::smp::events::{
    SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
    SmpLoadedPackedEventTextBandSummary,
};
use crate::inspect::smp::save_load::SmpLoadedSaveSlice;
use crate::state::RuntimeCargoCatalogEntry;

pub(crate) fn project_packed_event_collection(
    save_slice: &SmpLoadedSaveSlice,
    runtime_context: &RuntimeImportContext,
    cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> Result<
    (
        Option<RuntimePackedEventCollectionSummary>,
        Vec<RuntimeEventRecord>,
    ),
    String,
> {
    let Some(summary) = save_slice.event_runtime_collection.as_ref() else {
        return Ok((None, Vec::new()));
    };

    let mut imported_runtime_records = Vec::new();
    let mut imported_record_ids = BTreeSet::new();
    for record in &summary.records {
        if let Some(import_result) =
            smp_packed_record_to_runtime_event_record(record, runtime_context)
        {
            let runtime_record = import_result?;
            imported_record_ids.insert(record.live_entry_id);
            imported_runtime_records.push(runtime_record);
        }
    }

    let records = summary
        .records
        .iter()
        .map(|record| {
            runtime_packed_event_record_summary_from_smp(
                record,
                runtime_context,
                cargo_catalog,
                imported_record_ids.contains(&record.live_entry_id),
            )
        })
        .collect::<Vec<_>>();

    Ok((
        Some(RuntimePackedEventCollectionSummary {
            source_kind: summary.source_kind.clone(),
            mechanism_family: summary.mechanism_family.clone(),
            mechanism_confidence: summary.mechanism_confidence.clone(),
            container_profile_family: summary.container_profile_family.clone(),
            packed_state_version: summary.packed_state_version,
            packed_state_version_hex: summary.packed_state_version_hex.clone(),
            live_id_bound: summary.live_id_bound,
            live_record_count: summary.live_record_count,
            live_entry_ids: summary.live_entry_ids.clone(),
            decoded_record_count: records
                .iter()
                .filter(|record| record.decode_status != "unsupported_framing")
                .count(),
            imported_runtime_record_count: imported_runtime_records.len(),
            records,
        }),
        imported_runtime_records,
    ))
}

pub(crate) fn runtime_packed_event_record_summary_from_smp(
    record: &SmpLoadedPackedEventRecordSummary,
    runtime_context: &RuntimeImportContext,
    cargo_catalog: &[RuntimeCargoCatalogEntry],
    imported: bool,
) -> RuntimePackedEventRecordSummary {
    let lowered_decoded_conditions = lowered_record_decoded_conditions(record, runtime_context)
        .unwrap_or_else(|_| record.decoded_conditions.clone());
    let lowered_decoded_actions = lowered_record_decoded_actions(record, runtime_context)
        .unwrap_or_else(|_| record.decoded_actions.clone());
    RuntimePackedEventRecordSummary {
        record_index: record.record_index,
        live_entry_id: record.live_entry_id,
        payload_offset: record.payload_offset,
        payload_len: record.payload_len,
        decode_status: record.decode_status.clone(),
        payload_family: record.payload_family.clone(),
        trigger_kind: record.trigger_kind,
        active: record.active,
        marks_collection_dirty: record.marks_collection_dirty,
        one_shot: record.one_shot,
        compact_control: record
            .compact_control
            .as_ref()
            .map(runtime_packed_event_compact_control_summary_from_smp),
        text_bands: record
            .text_bands
            .iter()
            .map(runtime_packed_event_text_band_summary_from_smp)
            .collect(),
        standalone_condition_row_count: record.standalone_condition_row_count,
        standalone_condition_rows: record
            .standalone_condition_rows
            .iter()
            .map(|row| runtime_packed_event_condition_row_summary_from_smp(row, cargo_catalog))
            .collect(),
        negative_sentinel_scope: record
            .negative_sentinel_scope
            .as_ref()
            .map(runtime_packed_event_negative_sentinel_scope_summary_from_smp),
        grouped_effect_row_counts: record.grouped_effect_row_counts.clone(),
        grouped_effect_rows: record
            .grouped_effect_rows
            .iter()
            .map(|row| runtime_packed_event_grouped_effect_row_summary_from_smp(row, cargo_catalog))
            .collect(),
        grouped_company_targets: classify_real_grouped_company_targets(record),
        decoded_conditions: lowered_decoded_conditions,
        decoded_actions: lowered_decoded_actions,
        executable_import_ready: record.executable_import_ready,
        import_outcome: Some(determine_packed_event_import_outcome(
            record,
            runtime_context,
            imported,
        )),
        notes: record.notes.clone(),
    }
}

pub(crate) fn runtime_packed_event_negative_sentinel_scope_summary_from_smp(
    scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
) -> RuntimePackedEventNegativeSentinelScopeSummary {
    RuntimePackedEventNegativeSentinelScopeSummary {
        company_test_scope: scope.company_test_scope,
        player_test_scope: scope.player_test_scope,
        territory_scope_selector_is_0x63: scope.territory_scope_selector_is_0x63,
        source_row_indexes: scope.source_row_indexes.clone(),
    }
}

pub(crate) fn runtime_packed_event_compact_control_summary_from_smp(
    control: &crate::inspect::smp::events::SmpLoadedPackedEventCompactControlSummary,
) -> RuntimePackedEventCompactControlSummary {
    RuntimePackedEventCompactControlSummary {
        mode_byte_0x7ef: control.mode_byte_0x7ef,
        primary_selector_0x7f0: control.primary_selector_0x7f0,
        grouped_mode_0x7f4: control.grouped_mode_0x7f4,
        one_shot_header_0x7f5: control.one_shot_header_0x7f5,
        modifier_flag_0x7f9: control.modifier_flag_0x7f9,
        modifier_flag_0x7fa: control.modifier_flag_0x7fa,
        grouped_target_scope_ordinals_0x7fb: control.grouped_target_scope_ordinals_0x7fb.clone(),
        grouped_scope_checkboxes_0x7ff: control.grouped_scope_checkboxes_0x7ff.clone(),
        summary_toggle_0x800: control.summary_toggle_0x800,
        grouped_territory_selectors_0x80f: control.grouped_territory_selectors_0x80f.clone(),
    }
}

pub(crate) fn runtime_packed_event_text_band_summary_from_smp(
    band: &SmpLoadedPackedEventTextBandSummary,
) -> RuntimePackedEventTextBandSummary {
    RuntimePackedEventTextBandSummary {
        label: band.label.clone(),
        packed_len: band.packed_len,
        present: band.present,
        preview: band.preview.clone(),
    }
}

pub(crate) fn runtime_packed_event_condition_row_summary_from_smp(
    row: &crate::inspect::smp::events::SmpLoadedPackedEventConditionRowSummary,
    cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> RuntimePackedEventConditionRowSummary {
    let cargo_entry = row
        .recovered_cargo_slot
        .and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot));
    RuntimePackedEventConditionRowSummary {
        row_index: row.row_index,
        raw_condition_id: row.raw_condition_id,
        subtype: row.subtype,
        flag_bytes: row.flag_bytes.clone(),
        candidate_name: row.candidate_name.clone(),
        comparator: row.comparator.clone(),
        metric: row.metric.clone(),
        semantic_family: row.semantic_family.clone(),
        semantic_preview: row.semantic_preview.clone(),
        requires_candidate_name_binding: row.requires_candidate_name_binding,
        recovered_cargo_slot: row.recovered_cargo_slot,
        recovered_cargo_class: cargo_entry
            .map(|entry| format!("{:?}", entry.cargo_class).to_ascii_lowercase())
            .map(|value| value.replace("farmmine", "farm_mine"))
            .or_else(|| row.recovered_cargo_class.clone()),
        recovered_cargo_label: cargo_entry
            .map(|entry| entry.label.clone())
            .or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)),
        recovered_cargo_supplied_token_stem: cargo_entry
            .and_then(|entry| entry.supplied_token_stem.clone()),
        recovered_cargo_demanded_token_stem: cargo_entry
            .and_then(|entry| entry.demanded_token_stem.clone()),
        notes: row.notes.clone(),
    }
}

pub(crate) fn runtime_packed_event_grouped_effect_row_summary_from_smp(
    row: &crate::inspect::smp::events::SmpLoadedPackedEventGroupedEffectRowSummary,
    cargo_catalog: &[RuntimeCargoCatalogEntry],
) -> RuntimePackedEventGroupedEffectRowSummary {
    let cargo_entry = row
        .recovered_cargo_slot
        .and_then(|slot| cargo_catalog.iter().find(|entry| entry.slot_id == slot));
    RuntimePackedEventGroupedEffectRowSummary {
        group_index: row.group_index,
        row_index: row.row_index,
        descriptor_id: row.descriptor_id,
        descriptor_label: row.descriptor_label.clone(),
        target_mask_bits: row.target_mask_bits,
        parameter_family: row.parameter_family.clone(),
        grouped_target_subject: row.grouped_target_subject.clone(),
        grouped_target_scope: row.grouped_target_scope.clone(),
        opcode: row.opcode,
        raw_scalar_value: row.raw_scalar_value,
        value_byte_0x09: row.value_byte_0x09,
        value_dword_0x0d: row.value_dword_0x0d,
        value_byte_0x11: row.value_byte_0x11,
        value_byte_0x12: row.value_byte_0x12,
        value_word_0x14: row.value_word_0x14,
        value_word_0x16: row.value_word_0x16,
        row_shape: row.row_shape.clone(),
        semantic_family: row.semantic_family.clone(),
        semantic_preview: row.semantic_preview.clone(),
        recovered_cargo_slot: row.recovered_cargo_slot,
        recovered_cargo_class: cargo_entry
            .map(|entry| format!("{:?}", entry.cargo_class).to_ascii_lowercase())
            .map(|value| value.replace("farmmine", "farm_mine"))
            .or_else(|| row.recovered_cargo_class.clone()),
        recovered_cargo_label: cargo_entry
            .map(|entry| entry.label.clone())
            .or_else(|| row.recovered_cargo_label.clone())
            .or_else(|| row.recovered_cargo_slot.map(default_cargo_slot_label)),
        recovered_cargo_supplied_token_stem: cargo_entry
            .and_then(|entry| entry.supplied_token_stem.clone()),
        recovered_cargo_demanded_token_stem: cargo_entry
            .and_then(|entry| entry.demanded_token_stem.clone()),
        recovered_locomotive_id: row.recovered_locomotive_id,
        locomotive_name: row.locomotive_name.clone(),
        notes: row.notes.clone(),
    }
}

pub(crate) fn default_cargo_slot_label(slot: u32) -> String {
    format!("Cargo Production Slot {slot}")
}
