// FILE: src/parser/interpreter_numeric.rs

use crate::parser::types::Value;
use super::Interpreter;

#[derive(Debug, Clone, Copy)]
enum Num {
    I(i32),
    L(i64),
    F(f64),
}

impl Interpreter {
    fn unify_numeric(a: Value, b: Value) -> Result<(Num, Num), String> {
        let rank_a = Interpreter::numeric_rank(&a);
        let rank_b = Interpreter::numeric_rank(&b);
        if rank_a == 0 || rank_b == 0 {
            return Err("Not numeric".to_string());
        }
        let top = std::cmp::max(rank_a, rank_b);
        let na = Interpreter::coerce_value(a, top)?;
        let nb = Interpreter::coerce_value(b, top)?;
        Ok((na, nb))
    }

    fn numeric_rank(v: &Value) -> i32 {
        match v {
            Value::Int(_) => 1,
            Value::Long(_) => 2,
            Value::Float(_) => 3,
            _ => 0,
        }
    }

    fn coerce_value(v: Value, rank: i32) -> Result<Num, String> {
        match v {
            Value::Int(i) => match rank {
                3 => Ok(Num::F(i as f64)),
                2 => Ok(Num::L(i as i64)),
                1 => Ok(Num::I(i)),
                _ => Err("coerce => invalid rank".to_string()),
            },
            Value::Long(l) => match rank {
                3 => Ok(Num::F(l as f64)),
                2 => Ok(Num::L(l)),
                1 => {
                    if l < i64::from(std::i32::MIN) || l > i64::from(std::i32::MAX) {
                        return Err("Long->Int overflow".to_string());
                    }
                    Ok(Num::I(l as i32))
                }
                _ => Err("coerce => invalid rank".to_string()),
            },
            Value::Float(ff) => match rank {
                3 => Ok(Num::F(ff)),
                2 => Ok(Num::L(ff as i64)),
                1 => Ok(Num::I(ff as i32)),
                _ => Err("coerce_value => invalid rank".to_string()),
            },
            _ => Err("coerce_value => not numeric".to_string()),
        }
    }

    pub fn eval_gt(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        let res = match (lt, rt) {
            (Num::I(a), Num::I(b)) => a > b,
            (Num::L(a), Num::L(b)) => a > b,
            (Num::F(a), Num::F(b)) => a > b,
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        };
        Ok(Value::Bool(res))
    }

    pub fn eval_lt(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        let res = match (lt, rt) {
            (Num::I(a), Num::I(b)) => a < b,
            (Num::L(a), Num::L(b)) => a < b,
            (Num::F(a), Num::F(b)) => a < b,
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        };
        Ok(Value::Bool(res))
    }

    pub fn eval_le(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        let res = match (lt, rt) {
            (Num::I(a), Num::I(b)) => a <= b,
            (Num::L(a), Num::L(b)) => a <= b,
            (Num::F(a), Num::F(b)) => a <= b,
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        };
        Ok(Value::Bool(res))
    }

    pub fn eval_ge(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        let res = match (lt, rt) {
            (Num::I(a), Num::I(b)) => a >= b,
            (Num::L(a), Num::L(b)) => a >= b,
            (Num::F(a), Num::F(b)) => a >= b,
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        };
        Ok(Value::Bool(res))
    }

    pub fn eval_eqeq(&self, lv: Value, rv: Value) -> Result<Value, String> {
        if let Ok((lt, rt)) = Interpreter::unify_numeric(lv.clone(), rv.clone()) {
            return match (lt, rt) {
                (Num::I(a), Num::I(b)) => Ok(Value::Bool(a == b)),
                (Num::L(a), Num::L(b)) => Ok(Value::Bool(a == b)),
                (Num::F(a), Num::F(b)) => Ok(Value::Bool(a == b)),
                _ => unreachable!("unify_numeric => same rank => no cross combos"),
            };
        }
        Ok(Value::Bool(lv == rv))
    }

    pub fn eval_ne(&self, lv: Value, rv: Value) -> Result<Value, String> {
        if let Ok((lt, rt)) = Interpreter::unify_numeric(lv.clone(), rv.clone()) {
            return match (lt, rt) {
                (Num::I(a), Num::I(b)) => Ok(Value::Bool(a != b)),
                (Num::L(a), Num::L(b)) => Ok(Value::Bool(a != b)),
                (Num::F(a), Num::F(b)) => Ok(Value::Bool((a - b).abs() > std::f64::EPSILON)),
                _ => unreachable!("unify_numeric => same rank => no cross combos"),
            };
        }
        Ok(Value::Bool(lv != rv))
    }

