pub mod finance;

use std::collections::BTreeMap;
use std::fmt;
use std::fs::File;
use std::path::Path;
use std::str::FromStr;

use serde::Deserialize;
use serde::de::{self, Deserializer};

pub const CANONICAL_EXE_PATH: &str = "rt3_wineprefix/drive_c/rt3/RT3.exe";
pub const CONTROL_LOOP_ATLAS_PATH: &str = "docs/control-loop-atlas.md";
pub const FUNCTION_MAP_PATH: &str = "artifacts/exports/rt3-1.06/function-map.csv";
pub const BINARY_SUMMARY_PATH: &str = "artifacts/exports/rt3-1.06/binary-summary.json";

pub const REQUIRED_EXPORTS: &[&str] = &[
    BINARY_SUMMARY_PATH,
    "artifacts/exports/rt3-1.06/sections.csv",
    "artifacts/exports/rt3-1.06/imported-dlls.txt",
    "artifacts/exports/rt3-1.06/imported-functions.csv",
    "artifacts/exports/rt3-1.06/interesting-strings.txt",
    "artifacts/exports/rt3-1.06/subsystem-inventory.md",
    FUNCTION_MAP_PATH,
    "artifacts/exports/rt3-1.06/ghidra-startup-functions.csv",
    "artifacts/exports/rt3-1.06/startup-call-chain.md",
    "artifacts/exports/rt3-1.06/analysis-context-functions.csv",
    "artifacts/exports/rt3-1.06/analysis-context-strings.csv",
    "artifacts/exports/rt3-1.06/analysis-context.md",
    "artifacts/exports/rt3-1.06/pending-template-store-functions.csv",
    "artifacts/exports/rt3-1.06/pending-template-store-record-kinds.csv",
    "artifacts/exports/rt3-1.06/pending-template-store-management.md",
    "artifacts/exports/rt3-1.06/event-effects-table.json",
    "artifacts/exports/rt3-1.06/event-effects-cargo-bindings.json",
    "artifacts/exports/rt3-1.06/event-effects-building-bindings.json",
    "artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json",
    "artifacts/exports/rt3-1.06/building-type-sources.json",
    "artifacts/exports/rt3-1.06/economy-cargo-sources.json",
    "artifacts/exports/rt3-1.06/selected-year-bucket-ladder.json",
];

pub const REQUIRED_ATLAS_HEADINGS: &[&str] = &[
    "## CRT and Process Startup",
    "## Bootstrap and Shell Service Bring-Up",
    "## Shell UI Command and Deferred Work Flow",
    "## Presentation, Overlay, and Frame Timing",
    "## Map and Scenario Content Load",
    "## Multiplayer Session and Transport Flow",
    "## Input, Save/Load, and Simulation",
    "## Next Mapping Passes",
];

pub const FUNCTION_MAP_HEADER: &[&str] = &[
    "address",
    "size",
    "name",
    "subsystem",
    "calling_convention",
    "prototype_status",
    "source_tool",
    "confidence",
    "notes",
    "verified_against",
];

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address(pub u32);

impl Address {
    pub fn parse_hex(value: &str) -> Result<Self, String> {
        let trimmed = value.trim();
        let digits = trimmed
            .strip_prefix("0x")
            .or_else(|| trimmed.strip_prefix("0X"))
            .unwrap_or(trimmed);
        u32::from_str_radix(digits, 16)
            .map(Self)
            .map_err(|err| format!("invalid hex address `{trimmed}`: {err}"))
    }
}

impl fmt::Display for Address {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "0x{:08x}", self.0)
    }
}

impl FromStr for Address {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::parse_hex(value)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SubsystemId {
    Startup,
    Bootstrap,
    Shell,
    Support,
    Ui,
    Render,
    Audio,
    Input,
    Network,
    Filesystem,
    Resource,
    Map,
    Scenario,
    Save,
    Simulation,
    Unknown,
}

impl fmt::Display for SubsystemId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::Startup => "startup",
            Self::Bootstrap => "bootstrap",
            Self::Shell => "shell",
            Self::Support => "support",
            Self::Ui => "ui",
            Self::Render => "render",
            Self::Audio => "audio",
            Self::Input => "input",
            Self::Network => "network",
            Self::Filesystem => "filesystem",
            Self::Resource => "resource",
            Self::Map => "map",
            Self::Scenario => "scenario",
            Self::Save => "save",
            Self::Simulation => "simulation",
            Self::Unknown => "unknown",
        };
        f.write_str(label)
    }
}

