use std::collections::{BTreeMap, BTreeSet};

use crate::inspect::smp::services::*;
use crate::inspect::smp::world::SmpSaveCompanyChairmanAnalysisReport;

pub(in crate::inspect::smp) fn build_service_trace_branch_status(
    branch_name: &str,
    status: &str,
    grounded_inputs: &[&str],
    blocking_inputs: &[&str],
    candidate_consumers: &[&str],
    notes: &[&str],
) -> SmpServiceTraceBranchStatus {
    SmpServiceTraceBranchStatus {
        branch_name: branch_name.to_string(),
        status: status.to_string(),
        grounded_inputs: grounded_inputs
            .iter()
            .map(|value| value.to_string())
            .collect(),
        blocking_inputs: blocking_inputs
            .iter()
            .map(|value| value.to_string())
            .collect(),
        candidate_consumers: candidate_consumers
            .iter()
            .map(|value| value.to_string())
            .collect(),
        notes: notes.iter().map(|value| value.to_string()).collect(),
    }
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_payloads(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructureProfilePayloadSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut grouped =
        BTreeMap::<(String, String), (usize, BTreeSet<String>, BTreeSet<String>)>::new();
    for entry in &triplets.entries {
        let grouped_entry = grouped
            .entry((
                entry.profile_payload_dword_hex.clone(),
                entry.profile_status_kind.clone(),
            ))
            .or_insert_with(|| (0, BTreeSet::new(), BTreeSet::new()));
        grouped_entry.0 += 1;
        if grouped_entry.1.len() < 4 {
            grouped_entry.1.insert(entry.primary_name.clone());
        }
        if grouped_entry.2.len() < 4 {
            grouped_entry.2.insert(entry.secondary_name.clone());
        }
    }
    let mut summaries = grouped
        .into_iter()
        .map(
            |(
                (profile_payload_dword_hex, profile_status_kind),
                (count, primary_names, secondary_names),
            )| {
                SmpSavePlacedStructureProfilePayloadSummaryEntry {
                    profile_payload_dword_hex,
                    profile_status_kind,
                    count,
                    sample_primary_names: primary_names.into_iter().collect(),
                    sample_secondary_names: secondary_names.into_iter().collect(),
                }
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| {
                left.profile_payload_dword_hex
                    .cmp(&right.profile_payload_dword_hex)
            })
            .then_with(|| left.profile_status_kind.cmp(&right.profile_status_kind))
    });
    summaries.truncate(8);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_payload_deltas(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut payload_values = triplets
        .entries
        .iter()
        .map(|entry| entry.profile_payload_dword)
        .collect::<Vec<_>>();
    payload_values.sort_unstable();
    payload_values.dedup();
    let mut delta_counts = BTreeMap::<u32, usize>::new();
    for window in payload_values.windows(2) {
        let delta = window[1].wrapping_sub(window[0]);
        *delta_counts.entry(delta).or_insert(0) += 1;
    }
    let mut summaries = delta_counts
        .into_iter()
        .map(
            |(delta, count)| SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry {
                delta_hex: format!("0x{delta:08x}"),
                count,
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.delta_hex.cmp(&right.delta_hex))
    });
    summaries.truncate(6);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_footer_padding(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructureProfileFooterPaddingSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut grouped = BTreeMap::<usize, (usize, BTreeSet<String>)>::new();
    for entry in &triplets.entries {
        let grouped_entry = grouped
            .entry(entry.profile_pre_footer_padding_len)
            .or_insert_with(|| (0, BTreeSet::new()));
        grouped_entry.0 += 1;
        if grouped_entry.1.len() < 4 {
            grouped_entry
                .1
                .insert(entry.profile_pre_footer_padding_hex_bytes.join(","));
        }
    }
    let mut summaries = grouped
        .into_iter()
        .map(|(padding_len, (count, sample_hex_bytes))| {
            SmpSavePlacedStructureProfileFooterPaddingSummaryEntry {
                padding_len,
                count,
                sample_hex_bytes: sample_hex_bytes.into_iter().collect(),
            }
        })
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.padding_len.cmp(&right.padding_len))
    });
    summaries.truncate(6);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_companion_bytes(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructureProfileCompanionByteSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut grouped = BTreeMap::<String, (usize, BTreeSet<String>, BTreeSet<String>)>::new();
    for entry in &triplets.entries {
        let Some(companion_byte_hex) = entry.profile_companion_byte_hex.as_ref() else {
            continue;
        };
        let grouped_entry = grouped
            .entry(companion_byte_hex.clone())
            .or_insert_with(|| (0, BTreeSet::new(), BTreeSet::new()));
        grouped_entry.0 += 1;
        if grouped_entry.1.len() < 4 {
            grouped_entry.1.insert(entry.primary_name.clone());
        }
        if grouped_entry.2.len() < 4 {
            grouped_entry.2.insert(entry.secondary_name.clone());
        }
    }
    let mut summaries = grouped
        .into_iter()
        .map(
            |(companion_byte_hex, (count, primary_names, secondary_names))| {
                SmpSavePlacedStructureProfileCompanionByteSummaryEntry {
                    companion_byte_hex,
                    count,
                    sample_primary_names: primary_names.into_iter().collect(),
                    sample_secondary_names: secondary_names.into_iter().collect(),
                }
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
    });
    summaries.truncate(8);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_policy_trailing_words(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructurePolicyTrailingWordSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut grouped = BTreeMap::<String, usize>::new();
    for entry in &triplets.entries {
        *grouped
            .entry(entry.policy_trailing_word_hex.clone())
            .or_insert(0) += 1;
    }
    let mut summaries = grouped
        .into_iter()
        .map(|(policy_trailing_word_hex, count)| {
            SmpSavePlacedStructurePolicyTrailingWordSummaryEntry {
                policy_trailing_word_hex,
                count,
            }
        })
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right.count.cmp(&left.count).then_with(|| {
            left.policy_trailing_word_hex
                .cmp(&right.policy_trailing_word_hex)
        })
    });
    summaries.truncate(8);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_nonzero_companion_name_pairs(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry> {
    let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
        return Vec::new();
    };
    let mut grouped = BTreeMap::<(String, String, String), usize>::new();
    for entry in &triplets.entries {
        let Some(companion_byte_hex) = entry.profile_companion_byte_hex.as_ref() else {
            continue;
        };
        if companion_byte_hex == "0x00" {
            continue;
        }
        *grouped
            .entry((
                companion_byte_hex.clone(),
                entry.primary_name.clone(),
                entry.secondary_name.clone(),
            ))
            .or_insert(0) += 1;
    }
    let mut summaries = grouped
        .into_iter()
        .map(
            |((companion_byte_hex, primary_name, secondary_name), count)| {
                SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry {
                    companion_byte_hex,
                    primary_name,
                    secondary_name,
                    count,
                }
            },
        )
        .collect::<Vec<_>>();
    summaries.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
            .then_with(|| left.primary_name.cmp(&right.primary_name))
            .then_with(|| left.secondary_name.cmp(&right.secondary_name))
    });
    summaries.truncate(10);
    summaries
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_nonzero_companion_building_family_overlaps(
    summaries: &[SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry],
) -> Vec<SmpSavePlacedStructureNonzeroCompanionBuildingFamilyOverlapSummaryEntry> {
    let nonzero_stock_family_aliases = nonzero_stock_building_header_family_aliases();
    let mut overlaps = summaries
        .iter()
        .filter_map(|entry| {
            let primary_matches = nonzero_stock_family_aliases
                .contains(&canonicalize_building_like_name(&entry.primary_name));
            let secondary_matches = nonzero_stock_family_aliases
                .contains(&canonicalize_building_like_name(&entry.secondary_name));
            if !(primary_matches || secondary_matches) {
                return None;
            }
            Some(
                SmpSavePlacedStructureNonzeroCompanionBuildingFamilyOverlapSummaryEntry {
                    companion_byte_hex: entry.companion_byte_hex.clone(),
                    primary_name: entry.primary_name.clone(),
                    secondary_name: entry.secondary_name.clone(),
                    count: entry.count,
                    primary_matches_nonzero_stock_building_header_family: primary_matches,
                    secondary_matches_nonzero_stock_building_header_family: secondary_matches,
                },
            )
        })
        .collect::<Vec<_>>();
    overlaps.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
            .then_with(|| left.primary_name.cmp(&right.primary_name))
            .then_with(|| left.secondary_name.cmp(&right.secondary_name))
    });
    overlaps.truncate(10);
    overlaps
}

