use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AggFunc {
    Sum,
    Avg,
    Min,
    Max,
    Count,
}

/// Arithmetic and comparison operators used in binary expressions.
/// Having an enum (rather than a raw String) means the parser must
/// produce a valid operator; invalid operators are caught at parse
/// time rather than silently returning Empty at eval time.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinOp {
    Add,
    Sub,
    Mul,
    Div,
    Pow,
    Eq,
    Ne,
    Lt,
    Gt,
    Le,
    Ge,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Filter {
    pub category: String,
    pub item: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Expr {
    Number(f64),
    Ref(String),
    BinOp(BinOp, Box<Expr>, Box<Expr>),
    UnaryMinus(Box<Expr>),
    Agg(AggFunc, Box<Expr>, Option<Filter>),
    If(Box<Expr>, Box<Expr>, Box<Expr>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Formula {
    /// The raw formula text, e.g. "Profit = Revenue - Cost"
    pub raw: String,
    /// The item/dimension name this formula computes, e.g. "Profit"
    pub target: String,
    /// The category containing the target item
    pub target_category: String,
    /// The expression to evaluate
    pub expr: Expr,
    /// Optional WHERE filter
    pub filter: Option<Filter>,
}

impl Formula {
    pub fn new(
        raw: impl Into<String>,
        target: impl Into<String>,
        target_category: impl Into<String>,
        expr: Expr,
        filter: Option<Filter>,
    ) -> Self {
        Self {
            raw: raw.into(),
            target: target.into(),
            target_category: target_category.into(),
            expr,
            filter,
        }
    }
}
