/// DreamStack Type System.
///
/// Types are structural, with first-class signal and effect awareness.
/// The type system tracks whether values are reactive (wrapped in Signal<T>)
/// and which effects a function may perform.

use std::collections::HashMap;

/// Type variable identifier for inference.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeVar(pub u32);

/// The core type representation.
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
    /// Primitive types
    Int,
    Float,
    String,
    Bool,
    Unit,

    /// A reactive signal wrapping a value type.
    /// `Signal<Int>` means a mutable source of integers.
    Signal(Box<Type>),

    /// A derived computation that depends on signals.
    /// `Derived<Int>` is read-only, auto-updating.
    Derived(Box<Type>),

    /// A function type with effect annotations.
    /// `(args) -> return ! effects`
    Fn {
        params: Vec<Type>,
        ret: Box<Type>,
        effects: Vec<EffectType>,
    },

    /// An array/list type.
    Array(Box<Type>),

    /// A record/struct type (structural).
    Record(HashMap<String, Type>),

    /// A sum type / tagged union.
    Variant(Vec<(String, Type)>),

    /// Stream type — push-based async sequence.
    Stream(Box<Type>),

    /// Spring type — physics-animated value.
    Spring(Box<Type>),

    /// View type — a UI expression. Only valid inside `view` blocks.
    View,

    /// An unresolved type variable (for inference).
    Var(TypeVar),

    /// Refinement type: base type + predicate constraint.
    /// `Int where value > 0` becomes `Refined { base: Int, predicate: Gt(Value, IntLit(0)) }`
    Refined {
        base: Box<Type>,
        predicate: Predicate,
    },

    /// Named type alias reference (resolved during checking).
    Named(String),

    /// Error sentinel (for error recovery).
    Error,
}

/// Signal-specific type information.
#[derive(Debug, Clone, PartialEq)]
pub enum SignalType {
    Source,        // mutable, user-set
    Derived,      // computed, read-only
    Handler,      // event handler
}

/// Effect type — declares what side effects a function may perform.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum EffectType {
    /// HTTP/network effects.
    Http,
    /// Local storage / persistence.
    Storage,
    /// Time-related effects (setTimeout, intervals).
    Time,
    /// DOM manipulation (only allowed in view blocks).
    Dom,
    /// Console/logging.
    Log,
    /// Random number generation.
    Random,
    /// Custom user-defined effect.
    Custom(String),
    /// Pure — no effects (default).
    Pure,
}

/// A semantic predicate for refinement types.
#[derive(Debug, Clone, PartialEq)]
pub enum Predicate {
    Gt(Box<PredicateExpr>, Box<PredicateExpr>),
    Gte(Box<PredicateExpr>, Box<PredicateExpr>),
    Lt(Box<PredicateExpr>, Box<PredicateExpr>),
    Lte(Box<PredicateExpr>, Box<PredicateExpr>),
    Eq(Box<PredicateExpr>, Box<PredicateExpr>),
    Neq(Box<PredicateExpr>, Box<PredicateExpr>),
    And(Box<Predicate>, Box<Predicate>),
    Or(Box<Predicate>, Box<Predicate>),
    Not(Box<Predicate>),
    /// A call like `len(value) > 0` or `contains(value, "@")`
    Call(String, Vec<PredicateExpr>),
    /// Raw expression that couldn't be further decomposed
    Expr(String),
}

/// Predicate sub-expression.
#[derive(Debug, Clone, PartialEq)]
pub enum PredicateExpr {
    /// The refined value itself (the `value` keyword in `where value > 0`)
    Value,
    IntLit(i64),
    FloatLit(f64),
    StringLit(String),
    BoolLit(bool),
    Call(String, Vec<PredicateExpr>),
}

impl Predicate {
    /// Pretty-print the predicate for error messages.
    pub fn display(&self) -> String {
        match self {
            Predicate::Gt(l, r) => format!("{} > {}", l.display(), r.display()),
            Predicate::Gte(l, r) => format!("{} >= {}", l.display(), r.display()),
            Predicate::Lt(l, r) => format!("{} < {}", l.display(), r.display()),
            Predicate::Lte(l, r) => format!("{} <= {}", l.display(), r.display()),
            Predicate::Eq(l, r) => format!("{} == {}", l.display(), r.display()),
            Predicate::Neq(l, r) => format!("{} != {}", l.display(), r.display()),
            Predicate::And(l, r) => format!("{} and {}", l.display(), r.display()),
            Predicate::Or(l, r) => format!("{} or {}", l.display(), r.display()),
            Predicate::Not(p) => format!("not {}", p.display()),
            Predicate::Call(name, args) => {
                let args_str = args.iter().map(|a| a.display()).collect::<Vec<_>>().join(", ");
                format!("{}({})", name, args_str)
            }
            Predicate::Expr(s) => s.clone(),
        }
    }