pub(in crate::inspect::smp) fn summarize_peer_site_selector_candidate_saved_nonzero_companion_building_family_residues(
    summaries: &[SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry],
) -> Vec<SmpSavePlacedStructureNonzeroCompanionBuildingFamilyResidueSummaryEntry> {
    let nonzero_stock_family_aliases = nonzero_stock_building_header_family_aliases();
    let mut residues = summaries
        .iter()
        .filter(|entry| {
            !nonzero_stock_family_aliases
                .contains(&canonicalize_building_like_name(&entry.primary_name))
                && !nonzero_stock_family_aliases
                    .contains(&canonicalize_building_like_name(&entry.secondary_name))
        })
        .map(
            |entry| SmpSavePlacedStructureNonzeroCompanionBuildingFamilyResidueSummaryEntry {
                companion_byte_hex: entry.companion_byte_hex.clone(),
                primary_name: entry.primary_name.clone(),
                secondary_name: entry.secondary_name.clone(),
                count: entry.count,
            },
        )
        .collect::<Vec<_>>();
    residues.sort_by(|left, right| {
        right
            .count
            .cmp(&left.count)
            .then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
            .then_with(|| left.primary_name.cmp(&right.primary_name))
            .then_with(|| left.secondary_name.cmp(&right.secondary_name))
    });
    residues.truncate(10);
    residues
}

