use std::collections::HashMap;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum SettingsFieldType {
    Bool,
    Int { min: i64, max: i64 },
    Float { min: f64, max: f64 },
    Enum { variants: Vec<String> },
    Str,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum SettingsValue {
    Bool(bool),
    Int(i64),
    Float(f64),
    Str(String),
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct SettingsField {
    pub key: String,
    pub label: String,
    pub description: String,
    pub field_type: SettingsFieldType,
    pub default: SettingsValue,
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct SettingsSchema {
    pub fields: Vec<SettingsField>,
}

impl SettingsSchema {
    pub fn empty() -> Self { Self { fields: vec![] } }

    pub fn with_field(mut self, field: SettingsField) -> Self {
        self.fields.push(field);
        self
    }

    pub fn resolve(&self, overrides: &HashMap<String, SettingsValue>) -> SettingsBundle {
        let mut map = HashMap::new();
        for f in &self.fields {
            map.insert(
                f.key.clone(),
                overrides.get(&f.key).cloned().unwrap_or_else(|| f.default.clone()),
            );
        }
        SettingsBundle(map)
    }

    pub fn merge(schemas: impl Iterator<Item = SettingsSchema>) -> Self {
        let mut merged = Self::empty();
        for schema in schemas {
            merged.fields.extend(schema.fields);
        }
        merged
    }

    /// Type-check an incoming setting mutation. Returns `Ok(())` if `key`
    /// names a known field and `value`'s variant matches the declared
    /// `SettingsFieldType` (with range bounds respected for Int/Float and
    /// variant membership for Enum). Single source of truth — session
    /// actor, campaign persistence and replay validation all route through
    /// this.
    pub fn validate(&self, key: &str, value: &SettingsValue) -> Result<(), String> {
        let field = self.fields.iter().find(|f| f.key == key)
            .ok_or_else(|| format!("unknown game setting: {key}"))?;
        match (&field.field_type, value) {
            (SettingsFieldType::Bool, SettingsValue::Bool(_)) => Ok(()),
            (SettingsFieldType::Int { min, max }, SettingsValue::Int(v)) => {
                if v < min || v > max {
                    Err(format!("{key}: {v} out of range [{min}..{max}]"))
                } else { Ok(()) }
            }
            (SettingsFieldType::Float { min, max }, SettingsValue::Float(v)) => {
                if v < min || v > max {
                    Err(format!("{key}: {v} out of range [{min}..{max}]"))
                } else { Ok(()) }
            }
            (SettingsFieldType::Enum { variants }, SettingsValue::Str(s)) => {
                if variants.iter().any(|v| v == s) { Ok(()) }
                else { Err(format!("{key}: {s:?} not in {variants:?}")) }
            }
            (SettingsFieldType::Str, SettingsValue::Str(_)) => Ok(()),
            _ => Err(format!("{key}: value type does not match field type")),
        }
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct SettingsBundle(pub HashMap<String, SettingsValue>);

impl SettingsBundle {
    pub fn empty() -> Self { Self::default() }

    pub fn get_bool(&self, key: &str) -> bool {
        matches!(self.0.get(key), Some(SettingsValue::Bool(true)))
    }

    pub fn get_int(&self, key: &str) -> i64 {
        match self.0.get(key) { Some(SettingsValue::Int(v)) => *v, _ => 0 }
    }

    pub fn get_str(&self, key: &str) -> &str {
        match self.0.get(key) { Some(SettingsValue::Str(s)) => s.as_str(), _ => "" }
    }

    pub fn get_enum(&self, key: &str) -> &str { self.get_str(key) }
}
