//! Minimal Modelica-style algebraic expression AST for model equations.
//!
//! Supports: `+ - * /`, parentheses, unary `-`, `min`, `max`, `exp`, `ln`/`log`,
//! numeric literals, and references `component.field` or `"Component Name".field`.

use std::fmt;

/// Parsed expression node.
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Const(f64),
    /// `component` + `field` (field is temperature, z_ua, …).
    Ref { component: String, field: String },
    Neg(Box<Expr>),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
    Div(Box<Expr>, Box<Expr>),
    Min(Box<Expr>, Box<Expr>),
    Max(Box<Expr>, Box<Expr>),
    Exp(Box<Expr>),
    Ln(Box<Expr>),
}

impl Expr {
    /// Evaluate with a resolver for `component.field` references.
    pub fn eval<F>(&self, resolve: &mut F) -> Result<f64, String>
    where
        F: FnMut(&str, &str) -> Result<f64, String>,
    {
        match self {
            Expr::Const(v) => Ok(*v),
            Expr::Ref { component, field } => resolve(component, field),
            Expr::Neg(a) => Ok(-a.eval(resolve)?),
            Expr::Add(a, b) => Ok(a.eval(resolve)? + b.eval(resolve)?),
            Expr::Sub(a, b) => Ok(a.eval(resolve)? - b.eval(resolve)?),
            Expr::Mul(a, b) => Ok(a.eval(resolve)? * b.eval(resolve)?),
            Expr::Div(a, b) => {
                let den = b.eval(resolve)?;
                if den.abs() < 1e-30 {
                    return Err("division by zero in model equation".into());
                }
                Ok(a.eval(resolve)? / den)
            }
            Expr::Min(a, b) => Ok(a.eval(resolve)?.min(b.eval(resolve)?)),
            Expr::Max(a, b) => Ok(a.eval(resolve)?.max(b.eval(resolve)?)),
            Expr::Exp(a) => {
                let x = a.eval(resolve)?;
                let y = x.exp();
                if y.is_finite() {
                    Ok(y)
                } else {
                    Err(format!("exp({x}) overflow"))
                }
            }
            Expr::Ln(a) => {
                let x = a.eval(resolve)?;
                if x <= 0.0 {
                    return Err(format!("ln({x}) domain error"));
                }
                Ok(x.ln())
            }
        }
    }

    /// Collect all `component.field` references.
    pub fn collect_refs(&self, out: &mut Vec<(String, String)>) {
        match self {
            Expr::Const(_) => {}
            Expr::Ref { component, field } => out.push((component.clone(), field.clone())),
            Expr::Neg(a) | Expr::Exp(a) | Expr::Ln(a) => a.collect_refs(out),
            Expr::Add(a, b)
            | Expr::Sub(a, b)
            | Expr::Mul(a, b)
            | Expr::Div(a, b)
            | Expr::Min(a, b)
            | Expr::Max(a, b) => {
                a.collect_refs(out);
                b.collect_refs(out);
            }
        }
    }
}

/// Parse an expression string into an [`Expr`].
pub fn parse_expr(input: &str) -> Result<Expr, String> {
    let mut p = Parser::new(input);
    let e = p.parse_expr()?;
    p.skip_ws();
    if p.pos < p.chars.len() {
        return Err(format!(
            "trailing junk at '{}'",
            p.chars[p.pos..].iter().collect::<String>()
        ));
    }
    Ok(e)
}

struct Parser {
    chars: Vec<char>,
    pos: usize,
}

impl Parser {
    fn new(s: &str) -> Self {
        Self {
            chars: s.chars().collect(),
            pos: 0,
        }
    }

    fn skip_ws(&mut self) {
        while self.pos < self.chars.len() && self.chars[self.pos].is_whitespace() {
            self.pos += 1;
        }
    }

    fn peek(&self) -> Option<char> {
        self.chars.get(self.pos).copied()
    }

    fn bump(&mut self) -> Option<char> {
        let c = self.peek()?;
        self.pos += 1;
        Some(c)
    }

