use super::common::{
    SPECIAL_CONDITION_COUNT, SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT, SPECIAL_CONDITION_LABELS,
    SPECIAL_CONDITIONS_OFFSET, collect_special_conditions_input_paths,
    parse_special_condition_slot_index,
};
use crate::app::runtime_compare::{classify_candidate_table_header_profile, read_u32_le};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

use serde::Serialize;

#[derive(Debug, Clone, Serialize)]
pub(crate) struct RuntimeSpecialConditionsScanSample {
    pub(crate) path: String,
    pub(crate) profile_family: String,
    pub(crate) source_kind: String,
    pub(crate) enabled_visible_count: usize,
    pub(crate) enabled_visible_labels: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeSpecialConditionsSlotSummary {
    pub(crate) slot_index: u8,
    pub(crate) label: String,
    pub(crate) file_count_enabled: usize,
    pub(crate) sample_paths: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(crate) struct RuntimeSpecialConditionsScanReport {
    pub(crate) root_path: String,
    pub(crate) file_count: usize,
    pub(crate) files_with_probe_count: usize,
    pub(crate) files_with_any_enabled_count: usize,
    pub(crate) skipped_file_count: usize,
    pub(crate) enabled_slot_summaries: Vec<RuntimeSpecialConditionsSlotSummary>,
    pub(crate) sample_files_with_any_enabled: Vec<RuntimeSpecialConditionsScanSample>,
}

pub(crate) fn scan_special_conditions(root_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let mut candidate_paths = Vec::new();
    collect_special_conditions_input_paths(root_path, &mut candidate_paths)?;

    let file_count = candidate_paths.len();
    let mut samples = Vec::new();
    let mut skipped_file_count = 0usize;
    for path in candidate_paths {
        match load_special_conditions_scan_sample(&path) {
            Ok(sample) => samples.push(sample),
            Err(_) => skipped_file_count += 1,
        }
    }

    let files_with_probe_count = samples.len();
    let sample_files_with_any_enabled = samples
        .iter()
        .filter(|sample| sample.enabled_visible_count != 0)
        .cloned()
        .collect::<Vec<_>>();
    let files_with_any_enabled_count = sample_files_with_any_enabled.len();

    let mut grouped = BTreeMap::<(u8, String), Vec<String>>::new();
    for sample in &samples {
        for label in &sample.enabled_visible_labels {
            if let Some(slot_index) = parse_special_condition_slot_index(label) {
                grouped
                    .entry((slot_index, label.clone()))
                    .or_default()
                    .push(sample.path.clone());
            }
        }
    }

    let enabled_slot_summaries = grouped
        .into_iter()
        .map(
            |((slot_index, label), paths)| RuntimeSpecialConditionsSlotSummary {
                slot_index,
                label,
                file_count_enabled: paths.len(),
                sample_paths: paths.into_iter().take(12).collect(),
            },
        )
        .collect::<Vec<_>>();

    let report = RuntimeSpecialConditionsScanReport {
        root_path: root_path.display().to_string(),
        file_count,
        files_with_probe_count,
        files_with_any_enabled_count,
        skipped_file_count,
        enabled_slot_summaries,
        sample_files_with_any_enabled,
    };
    println!("{}", serde_json::to_string_pretty(&report)?);
    Ok(())
}

pub(crate) fn load_special_conditions_scan_sample(
    smp_path: &Path,
) -> Result<RuntimeSpecialConditionsScanSample, Box<dyn std::error::Error>> {
    let bytes = fs::read(smp_path)?;
    let table_len = SPECIAL_CONDITION_COUNT * 4;
    let table_end = SPECIAL_CONDITIONS_OFFSET
        .checked_add(table_len)
        .ok_or("special-conditions table overflow")?;
    if bytes.len() < table_end {
        return Err(format!(
            "{} is too small for the fixed special-conditions table",
            smp_path.display()
        )
        .into());
    }

    let hidden_sentinel = read_u32_le(
        &bytes,
        SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4,
    )
    .ok_or_else(|| {
        format!(
            "{} is missing the hidden special-condition sentinel",
            smp_path.display()
        )
    })?;
    if hidden_sentinel != 1 {
        return Err(format!(
            "{} does not match the fixed special-conditions table sentinel",
            smp_path.display()
        )
        .into());
    }

    let enabled_visible_labels = (0..SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT)
        .filter_map(|slot_index| {
            let value = read_u32_le(&bytes, SPECIAL_CONDITIONS_OFFSET + slot_index * 4)?;
            (value != 0).then(|| {
                format!(
                    "slot {}: {}",
                    slot_index, SPECIAL_CONDITION_LABELS[slot_index]
                )
            })
        })
        .collect::<Vec<_>>();

    let extension = smp_path
        .extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_ascii_lowercase())
        .unwrap_or_default();
    let profile_family = classify_candidate_table_header_profile(Some(extension.clone()), &bytes);
    let source_kind = match extension.as_str() {
        "gmp" => "map-fixed-special-conditions-range",
        "gms" => "save-fixed-special-conditions-range",
        "gmx" => "sandbox-fixed-special-conditions-range",
        _ => "fixed-special-conditions-range",
    }
    .to_string();

    Ok(RuntimeSpecialConditionsScanSample {
        path: smp_path.display().to_string(),
        profile_family,
        source_kind,
        enabled_visible_count: enabled_visible_labels.len(),
        enabled_visible_labels,
    })
}