impl FromStr for SubsystemId {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "startup" => Ok(Self::Startup),
            "bootstrap" => Ok(Self::Bootstrap),
            "shell" => Ok(Self::Shell),
            "support" => Ok(Self::Support),
            "ui" => Ok(Self::Ui),
            "render" => Ok(Self::Render),
            "audio" => Ok(Self::Audio),
            "input" => Ok(Self::Input),
            "network" => Ok(Self::Network),
            "filesystem" => Ok(Self::Filesystem),
            "resource" => Ok(Self::Resource),
            "map" => Ok(Self::Map),
            "scenario" => Ok(Self::Scenario),
            "save" => Ok(Self::Save),
            "simulation" => Ok(Self::Simulation),
            "unknown" => Ok(Self::Unknown),
            other => Err(format!("unknown subsystem `{other}`")),
        }
    }
}

impl<'de> Deserialize<'de> for SubsystemId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let raw = String::deserialize(deserializer)?;
        raw.parse().map_err(de::Error::custom)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ControlLoopId {
    CrtStartup,
    Bootstrap,
    ShellUi,
    PresentationFrame,
    MapScenarioLoad,
    MultiplayerTransport,
    InputSaveSimulation,
}

impl fmt::Display for ControlLoopId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::CrtStartup => "crt-startup",
            Self::Bootstrap => "bootstrap",
            Self::ShellUi => "shell-ui",
            Self::PresentationFrame => "presentation-frame",
            Self::MapScenarioLoad => "map-scenario-load",
            Self::MultiplayerTransport => "multiplayer-transport",
            Self::InputSaveSimulation => "input-save-simulation",
        };
        f.write_str(label)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LoopRole {
    Root,
    Dispatcher,
    Cadence,
    StateAnchor,
    Gateway,
}

impl fmt::Display for LoopRole {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let label = match self {
            Self::Root => "root",
            Self::Dispatcher => "dispatcher",
            Self::Cadence => "cadence",
            Self::StateAnchor => "state-anchor",
            Self::Gateway => "gateway",
        };
        f.write_str(label)
    }
}

#[derive(Debug, Deserialize)]
pub struct FunctionRecord {
    #[serde(deserialize_with = "deserialize_address")]
    pub address: Address,
    #[serde(default, deserialize_with = "deserialize_optional_u32")]
    pub size: Option<u32>,
    pub name: String,
    pub subsystem: SubsystemId,
    pub calling_convention: String,
    pub prototype_status: String,
    pub source_tool: String,
    pub confidence: u8,
    pub notes: String,
    pub verified_against: String,
}

#[derive(Debug, Deserialize)]
pub struct BinarySummary {
    pub path: String,
    pub sha256: String,
    pub size_bytes: u64,
    #[serde(default)]
    pub summary: BTreeMap<String, String>,
}

pub fn load_function_map(path: &Path) -> Result<Vec<FunctionRecord>, Box<dyn std::error::Error>> {
    let mut reader = csv::ReaderBuilder::new()
        .trim(csv::Trim::All)
        .from_path(path)?;
    let headers = reader.headers()?.clone();
    let header_values: Vec<&str> = headers.iter().collect();
    if header_values != FUNCTION_MAP_HEADER {
        return Err(format!("unexpected function-map header: {header_values:?}").into());
    }

    let mut rows = Vec::new();
    for record in reader.deserialize() {
        rows.push(record?);
    }
    Ok(rows)
}

pub fn load_binary_summary(path: &Path) -> Result<BinarySummary, Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    Ok(serde_json::from_reader(file)?)
}

fn deserialize_address<'de, D>(deserializer: D) -> Result<Address, D::Error>
where
    D: Deserializer<'de>,
{
    let raw = String::deserialize(deserializer)?;
    raw.parse().map_err(de::Error::custom)
}

fn deserialize_optional_u32<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
    D: Deserializer<'de>,
{
    let raw = String::deserialize(deserializer)?;
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return Ok(None);
    }

    let parsed = trimmed
        .parse::<u32>()
        .or_else(|_| u32::from_str_radix(trimmed.trim_start_matches("0x"), 16))
        .map_err(de::Error::custom)?;
    Ok(Some(parsed))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_hex_addresses() {
        let address = Address::parse_hex("0x005a313b").expect("address should parse");
        assert_eq!(address.0, 0x005a313b);
        assert_eq!(address.to_string(), "0x005a313b");
    }

    #[test]
    fn parses_subsystems() {
        let subsystem: SubsystemId = "shell".parse().expect("subsystem should parse");
        assert_eq!(subsystem, SubsystemId::Shell);
        assert_eq!(subsystem.to_string(), "shell");
    }
}
