//! parser.rs - Replica deliberada de `ast_sipi.py`: misma precedencia
//! (or < and < igualdad < comparacion < suma/resta < mult/div/modulo <
//! unario < primario), mismos alias `y`/`o`/`no` (ya resueltos por el
//! lexer a `and`/`or`/`not`), y misma semantica de evaluacion (texto +
//! numero concatena, division/modulo por cero son error, cortocircuito
//! real en `and`/`or`). A proposito NO incluye llamadas a funcion ni
//! control de flujo: el alcance declarado de este prototipo es
//! "tokeniza y evalua expresiones", igual que documenta el README de
//! esta carpeta.

use crate::lexer::{tokenizar, Token};
use std::collections::HashMap;
use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
    Numero(f64),
    Texto(String),
    Booleano(bool),
    Nulo,
    Variable(String),
    Unario(String, Box<Expr>),
    Binario(String, Box<Expr>, Box<Expr>),
}

#[derive(Debug, Clone, PartialEq)]
pub enum Valor {
    Numero(f64),
    Texto(String),
    Booleano(bool),
    Nulo,
}

impl fmt::Display for Valor {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Valor::Numero(n) => {
                if n.fract() == 0.0 && n.is_finite() {
                    write!(f, "{}", *n as i64)
                } else {
                    write!(f, "{}", n)
                }
            }
            Valor::Texto(s) => write!(f, "{}", s),
            Valor::Booleano(b) => write!(f, "{}", if *b { "verdadero" } else { "falso" }),
            Valor::Nulo => write!(f, "nulo"),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct ErrorSintactico(pub String);
#[derive(Debug, Clone, PartialEq)]
pub struct ErrorEvaluacion(pub String);

impl fmt::Display for ErrorSintactico {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}
impl fmt::Display for ErrorEvaluacion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

struct Parser {
    tokens: Vec<Token>,
    pos: usize,
}

impl Parser {
    fn actual(&self) -> Option<&Token> {
        self.tokens.get(self.pos)
    }

    fn avanzar(&mut self) -> Option<Token> {
        let t = self.tokens.get(self.pos).cloned();
        self.pos += 1;
        t
    }

    fn es_operador(&self, esperado: &str) -> bool {
        matches!(self.actual(), Some(Token::Operador(op)) if op == esperado)
    }

