use std::collections::BTreeSet;
use std::fs;
use std::path::Path;

use crate::inspect::pk4::inspect_pk4_bytes;

use super::types::{CargoSkinDescriptorEntry, CargoSkinInspectionReport, parse_cargo_name_token};

pub fn inspect_cargo_skin_pk4(
    path: &Path,
) -> Result<CargoSkinInspectionReport, Box<dyn std::error::Error>> {
    let bytes = fs::read(path)?;
    let inspection = inspect_pk4_bytes(&bytes)?;
    let mut entries = Vec::new();
    for entry in &inspection.entries {
        if entry.extension.as_deref() != Some("dsc") {
            continue;
        }
        let payload = bytes
            .get(entry.payload_absolute_offset..entry.payload_end_offset)
            .ok_or_else(|| format!("pk4 payload range out of bounds for {}", entry.name))?;
        let parsed = parse_cargo_skin_descriptor_entry(&entry.name, payload)?;
        entries.push(parsed);
    }
    entries.sort_by(|left, right| left.name.visible_name.cmp(&right.name.visible_name));

    let mut notes = Vec::new();
    notes.push(
        "cargoSkin descriptors are parsed from .dsc payloads whose first non-empty line names the descriptor kind and whose second non-empty line carries the cargo token."
            .to_string(),
    );
    notes.push(
        "A leading `~####` token is preserved as raw_name and normalized into visible_name by stripping the localized string id prefix."
            .to_string(),
    );

    let unique_visible_name_count = entries
        .iter()
        .map(|entry| entry.name.visible_name.as_str())
        .collect::<BTreeSet<_>>()
        .len();

    Ok(CargoSkinInspectionReport {
        pk4_path: path.display().to_string(),
        entry_count: entries.len(),
        unique_visible_name_count,
        notes,
        entries,
    })
}

pub(super) fn parse_cargo_skin_descriptor_entry(
    entry_name: &str,
    bytes: &[u8],
) -> Result<CargoSkinDescriptorEntry, Box<dyn std::error::Error>> {
    let text = std::str::from_utf8(bytes)?;
    let mut lines = text.lines().map(str::trim).filter(|line| !line.is_empty());
    let descriptor_kind = lines
        .next()
        .ok_or_else(|| format!("cargo skin descriptor {entry_name} is missing the kind line"))?;
    let raw_name = lines
        .next()
        .ok_or_else(|| format!("cargo skin descriptor {entry_name} is missing the name line"))?;

    Ok(CargoSkinDescriptorEntry {
        pk4_entry_name: entry_name.to_string(),
        payload_len: bytes.len(),
        payload_len_hex: format!("0x{:x}", bytes.len()),
        descriptor_kind: descriptor_kind.to_string(),
        name: parse_cargo_name_token(raw_name),
    })
}