    /// Try to evaluate the predicate statically with a concrete value.
    /// Returns Some(true/false) if evaluable, None if dynamic.
    pub fn evaluate_static(&self, value: &PredicateExpr) -> Option<bool> {
        match self {
            Predicate::Gt(l, r) => {
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv > rv)
            }
            Predicate::Gte(l, r) => {
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv >= rv)
            }
            Predicate::Lt(l, r) => {
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv < rv)
            }
            Predicate::Lte(l, r) => {
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv <= rv)
            }
            Predicate::Eq(l, r) => {
                // Prefer exact integer comparison to avoid f64 precision issues
                if let Some((lv, rv)) = Self::try_resolve_ints(l, r, value) {
                    return Some(lv == rv);
                }
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv == rv)
            }
            Predicate::Neq(l, r) => {
                if let Some((lv, rv)) = Self::try_resolve_ints(l, r, value) {
                    return Some(lv != rv);
                }
                let lv = Self::resolve_expr(l, value)?;
                let rv = Self::resolve_expr(r, value)?;
                Some(lv != rv)
            }
            Predicate::And(l, r) => {
                let lv = l.evaluate_static(value)?;
                let rv = r.evaluate_static(value)?;
                Some(lv && rv)
            }
            Predicate::Or(l, r) => {
                let lv = l.evaluate_static(value)?;
                let rv = r.evaluate_static(value)?;
                Some(lv || rv)
            }
            Predicate::Not(p) => {
                let v = p.evaluate_static(value)?;
                Some(!v)
            }
            _ => None, // calls and raw expressions can't be statically evaluated
        }
    }

    fn resolve_expr(expr: &PredicateExpr, value: &PredicateExpr) -> Option<f64> {
        match expr {
            PredicateExpr::Value => match value {
                PredicateExpr::IntLit(n) => Some(*n as f64),
                PredicateExpr::FloatLit(f) => Some(*f),
                _ => None,
            },
            PredicateExpr::IntLit(n) => Some(*n as f64),
            PredicateExpr::FloatLit(f) => Some(*f),
            _ => None,
        }
    }

    /// Try to resolve both as integers for exact comparison (avoids f64 precision loss).
    fn try_resolve_ints(l: &PredicateExpr, r: &PredicateExpr, value: &PredicateExpr) -> Option<(i64, i64)> {
        let lv = match l {
            PredicateExpr::Value => match value {
                PredicateExpr::IntLit(n) => Some(*n),
                _ => None,
            },
            PredicateExpr::IntLit(n) => Some(*n),
            _ => None,
        }?;
        let rv = match r {
            PredicateExpr::Value => match value {
                PredicateExpr::IntLit(n) => Some(*n),
                _ => None,
            },
            PredicateExpr::IntLit(n) => Some(*n),
            _ => None,
        }?;
        Some((lv, rv))
    }
}

impl PredicateExpr {
    pub fn display(&self) -> String {
        match self {
            PredicateExpr::Value => "value".to_string(),
            PredicateExpr::IntLit(n) => n.to_string(),
            PredicateExpr::FloatLit(f) => f.to_string(),
            PredicateExpr::StringLit(s) => format!("\"{}\"", s),
            PredicateExpr::BoolLit(b) => b.to_string(),
            PredicateExpr::Call(name, args) => {
                let args_str = args.iter().map(|a| a.display()).collect::<Vec<_>>().join(", ");
                format!("{}({})", name, args_str)
            }
        }
    }
}

impl Type {
    /// Unwrap the inner type of a Signal or Derived.
    pub fn unwrap_reactive(&self) -> &Type {
        match self {
            Type::Signal(inner) | Type::Derived(inner) => inner,
            Type::Refined { base, .. } => base.unwrap_reactive(),
            other => other,
        }
    }

    /// Strip refinement wrapper, returning the base type.
    pub fn unwrap_refined(&self) -> &Type {
        match self {
            Type::Refined { base, .. } => base.unwrap_refined(),
            other => other,
        }
    }

    /// Check if this type is reactive (Signal or Derived).
    pub fn is_reactive(&self) -> bool {
        matches!(self, Type::Signal(_) | Type::Derived(_))
    }

    /// Check if this type is a function.
    pub fn is_fn(&self) -> bool {
        matches!(self, Type::Fn { .. })
    }