    fn parse_expr(&mut self) -> Result<Expr, String> {
        let mut left = self.parse_term()?;
        loop {
            self.skip_ws();
            match self.peek() {
                Some('+') => {
                    self.bump();
                    let right = self.parse_term()?;
                    left = Expr::Add(Box::new(left), Box::new(right));
                }
                Some('-') => {
                    self.bump();
                    let right = self.parse_term()?;
                    left = Expr::Sub(Box::new(left), Box::new(right));
                }
                _ => break,
            }
        }
        Ok(left)
    }

    fn parse_term(&mut self) -> Result<Expr, String> {
        let mut left = self.parse_unary()?;
        loop {
            self.skip_ws();
            match self.peek() {
                Some('*') => {
                    self.bump();
                    let right = self.parse_unary()?;
                    left = Expr::Mul(Box::new(left), Box::new(right));
                }
                Some('/') => {
                    self.bump();
                    let right = self.parse_unary()?;
                    left = Expr::Div(Box::new(left), Box::new(right));
                }
                _ => break,
            }
        }
        Ok(left)
    }

    fn parse_unary(&mut self) -> Result<Expr, String> {
        self.skip_ws();
        if self.peek() == Some('-') {
            self.bump();
            let e = self.parse_unary()?;
            return Ok(Expr::Neg(Box::new(e)));
        }
        if self.peek() == Some('+') {
            self.bump();
            return self.parse_unary();
        }
        self.parse_primary()
    }

    fn parse_primary(&mut self) -> Result<Expr, String> {
        self.skip_ws();
        match self.peek() {
            Some('(') => {
                self.bump();
                let e = self.parse_expr()?;
                self.skip_ws();
                if self.bump() != Some(')') {
                    return Err("expected ')'".into());
                }
                Ok(e)
            }
            Some('"') => self.parse_quoted_ref(),
            Some(c) if c.is_ascii_digit() || c == '.' => self.parse_number(),
            Some(c) if is_ident_start(c) => self.parse_ident_or_call(),
            Some(c) => Err(format!("unexpected '{c}'")),
            None => Err("unexpected end of expression".into()),
        }
    }

    fn parse_number(&mut self) -> Result<Expr, String> {
        let start = self.pos;
        while let Some(c) = self.peek() {
            if c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' {
                self.bump();
                if (c == 'e' || c == 'E') && matches!(self.peek(), Some('+') | Some('-')) {
                    self.bump();
                }
            } else {
                break;
            }
        }
        let s: String = self.chars[start..self.pos].iter().collect();
        s.parse::<f64>()
            .map(Expr::Const)
            .map_err(|_| format!("invalid number '{s}'"))
    }

    fn parse_quoted_ref(&mut self) -> Result<Expr, String> {
        self.bump(); // "
        let start = self.pos;
        while let Some(c) = self.peek() {
            if c == '"' {
                break;
            }
            self.bump();
        }
        let name: String = self.chars[start..self.pos].iter().collect();
        if self.bump() != Some('"') {
            return Err("unterminated quoted component name".into());
        }
        self.skip_ws();
        if self.bump() != Some('.') {
            return Err("expected '.' after quoted component name".into());
        }
        let field = self.parse_ident_string()?;
        Ok(Expr::Ref {
            component: name,
            field,
        })
    }

    fn parse_ident_string(&mut self) -> Result<String, String> {
        self.skip_ws();
        let start = self.pos;
        if !self.peek().is_some_and(is_ident_start) {
            return Err("expected identifier".into());
        }
        self.bump();
        while self.peek().is_some_and(is_ident_cont) {
            self.bump();
        }
        Ok(self.chars[start..self.pos].iter().collect())
    }

    fn parse_ident_or_call(&mut self) -> Result<Expr, String> {
        let ident = self.parse_ident_string()?;
        self.skip_ws();
        if self.peek() == Some('(') {
            return self.parse_call(&ident);
        }
        // component.field[.more] — last segment is field, rest is component
        let mut parts = vec![ident];
        while self.peek() == Some('.') {
            self.bump();
            parts.push(self.parse_ident_string()?);
            self.skip_ws();
        }
        if parts.len() < 2 {
            return Err(format!(
                "bare identifier '{}' — expected component.field or function call",
                parts[0]
            ));
        }
        let field = parts.pop().unwrap();
        let component = parts.join(".");
        Ok(Expr::Ref { component, field })
    }

    fn parse_call(&mut self, name: &str) -> Result<Expr, String> {
        self.bump(); // (
        self.skip_ws();
        let lower = name.to_ascii_lowercase();
        match lower.as_str() {
            "min" | "max" => {
                let a = self.parse_expr()?;
                self.skip_ws();
                if self.bump() != Some(',') {
                    return Err(format!("expected ',' in {lower}(...)"));
                }
                let b = self.parse_expr()?;
                self.skip_ws();
                if self.bump() != Some(')') {
                    return Err(format!("expected ')' after {lower}"));
                }
                Ok(if lower == "min" {
                    Expr::Min(Box::new(a), Box::new(b))
                } else {
                    Expr::Max(Box::new(a), Box::new(b))
                })
            }
            "exp" => {
                let a = self.parse_expr()?;
                self.skip_ws();
                if self.bump() != Some(')') {
                    return Err("expected ')' after exp".into());
                }
                Ok(Expr::Exp(Box::new(a)))
            }
            "ln" | "log" => {
                let a = self.parse_expr()?;
                self.skip_ws();
                if self.bump() != Some(')') {
                    return Err("expected ')' after ln".into());
                }
                Ok(Expr::Ln(Box::new(a)))
            }
            other => Err(format!(
                "unknown function '{other}' (supported: min, max, exp, ln)"
            )),
        }
    }
}

