use crate::span::Span;

/// A spanned AST node — every node carries its source location.
#[derive(Debug, Clone, PartialEq)]
pub struct Spanned<T> {
    pub node: T,
    pub span: Span,
}

impl<T> Spanned<T> {
    pub fn new(node: T, span: Span) -> Self {
        Self { node, span }
    }
}

/// Top-level expression AST.
pub type Expr = Spanned<ExprKind>;

#[derive(Debug, Clone, PartialEq)]
pub enum ExprKind {
    /// Numeric literal: `42`, `3.14`
    Number(f64),

    /// Number with a unit: `5kg`, `200g`
    UnitNumber { value: f64, unit: String },

    /// Currency value: `$20`, `€15`
    CurrencyValue { amount: f64, currency: String },

    /// Boolean literal
    Boolean(bool),

    /// Date literal (year, month, day)
    DateLiteral { year: i32, month: u32, day: u32 },

    /// The keyword `today`
    Today,

    /// Duration literal: `3 weeks`, `5 days`
    Duration { value: f64, unit: DurationUnit },

    /// Binary operation: `a + b`, `a * b`
    BinaryOp {
        op: BinOp,
        left: Box<Expr>,
        right: Box<Expr>,
    },

    /// Unary negation: `-x`
    UnaryNeg(Box<Expr>),

    /// Percentage applied to a value: `100 - 20%`, `$100 + 10%`
    PercentOp {
        op: PercentOp,
        base: Box<Expr>,
        percentage: f64,
    },

    /// Currency conversion: `$20 in EUR`
    Conversion {
        expr: Box<Expr>,
        target_currency: String,
    },

    /// Date range: `March 12 to July 30`
    DateRange {
        from: Box<Expr>,
        to: Box<Expr>,
    },

    /// Comparison: `5 > 3`
    Comparison {
        op: CmpOp,
        left: Box<Expr>,
        right: Box<Expr>,
    },

    /// Variable reference: `x`, `total`
    Identifier(String),

    /// Variable assignment: `x = 5`
    Assignment {
        name: String,
        value: Box<Expr>,
    },

    /// Line reference: `line1`, `#1` (1-indexed line number)
    LineRef(usize),

    /// Previous-line reference: `prev`, `ans`
    PrevRef,

    /// Function call: `sqrt(4)`, `abs(-5)`
    FunctionCall {
        name: String,
        args: Vec<Expr>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
    Add,
    Sub,
    Mul,
    Div,
    Pow,
    Mod,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PercentOp {
    Add,
    Sub,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpOp {
    Gt,
    Lt,
    Gte,
    Lte,
    Eq,
    Neq,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DurationUnit {
    Days,
    Weeks,
    Months,
    Years,
    Hours,
    Minutes,
    Seconds,
}

impl std::fmt::Display for BinOp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BinOp::Add => write!(f, "+"),
            BinOp::Sub => write!(f, "-"),
            BinOp::Mul => write!(f, "*"),
            BinOp::Div => write!(f, "/"),
            BinOp::Pow => write!(f, "^"),
            BinOp::Mod => write!(f, "%"),
        }
    }
}

impl std::fmt::Display for CmpOp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CmpOp::Gt => write!(f, ">"),
            CmpOp::Lt => write!(f, "<"),
            CmpOp::Gte => write!(f, ">="),
            CmpOp::Lte => write!(f, "<="),
            CmpOp::Eq => write!(f, "=="),
            CmpOp::Neq => write!(f, "!="),
        }
    }
}
