use std::fs;
use std::path::Path;

use crate::opt::{OptConfig, OptMode};
use crate::{DiagCode, DiagConfig, ReplConfig, Severity};

#[derive(Debug, Clone, Default)]
pub struct EffectiveConfig {
    pub repl: ReplConfig,
    pub diag: DiagConfig,
    pub opt: OptConfig,
}

pub fn load_effective_config_from_cwd() -> EffectiveConfig {
    let path = Path::new("gonidium.toml");
    if !path.exists() {
        return EffectiveConfig::default();
    }
    match fs::read_to_string(path) {
        Ok(raw) => parse_effective_config_toml(&raw).unwrap_or_else(|_| EffectiveConfig::default()),
        Err(_) => EffectiveConfig::default(),
    }
}

pub fn parse_effective_config_toml(raw: &str) -> Result<EffectiveConfig, String> {
    let mut cfg = EffectiveConfig::default();
    // Parse as a full TOML document (not a single TOML value).
    let raw = raw.trim_start_matches('\u{feff}');
    let value: toml::Value =
        toml::from_str(raw).map_err(|e| format!("invalid gonidium.toml: {e}"))?;
    let Some(root) = value.as_table() else {
        return Ok(cfg);
    };

    if let Some(repl_val) = root.get("repl").and_then(|v| v.as_table()) {
        if let Some(v) = repl_val.get("opt_enabled").and_then(|v| v.as_bool()) {
            cfg.repl.opt_enabled = v;
        }
        if let Some(v) = repl_val
            .get("print_simplified_expr")
            .and_then(|v| v.as_bool())
        {
            cfg.repl.print_simplified_expr = v;
        }
        if let Some(v) = repl_val
            .get("header_locals_limit")
            .and_then(|v| v.as_integer())
        {
            cfg.repl.header_locals_limit = (v.max(1) as usize).max(1);
        }
    }

    if let Some(opt_val) = root.get("optimization").and_then(|v| v.as_table()) {
        if let Some(mode_str) = opt_val.get("mode").and_then(|v| v.as_str()) {
            cfg.opt.mode = match mode_str {
                "conservative" => OptMode::Conservative,
                "aggressive" => OptMode::Aggressive,
                _ => cfg.opt.mode,
            };
        }
        if let Some(v) = opt_val.get("const_chain_fold").and_then(|v| v.as_bool()) {
            cfg.opt.const_chain_fold = Some(v);
        }
    }

    if let Some(diag_val) = root.get("diagnostics").and_then(|v| v.as_table()) {
        let mut diag_cfg = DiagConfig::default();
        for (key, severity) in [
            ("allow", Severity::Allow),
            ("info", Severity::Info),
            ("warning", Severity::Warning),
            ("error", Severity::Error),
        ] {
            if let Some(arr) = diag_val.get(key).and_then(|v| v.as_array()) {
                for item in arr {
                    let code_str = item
                        .as_str()
                        .ok_or_else(|| format!("diagnostics.{key} must be a string array"))?;
                    let code = diag_code_from_short_code(code_str)
                        .ok_or_else(|| format!("unknown diagnostic code `{code_str}`"))?;
                    diag_cfg = diag_cfg.set(code, severity);
                }
            }
        }
        cfg.diag = diag_cfg;
    }

    Ok(cfg)
}

pub fn diag_code_from_short_code(s: &str) -> Option<DiagCode> {
    use DiagCode::*;
    Some(match s {
        "P001" => ParseIntImaginary,
        "P002" => ParseUnknownDType,
        "P003" => ParseSyntax,
        "P101" => ParseUndefinedVar,
        "P102" => ParseReservedIdentifier,
        "L101" => LowerUndefinedVar,
        "L102" => LowerTypeMismatch,
        "L103" => LowerIntOutOfRange,
        "L104" => LowerUnknownFunction,
        "L105" => LowerArgCount,
        "L106" => LowerDivByZero,
        "L201" => LowerFloatPrecisionLoss,
        "L202" => LowerUnsafeUIntToSInt,
        "L203" => LowerRedundantCastChain,
        "L301" => LowerImplicitPromotion,
        "O201" => ConstFoldUnsafe,
        "O301" => OptPromotion,
        "C201" => ComposeSymbolReuse,
        _ => return None,
    })
}