    /// Pretty-print the type.
    pub fn display(&self) -> String {
        match self {
            Type::Int => "Int".to_string(),
            Type::Float => "Float".to_string(),
            Type::String => "String".to_string(),
            Type::Bool => "Bool".to_string(),
            Type::Unit => "()".to_string(),
            Type::Signal(inner) => format!("Signal<{}>", inner.display()),
            Type::Derived(inner) => format!("Derived<{}>", inner.display()),
            Type::Fn { params, ret, effects } => {
                let params_str = params.iter().map(|p| p.display()).collect::<Vec<_>>().join(", ");
                let eff_str = if effects.is_empty() || effects == &[EffectType::Pure] {
                    String::new()
                } else {
                    format!(" ! {}", effects.iter().map(|e| format!("{:?}", e)).collect::<Vec<_>>().join(", "))
                };
                format!("({}) -> {}{}", params_str, ret.display(), eff_str)
            }
            Type::Array(inner) => format!("[{}]", inner.display()),
            Type::Record(fields) => {
                let fields_str = fields.iter()
                    .map(|(k, v)| format!("{}: {}", k, v.display()))
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("{{ {} }}", fields_str)
            }
            Type::Variant(variants) => {
                let vars_str = variants.iter()
                    .map(|(name, ty)| format!("{}({})", name, ty.display()))
                    .collect::<Vec<_>>()
                    .join(" | ");
                vars_str
            }
            Type::Stream(inner) => format!("Stream<{}>", inner.display()),
            Type::Spring(inner) => format!("Spring<{}>", inner.display()),
            Type::View => "View".to_string(),
            Type::Var(tv) => format!("?{}", tv.0),
            Type::Refined { base, predicate } => {
                format!("{} where {}", base.display(), predicate.display())
            }
            Type::Named(name) => name.clone(),
            Type::Error => "<error>".to_string(),
        }
    }
}

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

    #[test]
    fn test_type_display() {
        assert_eq!(Type::Int.display(), "Int");
        assert_eq!(Type::Signal(Box::new(Type::Int)).display(), "Signal<Int>");
        assert_eq!(Type::Derived(Box::new(Type::Bool)).display(), "Derived<Bool>");
        assert_eq!(Type::Array(Box::new(Type::String)).display(), "[String]");
        assert_eq!(Type::Stream(Box::new(Type::Int)).display(), "Stream<Int>");

        let fn_type = Type::Fn {
            params: vec![Type::Int, Type::String],
            ret: Box::new(Type::Bool),
            effects: vec![EffectType::Http],
        };
        assert_eq!(fn_type.display(), "(Int, String) -> Bool ! Http");
    }

    #[test]
    fn test_reactive_checks() {
        assert!(Type::Signal(Box::new(Type::Int)).is_reactive());
        assert!(Type::Derived(Box::new(Type::Int)).is_reactive());
        assert!(!Type::Int.is_reactive());
        assert!(!Type::String.is_reactive());
    }

    #[test]
    fn test_unwrap_reactive() {
        let sig = Type::Signal(Box::new(Type::Int));
        assert_eq!(*sig.unwrap_reactive(), Type::Int);

        let derived = Type::Derived(Box::new(Type::Bool));
        assert_eq!(*derived.unwrap_reactive(), Type::Bool);

        // Non-reactive returns self
        assert_eq!(*Type::Int.unwrap_reactive(), Type::Int);
    }

    #[test]
    fn test_predicate_display() {
        let pred = Predicate::Gt(
            Box::new(PredicateExpr::Value),
            Box::new(PredicateExpr::IntLit(0)),
        );
        assert_eq!(pred.display(), "value > 0");

        let compound = Predicate::And(
            Box::new(Predicate::Gte(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(0)),
            )),
            Box::new(Predicate::Lte(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(100)),
            )),
        );
        assert_eq!(compound.display(), "value >= 0 and value <= 100");
    }

    #[test]
    fn test_evaluate_static_int() {
        let pred = Predicate::Gt(
            Box::new(PredicateExpr::Value),
            Box::new(PredicateExpr::IntLit(0)),
        );
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(5)), Some(true));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(0)), Some(false));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(-1)), Some(false));
    }

    #[test]
    fn test_evaluate_static_eq_integer_precision() {
        // Test that integer equality uses exact comparison, not f64
        let pred = Predicate::Eq(
            Box::new(PredicateExpr::Value),
            Box::new(PredicateExpr::IntLit(42)),
        );
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(42)), Some(true));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(43)), Some(false));
    }

    #[test]
    fn test_evaluate_static_compound() {
        // value >= 0 and value <= 100
        let pred = Predicate::And(
            Box::new(Predicate::Gte(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(0)),
            )),
            Box::new(Predicate::Lte(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(100)),
            )),
        );
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(50)), Some(true));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(0)), Some(true));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(100)), Some(true));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(-1)), Some(false));
        assert_eq!(pred.evaluate_static(&PredicateExpr::IntLit(101)), Some(false));
    }

    #[test]
    fn test_unwrap_refined() {
        let refined = Type::Refined {
            base: Box::new(Type::Int),
            predicate: Predicate::Gt(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(0)),
            ),
        };
        assert_eq!(*refined.unwrap_refined(), Type::Int);
        // Non-refined returns self
        assert_eq!(*Type::Int.unwrap_refined(), Type::Int);
    }

    #[test]
    fn test_refined_type_display() {
        let refined = Type::Refined {
            base: Box::new(Type::Int),
            predicate: Predicate::Gt(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(0)),
            ),
        };
        assert_eq!(refined.display(), "Int where value > 0");
    }

    #[test]
    fn test_refined_unwrap_reactive() {
        // Refined wrapping a non-reactive type
        let refined = Type::Refined {
            base: Box::new(Type::Int),
            predicate: Predicate::Gt(
                Box::new(PredicateExpr::Value),
                Box::new(PredicateExpr::IntLit(0)),
            ),
        };
        assert_eq!(*refined.unwrap_reactive(), Type::Int);
    }
}