fn is_ident_start(c: char) -> bool {
    c.is_ascii_alphabetic() || c == '_'
}

fn is_ident_cont(c: char) -> bool {
    c.is_ascii_alphanumeric() || c == '_'
}

impl fmt::Display for Expr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Expr::Const(v) => write!(f, "{v}"),
            Expr::Ref { component, field } => write!(f, "{component}.{field}"),
            Expr::Neg(a) => write!(f, "-({a})"),
            Expr::Add(a, b) => write!(f, "({a} + {b})"),
            Expr::Sub(a, b) => write!(f, "({a} - {b})"),
            Expr::Mul(a, b) => write!(f, "({a} * {b})"),
            Expr::Div(a, b) => write!(f, "({a} / {b})"),
            Expr::Min(a, b) => write!(f, "min({a}, {b})"),
            Expr::Max(a, b) => write!(f, "max({a}, {b})"),
            Expr::Exp(a) => write!(f, "exp({a})"),
            Expr::Ln(a) => write!(f, "ln({a})"),
        }
    }
}

/// Algebraic model equation: `lhs - rhs = 0`.
#[derive(Debug, Clone)]
pub struct AlgebraicEquation {
    pub id: String,
    pub lhs: Expr,
    pub rhs: Expr,
}

impl AlgebraicEquation {
    pub fn residual<F>(&self, mut resolve: F) -> Result<f64, String>
    where
        F: FnMut(&str, &str) -> Result<f64, String>,
    {
        Ok(self.lhs.eval(&mut resolve)? - self.rhs.eval(&mut resolve)?)
    }
}

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

    fn eval_const(e: &Expr) -> f64 {
        e.eval(&mut |_, _| Err("no refs".into())).unwrap()
    }

    #[test]
    fn arithmetic_and_funcs() {
        assert!((eval_const(&parse_expr("1 + 2 * 3").unwrap()) - 7.0).abs() < 1e-12);
        assert!((eval_const(&parse_expr("min(2, 5)").unwrap()) - 2.0).abs() < 1e-12);
        assert!((eval_const(&parse_expr("max(2, 5)").unwrap()) - 5.0).abs() < 1e-12);
        assert!((eval_const(&parse_expr("exp(0)").unwrap()) - 1.0).abs() < 1e-12);
        assert!((eval_const(&parse_expr("ln(exp(1))").unwrap()) - 1.0).abs() < 1e-9);
        assert!((eval_const(&parse_expr("-(1+2)").unwrap()) + 3.0).abs() < 1e-12);
    }

    #[test]
    fn refs_and_quoted() {
        let e = parse_expr(r#"evap.z_ua + "SST probe".T"#).unwrap();
        let v = e
            .eval(&mut |c, f| match (c, f) {
                ("evap", "z_ua") => Ok(0.1),
                ("SST probe", "T") => Ok(280.0),
                _ => Err("bad".into()),
            })
            .unwrap();
        assert!((v - 280.1).abs() < 1e-12);
    }
}
