use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoNameToken {
    pub raw_name: String,
    pub visible_name: String,
    #[serde(default)]
    pub localized_string_id: Option<u32>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoTypeEntry {
    pub file_name: String,
    pub file_size: usize,
    pub file_size_hex: String,
    pub header_magic: u32,
    pub header_magic_hex: String,
    pub name: CargoNameToken,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoTypeInspectionReport {
    pub directory_path: String,
    pub entry_count: usize,
    pub unique_visible_name_count: usize,
    pub notes: Vec<String>,
    pub entries: Vec<CargoTypeEntry>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoSkinDescriptorEntry {
    pub pk4_entry_name: String,
    pub payload_len: usize,
    pub payload_len_hex: String,
    pub descriptor_kind: String,
    pub name: CargoNameToken,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoSkinInspectionReport {
    pub pk4_path: String,
    pub entry_count: usize,
    pub unique_visible_name_count: usize,
    pub notes: Vec<String>,
    pub entries: Vec<CargoSkinDescriptorEntry>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoEconomySourceReport {
    pub cargo_types_dir: String,
    pub cargo_skin_pk4_path: String,
    pub named_cargo_price_row_count: usize,
    pub named_cargo_production_row_count: usize,
    pub cargo_type_count: usize,
    pub cargo_skin_count: usize,
    pub shared_visible_name_count: usize,
    pub visible_name_union_count: usize,
    pub cargo_type_only_visible_names: Vec<String>,
    pub cargo_skin_only_visible_names: Vec<String>,
    pub live_registry_count: usize,
    pub live_registry_entries: Vec<CargoLiveRegistryEntry>,
    pub price_selector_candidate_excess_count: usize,
    pub price_selector_candidate_only_visible_names: Vec<String>,
    pub production_selector: Option<CargoSelectorReport>,
    pub price_selector: CargoSelectorReport,
    pub notes: Vec<String>,
    pub cargo_type_entries: Vec<CargoTypeEntry>,
    pub cargo_skin_entries: Vec<CargoSkinDescriptorEntry>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CargoRegistrySourceKind {
    CargoTypes,
    CargoSkin,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoLiveRegistryEntry {
    pub visible_name: String,
    pub raw_names: Vec<String>,
    pub localized_string_ids: Vec<u32>,
    pub source_kinds: Vec<CargoRegistrySourceKind>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoSelectorEntry {
    pub selector_index: usize,
    #[serde(default)]
    pub descriptor_id: Option<u32>,
    pub visible_name: String,
    pub source_kinds: Vec<CargoRegistrySourceKind>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CargoSelectorReport {
    pub selector_kind: String,
    pub exact_resolution: bool,
    pub selector_row_count: usize,
    pub candidate_registry_count: usize,
    pub notes: Vec<String>,
    pub entries: Vec<CargoSelectorEntry>,
}

pub(super) fn parse_cargo_name_token(raw_name: &str) -> CargoNameToken {
    let mut visible_name = raw_name.to_string();
    let mut localized_string_id = None;
    if let Some(rest) = raw_name.strip_prefix('~') {
        let digits = rest
            .chars()
            .take_while(|character| character.is_ascii_digit())
            .collect::<String>();
        if !digits.is_empty() {
            localized_string_id = digits.parse::<u32>().ok();
            visible_name = rest[digits.len()..].to_string();
        }
    }

    CargoNameToken {
        raw_name: raw_name.to_string(),
        visible_name,
        localized_string_id,
    }
}