    pub fn eval_add(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                a.checked_add(b).map(Value::Int).ok_or_else(|| "Overflow in i32 add".to_string())
            }
            (Num::L(a), Num::L(b)) => {
                a.checked_add(b).map(Value::Long).ok_or_else(|| "Overflow in i64 add".to_string())
            }
            (Num::F(a), Num::F(b)) => Ok(Value::Float(a + b)),
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        }
    }

    pub fn eval_sub(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                a.checked_sub(b).map(Value::Int).ok_or_else(|| "Overflow in i32 sub".to_string())
            }
            (Num::L(a), Num::L(b)) => {
                a.checked_sub(b).map(Value::Long).ok_or_else(|| "Overflow in i64 sub".to_string())
            }
            (Num::F(a), Num::F(b)) => Ok(Value::Float(a - b)),
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        }
    }

    pub fn eval_mul(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                a.checked_mul(b).map(Value::Int).ok_or_else(|| "Overflow in i32 mul".to_string())
            }
            (Num::L(a), Num::L(b)) => {
                a.checked_mul(b).map(Value::Long).ok_or_else(|| "Overflow in i64 mul".to_string())
            }
            (Num::F(a), Num::F(b)) => Ok(Value::Float(a * b)),
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        }
    }

    pub fn eval_div(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(_), Num::I(0)) => Err("Division by zero".to_string()),
            (Num::L(_), Num::L(0)) => Err("Division by zero".to_string()),
            (Num::F(_), Num::F(bf)) if bf == 0.0 => Err("Division by zero".to_string()),
            (Num::I(a), Num::I(b)) => Ok(Value::Int(a / b)),
            (Num::L(a), Num::L(b)) => Ok(Value::Long(a / b)),
            (Num::F(a), Num::F(b)) => Ok(Value::Float(a / b)),
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        }
    }

    pub fn eval_mod(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(_), Num::I(0)) => Err("Mod by zero".to_string()),
            (Num::L(_), Num::L(0)) => Err("Mod by zero".to_string()),
            (Num::F(_), Num::F(bf)) if bf == 0.0 => Err("Mod by zero".to_string()),
            (Num::I(a), Num::I(b)) => Ok(Value::Int(a % b)),
            (Num::L(a), Num::L(b)) => Ok(Value::Long(a % b)),
            (Num::F(a), Num::F(b)) => Ok(Value::Float(a % b)),
            _ => unreachable!("unify_numeric => same rank => no cross combos"),
        }
    }

    pub fn eval_bitand(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => Ok(Value::Int(a & b)),
            (Num::L(a), Num::L(b)) => Ok(Value::Long(a & b)),
            _ => Err("bitwise & is not defined for floats".to_string()),
        }
    }

    pub fn eval_shl(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                if b < 0 || b >= 32 {
                    return Err(format!("invalid shift amount: {}", b));
                }
                Ok(Value::Int(a << b))
            }
            (Num::L(a), Num::L(b)) => {
                if b < 0 || b >= 64 {
                    return Err(format!("invalid shift amount: {}", b));
                }
                Ok(Value::Long(a << b))
            }
            _ => Err("Cannot shift a float or shift by float".to_string()),
        }
    }

    pub fn eval_shr(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                if b < 0 || b >= 32 {
                    return Err(format!("invalid shift amount: {}", b));
                }
                Ok(Value::Int((a as u32 >> b) as i32))
            }
            (Num::L(a), Num::L(b)) => {
                if b < 0 || b >= 64 {
                    return Err(format!("invalid shift amount: {}", b));
                }
                Ok(Value::Long((a as u64 >> b) as i64))
            }
            _ => Err("Cannot shift a float or shift by float".to_string()),
        }
    }

    pub fn eval_bitxor(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => Ok(Value::Int(a ^ b)),
            (Num::L(a), Num::L(b)) => Ok(Value::Long(a ^ b)),
            _ => Err("bitwise ^ is not defined for floats".to_string()),
        }
    }

    pub fn eval_bittilde(&self, lv: Value, rv: Value) -> Result<Value, String> {
        // interpret "~" as NAND => ~(a & b)
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => {
                let nand = !(a & b);
                Ok(Value::Int(nand))
            }
            (Num::L(a), Num::L(b)) => {
                let nand = !(a & b);
                Ok(Value::Long(nand))
            }
            _ => Err("bitwise ~ is not defined for floats".to_string()),
        }
    }

    pub fn eval_bitor(&self, lv: Value, rv: Value) -> Result<Value, String> {
        let (lt, rt) = Interpreter::unify_numeric(lv, rv)?;
        match (lt, rt) {
            (Num::I(a), Num::I(b)) => Ok(Value::Int(a | b)),
            (Num::L(a), Num::L(b)) => Ok(Value::Long(a | b)),
            _ => Err("bitwise | is not defined for floats".to_string()),
        }
    }
}