    // or
    fn expresion(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.y_logico()?;
        while self.es_operador("or") {
            self.avanzar();
            let der = self.y_logico()?;
            izq = Expr::Binario("or".to_string(), Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // and
    fn y_logico(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.igualdad()?;
        while self.es_operador("and") {
            self.avanzar();
            let der = self.igualdad()?;
            izq = Expr::Binario("and".to_string(), Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // == !=
    fn igualdad(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.comparacion()?;
        loop {
            let op = match self.actual() {
                Some(Token::Operador(o)) if o == "==" || o == "!=" => o.clone(),
                _ => break,
            };
            self.avanzar();
            let der = self.comparacion()?;
            izq = Expr::Binario(op, Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // < <= > >=
    fn comparacion(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.suma()?;
        loop {
            let op = match self.actual() {
                Some(Token::Operador(o)) if ["<", "<=", ">", ">="].contains(&o.as_str()) => o.clone(),
                _ => break,
            };
            self.avanzar();
            let der = self.suma()?;
            izq = Expr::Binario(op, Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // + -
    fn suma(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.termino()?;
        loop {
            let op = match self.actual() {
                Some(Token::Operador(o)) if o == "+" || o == "-" => o.clone(),
                _ => break,
            };
            self.avanzar();
            let der = self.termino()?;
            izq = Expr::Binario(op, Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // * / %
    fn termino(&mut self) -> Result<Expr, ErrorSintactico> {
        let mut izq = self.unario()?;
        loop {
            let op = match self.actual() {
                Some(Token::Operador(o)) if o == "*" || o == "/" || o == "%" => o.clone(),
                _ => break,
            };
            self.avanzar();
            let der = self.unario()?;
            izq = Expr::Binario(op, Box::new(izq), Box::new(der));
        }
        Ok(izq)
    }

    // - + not (prefijo, encadenable)
    fn unario(&mut self) -> Result<Expr, ErrorSintactico> {
        if let Some(Token::Operador(op)) = self.actual() {
            if op == "-" || op == "+" || op == "not" {
                let op = op.clone();
                self.avanzar();
                let operando = self.unario()?;
                return Ok(Expr::Unario(op, Box::new(operando)));
            }
        }
        self.primario()
    }

    fn primario(&mut self) -> Result<Expr, ErrorSintactico> {
        match self.avanzar() {
            Some(Token::Numero(n)) => Ok(Expr::Numero(n)),
            Some(Token::Texto(s)) => Ok(Expr::Texto(s)),
            Some(Token::Identificador(nombre)) => match nombre.as_str() {
                "verdadero" => Ok(Expr::Booleano(true)),
                "falso" => Ok(Expr::Booleano(false)),
                "nulo" => Ok(Expr::Nulo),
                _ => Ok(Expr::Variable(nombre)),
            },
            Some(Token::ParentesisAbre) => {
                let interior = self.expresion()?;
                if !matches!(self.actual(), Some(Token::ParentesisCierra)) {
                    return Err(ErrorSintactico("Falta ')' para cerrar el parentesis".to_string()));
                }
                self.avanzar();
                Ok(interior)
            }
            otro => Err(ErrorSintactico(format!(
                "Se esperaba un valor, se encontro: {:?}",
                otro
            ))),
        }
    }
}

pub fn parsear(texto: &str) -> Result<Expr, ErrorSintactico> {
    let tokens = tokenizar(texto).map_err(|e| ErrorSintactico(e.to_string()))?;
    let mut parser = Parser { tokens, pos: 0 };
    let expr = parser.expresion()?;
    if parser.pos != parser.tokens.len() {
        return Err(ErrorSintactico(format!(
            "Token(s) sobrante(s) despues de la expresion, a partir de la posicion {}",
            parser.pos
        )));
    }
    Ok(expr)
}

fn es_verdadero(v: &Valor) -> bool {
    match v {
        Valor::Booleano(b) => *b,
        Valor::Nulo => false,
        Valor::Numero(n) => *n != 0.0,
        Valor::Texto(s) => !s.is_empty(),
    }
}

fn texto_de(v: &Valor) -> String {
    format!("{}", v)
}

pub fn evaluar(expr: &Expr, variables: &HashMap<String, Valor>) -> Result<Valor, ErrorEvaluacion> {
    match expr {
        Expr::Numero(n) => Ok(Valor::Numero(*n)),
        Expr::Texto(s) => Ok(Valor::Texto(s.clone())),
        Expr::Booleano(b) => Ok(Valor::Booleano(*b)),
        Expr::Nulo => Ok(Valor::Nulo),
        Expr::Variable(nombre) => variables
            .get(nombre)
            .cloned()
            .ok_or_else(|| ErrorEvaluacion(format!("Variable no declarada: '{}'", nombre))),
        Expr::Unario(op, operando) => {
            let v = evaluar(operando, variables)?;
            match op.as_str() {
                "-" => match v {
                    Valor::Numero(n) => Ok(Valor::Numero(-n)),
                    _ => Err(ErrorEvaluacion("Operador unario '-' requiere un numero".to_string())),
                },
                "+" => match v {
                    Valor::Numero(n) => Ok(Valor::Numero(n)),
                    _ => Err(ErrorEvaluacion("Operador unario '+' requiere un numero".to_string())),
                },
                "not" => Ok(Valor::Booleano(!es_verdadero(&v))),
                otro => Err(ErrorEvaluacion(format!("Operador unario desconocido: {}", otro))),
            }
        }
        Expr::Binario(op, izq_expr, der_expr) => {
            // Cortocircuito real: el lado derecho de and/or ni se evalua
            // si ya no hace falta.
            if op == "and" {
                let izq = evaluar(izq_expr, variables)?;
                return if !es_verdadero(&izq) {
                    Ok(izq)
                } else {
                    evaluar(der_expr, variables)
                };
            }
            if op == "or" {
                let izq = evaluar(izq_expr, variables)?;
                return if es_verdadero(&izq) {
                    Ok(izq)
                } else {
                    evaluar(der_expr, variables)
                };
            }

            let izq = evaluar(izq_expr, variables)?;
            let der = evaluar(der_expr, variables)?;

            match op.as_str() {
                "+" => {
                    if matches!(izq, Valor::Texto(_)) || matches!(der, Valor::Texto(_)) {
                        Ok(Valor::Texto(format!("{}{}", texto_de(&izq), texto_de(&der))))
                    } else {
                        Ok(Valor::Numero(num(&izq)? + num(&der)?))
                    }
                }
                "-" => Ok(Valor::Numero(num(&izq)? - num(&der)?)),
                "*" => Ok(Valor::Numero(num(&izq)? * num(&der)?)),
                "/" => {
                    let (a, b) = (num(&izq)?, num(&der)?);
                    if b == 0.0 {
                        Err(ErrorEvaluacion(format!(
                            "Division por cero al evaluar la expresion '{} / {}'.",
                            Valor::Numero(a),
                            Valor::Numero(b)
                        )))
                    } else {
                        Ok(Valor::Numero(a / b))
                    }
                }
                "%" => {
                    let (a, b) = (num(&izq)?, num(&der)?);
                    if b == 0.0 {
                        Err(ErrorEvaluacion(format!(
                            "Division por cero al evaluar la expresion '{} % {}'.",
                            Valor::Numero(a),
                            Valor::Numero(b)
                        )))
                    } else {
                        Ok(Valor::Numero(a % b))
                    }
                }
                "==" => Ok(Valor::Booleano(izq == der)),
                "!=" => Ok(Valor::Booleano(izq != der)),
                "<" => Ok(Valor::Booleano(num(&izq)? < num(&der)?)),
                "<=" => Ok(Valor::Booleano(num(&izq)? <= num(&der)?)),
                ">" => Ok(Valor::Booleano(num(&izq)? > num(&der)?)),
                ">=" => Ok(Valor::Booleano(num(&izq)? >= num(&der)?)),
                otro => Err(ErrorEvaluacion(format!("Operador desconocido: {}", otro))),
            }
        }
    }
}

fn num(v: &Valor) -> Result<f64, ErrorEvaluacion> {
    match v {
        Valor::Numero(n) => Ok(*n),
        otro => Err(ErrorEvaluacion(format!("Se esperaba un numero, se encontro: {}", otro))),
    }
}

pub fn evaluar_texto(texto: &str, variables: &HashMap<String, Valor>) -> Result<Valor, String> {
    let expr = parsear(texto).map_err(|e| e.to_string())?;
    evaluar(&expr, variables).map_err(|e| e.to_string())
}

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

    fn sin_vars() -> HashMap<String, Valor> {
        HashMap::new()
    }

    #[test]
    fn precedencia_multiplicacion_antes_que_suma() {
        let expr = parsear("2 + 3 * 4").unwrap();
        match expr {
            Expr::Binario(op, _, der) => {
                assert_eq!(op, "+");
                assert!(matches!(*der, Expr::Binario(ref o, _, _) if o == "*"));
            }
            _ => panic!("se esperaba Binario"),
        }
    }

    #[test]
    fn parentesis_cambian_precedencia() {
        let expr = parsear("(2 + 3) * 4").unwrap();
        match expr {
            Expr::Binario(op, izq, _) => {
                assert_eq!(op, "*");
                assert!(matches!(*izq, Expr::Binario(ref o, _, _) if o == "+"));
            }
            _ => panic!("se esperaba Binario"),
        }
    }

    #[test]
    fn aritmetica_basica() {
        assert_eq!(evaluar_texto("2 + 3 * 4", &sin_vars()).unwrap(), Valor::Numero(14.0));
    }

    #[test]
    fn division_por_cero_es_error() {
        assert!(evaluar_texto("5 / 0", &sin_vars()).is_err());
    }

    #[test]
    fn concatenacion_texto_y_numero() {
        let r = evaluar_texto("\"Puntaje: \" + 10", &sin_vars()).unwrap();
        assert_eq!(r, Valor::Texto("Puntaje: 10".to_string()));
    }

    #[test]
    fn variables_inyectadas() {
        let mut vars = HashMap::new();
        vars.insert("x".to_string(), Valor::Numero(2.0));
        vars.insert("z".to_string(), Valor::Numero(3.0));
        assert_eq!(evaluar_texto("x + z", &vars).unwrap(), Valor::Numero(5.0));
    }

    #[test]
    fn variable_no_declarada_es_error() {
        assert!(evaluar_texto("x + 1", &sin_vars()).is_err());
    }

    #[test]
    fn cortocircuito_and_no_evalua_lado_derecho() {
        let mut vars = HashMap::new();
        vars.insert("falso".to_string(), Valor::Booleano(false));
        // 'f()' no es una llamada valida en este prototipo (a proposito
        // no soporta llamadas) -- si el cortocircuito fallara, esto
        // devolveria un error en vez de 'falso'.
        let r = evaluar_texto("falso and (1/0)", &vars).unwrap();
        assert_eq!(r, Valor::Booleano(false));
    }

    #[test]
    fn cortocircuito_or_no_evalua_lado_derecho() {
        let mut vars = HashMap::new();
        vars.insert("verdadero".to_string(), Valor::Booleano(true));
        let r = evaluar_texto("verdadero or (1/0)", &vars).unwrap();
        assert_eq!(r, Valor::Booleano(true));
    }

    #[test]
    fn comparaciones() {
        assert_eq!(evaluar_texto("5 > 3", &sin_vars()).unwrap(), Valor::Booleano(true));
        assert_eq!(evaluar_texto("5 < 3", &sin_vars()).unwrap(), Valor::Booleano(false));
        assert_eq!(evaluar_texto("5 == 5", &sin_vars()).unwrap(), Valor::Booleano(true));
    }

    #[test]
    fn unarios_encadenados() {
        assert_eq!(evaluar_texto("- - 5", &sin_vars()).unwrap(), Valor::Numero(5.0));
    }

    #[test]
    fn error_sintactico_parentesis_sin_cerrar() {
        assert!(parsear("(1 + 2").is_err());
    }

    #[test]
    fn error_sintactico_token_sobrante() {
        assert!(parsear("1 + 2 3").is_err());
    }
}