pub(in crate::inspect::smp) fn nonzero_stock_building_header_family_aliases() -> BTreeSet<String> {
    [
        "Brewery",
        "ConcretePlant",
        "ConstructionFirm",
        "DairyProcessor",
        "Distillery",
        "ElectronicsPlant",
        "Furnace",
        "FurnitureFactory",
        "Hospital",
        "Lumbermill",
        "MachineShop",
        "MeatPackingPlant",
        "Museum",
        "PaperMill",
        "PharmaceuticalPlant",
        "Port",
        "Recycling Plant",
        "Steel Mill",
        "Textile Mill",
        "TextileMill",
        "Tire Factory",
        "Tool and Die",
        "Toolndie",
        "Warehouse",
    ]
    .into_iter()
    .map(canonicalize_building_like_name)
    .collect()
}

pub(in crate::inspect::smp) fn canonicalize_building_like_name(name: &str) -> String {
    name.chars()
        .filter(|ch| !matches!(ch, ' ' | '_' | '-'))
        .flat_map(|ch| ch.to_lowercase())
        .collect()
}

pub(in crate::inspect::smp) fn summarize_near_city_acquisition_tri_lane_save_shape_family_candidates(
    analysis: &SmpSaveCompanyChairmanAnalysisReport,
) -> Vec<SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry> {
    let Some(probe) = analysis.region_fixed_row_run_candidates.as_ref() else {
        return Vec::new();
    };
    probe
        .candidates
        .iter()
        .take(5)
        .enumerate()
        .map(
            |(index, candidate)| SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry {
                rank: index + 1,
                shape_family_signature: candidate.shape_family_signature.clone(),
                rows_offset_hex: candidate.rows_offset_hex.clone(),
                row_count: candidate.row_count,
                row_stride_hex: candidate.row_stride_hex.clone(),
                best_probable_density_lane_relative_offset_hex: candidate
                    .best_probable_density_lane_relative_offset_hex
                    .clone(),
            },
        )
        .collect()
}
