// eval.mbt — Tree-walking evaluator for matcher expressions.
//
// Evaluation is deterministic and side-effect free. `&&` and `||`
// short-circuit, so `false && unknown` does not raise. `in` tests list
// membership. Field access reads fields from records. Arithmetic is
// intentionally small: `+`, `-`, and `*` keep integers when both
// operands are integers, `/` always produces a double, and `%` works on
// integers only. Unknown identifiers and function names, wrong argument
// counts, and type mismatches raise `MatcherEval` errors.

///|
/// Evaluates a matcher expression.
///
/// `lookup` resolves identifiers such as `r_sub` or `p_obj` and returns
/// `None` for unknown names; `functions` resolves calls.
pub fn Expr::eval(
  self : Expr,
  lookup : (String) -> Value?,
  functions : FunctionRegistry,
) -> Result[Value, CasbinError] {
  Ok(eval_raise(self, lookup, functions)) catch {
    error => Err(error)
  }
}

///|
fn eval_raise(
  expression : Expr,
  lookup : (String) -> Value?,
  functions : FunctionRegistry,
) -> Value raise CasbinError {
  match expression {
    Literal(value) => value
    Ident(name) =>
      match lookup(name) {
        Some(value) => value
        None =>
          raise casbin_error(MatcherEval, "no parameter \"" + name + "\" found")
      }
    List(items) => {
      let values : Array[Value] = []
      for item in items {
        values.push(eval_raise(item, lookup, functions))
      }
      Value::Array(values)
    }
    Member(target, field) =>
      match eval_raise(target, lookup, functions) {
        Value::Object(fields) =>
          match find_field(fields, field) {
            Some(value) => value
            None =>
              raise casbin_error(
                MatcherEval,
                "record has no field \"" + field + "\"",
              )
          }
        other =>
          raise casbin_error(
            MatcherEval,
            "cannot read field \"" + field + "\" of " + other.type_name(),
          )
      }
    Unary(op, operand) => eval_unary(op, operand, lookup, functions)
    Binary(op, left, right) => eval_binary(op, left, right, lookup, functions)
    Call(name, arguments) => {
      let values : Array[Value] = []
      for argument in arguments {
        values.push(eval_raise(argument, lookup, functions))
      }
      match functions.get(name) {
        Some(function) => function(values)
        None =>
          raise casbin_error(
            MatcherEval,
            "function \"" + name + "\" is not defined",
          )
      }
    }
  }
}

///|
fn eval_unary(
  op : UnaryOp,
  operand : Expr,
  lookup : (String) -> Value?,
  functions : FunctionRegistry,
) -> Value raise CasbinError {
  let value = eval_raise(operand, lookup, functions)
  match op {
    Not => Value::Bool(!require_bool(value, "!"))
    Neg =>
      match value {
        Value::Int(i) => Value::Int(-i)
        Value::Double(d) => Value::Double(-d)
        other =>
          raise casbin_error(MatcherEval, "cannot negate " + other.type_name())
      }
  }
}

///|
fn eval_binary(
  op : BinaryOp,
  left : Expr,
  right : Expr,
  lookup : (String) -> Value?,
  functions : FunctionRegistry,
) -> Value raise CasbinError {
  match op {
    Or => {
      let left_value = eval_raise(left, lookup, functions)
      if require_bool(left_value, "||") {
        Value::Bool(true)
      } else {
        Value::Bool(require_bool(eval_raise(right, lookup, functions), "||"))
      }
    }
    And => {
      let left_value = eval_raise(left, lookup, functions)
      if require_bool(left_value, "&&") {
        Value::Bool(require_bool(eval_raise(right, lookup, functions), "&&"))
      } else {
        Value::Bool(false)
      }
    }
    Eq =>
      Value::Bool(
        eval_raise(left, lookup, functions).equals(
          eval_raise(right, lookup, functions),
        ),
      )
    NotEq =>
      Value::Bool(
        !eval_raise(left, lookup, functions).equals(
          eval_raise(right, lookup, functions),
        ),
      )
    In => {
      let needle = eval_raise(left, lookup, functions)
      let haystack = eval_raise(right, lookup, functions)
      match haystack {
        Value::Array(items) => {
          let mut found = false
          for item in items {
            if needle.equals(item) {
              found = true
              break
            }
          }
          Value::Bool(found)
        }
        other =>
          raise casbin_error(
            MatcherEval,
            "right operand of \"in\" must be a list, got " + other.type_name(),
          )
      }
    }
    Less | LessEq | Greater | GreaterEq => {
      let a = eval_raise(left, lookup, functions)
      let b = eval_raise(right, lookup, functions)
      let order = compare_values(a, b)
      Value::Bool(
        match op {
          Less => order < 0
          LessEq => order <= 0
          Greater => order > 0
          _ => order >= 0
        },
      )
    }
    Add | Sub | Mul | Div | Mod => {
      let a = eval_raise(left, lookup, functions)
      let b = eval_raise(right, lookup, functions)
      eval_arithmetic(op, a, b)
    }
  }
}

///|
fn eval_arithmetic(
  op : BinaryOp,
  a : Value,
  b : Value,
) -> Value raise CasbinError {
  match op {
    Add =>
      match (a, b) {
        (Value::String(x), Value::String(y)) => Value::String(x + y)
        _ => numeric_binary(a, b, add_int, add_double, "add")
      }
    Sub => numeric_binary(a, b, sub_int, sub_double, "subtract")
    Mul => numeric_binary(a, b, mul_int, mul_double, "multiply")
    Div =>
      match (as_double(a), as_double(b)) {
        (Some(x), Some(y)) =>
          if y == 0.0 {
            raise casbin_error(MatcherEval, "division by zero")
          } else {
            Value::Double(x / y)
          }
        _ =>
          raise casbin_error(
            MatcherEval,
            "cannot divide " + a.type_name() + " by " + b.type_name(),
          )
      }
    Mod =>
      match (a, b) {
        (Value::Int(x), Value::Int(y)) =>
          if y == 0 {
            raise casbin_error(MatcherEval, "modulo by zero")
          } else {
            Value::Int(x % y)
          }
        _ =>
          raise casbin_error(
            MatcherEval,
            "cannot take the modulo of " +
            a.type_name() +
            " and " +
            b.type_name(),
          )
      }
    _ => raise casbin_error(MatcherEval, "unsupported arithmetic operator")
  }
}

///|
fn numeric_binary(
  a : Value,
  b : Value,
  int_op : (Int, Int) -> Int,
  double_op : (Double, Double) -> Double,
  name : String,
) -> Value raise CasbinError {
  match (a, b) {
    (Value::Int(x), Value::Int(y)) => Value::Int(int_op(x, y))
    (Value::Double(x), Value::Double(y)) => Value::Double(double_op(x, y))
    (Value::Int(x), Value::Double(y)) =>
      Value::Double(double_op(x.to_double(), y))
    (Value::Double(x), Value::Int(y)) =>
      Value::Double(double_op(x, y.to_double()))
    _ =>
      raise casbin_error(
        MatcherEval,
        "cannot " + name + " " + a.type_name() + " and " + b.type_name(),
      )
  }
}

///|
fn compare_values(a : Value, b : Value) -> Int raise CasbinError {
  match (a, b) {
    (Value::Int(x), Value::Int(y)) => compare_int(x, y)
    (Value::Double(x), Value::Double(y)) => compare_double(x, y)
    (Value::Int(x), Value::Double(y)) => compare_double(x.to_double(), y)
    (Value::Double(x), Value::Int(y)) => compare_double(x, y.to_double())
    (Value::String(x), Value::String(y)) => compare_string(x, y)
    _ =>
      raise casbin_error(
        MatcherEval,
        "cannot compare " + a.type_name() + " with " + b.type_name(),
      )
  }
}

///|
fn require_bool(value : Value, operator : String) -> Bool raise CasbinError {
  match value {
    Value::Bool(flag) => flag
    other =>
      raise casbin_error(
        MatcherEval,
        "operator \"" + operator + "\" requires bool, got " + other.type_name(),
      )
  }
}

///|
fn as_double(value : Value) -> Double? {
  match value {
    Value::Int(i) => Some(i.to_double())
    Value::Double(d) => Some(d)
    _ => None
  }
}

///|
fn find_field(fields : Array[(String, Value)], name : String) -> Value? {
  for field in fields {
    if field.0 == name {
      return Some(field.1)
    }
  }
  None
}

///|
fn add_int(x : Int, y : Int) -> Int {
  x + y
}

///|
fn add_double(x : Double, y : Double) -> Double {
  x + y
}

///|
fn sub_int(x : Int, y : Int) -> Int {
  x - y
}

///|
fn sub_double(x : Double, y : Double) -> Double {
  x - y
}

///|
fn mul_int(x : Int, y : Int) -> Int {
  x * y
}

///|
fn mul_double(x : Double, y : Double) -> Double {
  x * y
}

///|
fn compare_int(x : Int, y : Int) -> Int {
  if x < y {
    -1
  } else if x == y {
    0
  } else {
    1
  }
}

///|
fn compare_double(x : Double, y : Double) -> Int {
  if x < y {
    -1
  } else if x == y {
    0
  } else {
    1
  }
}

///|
fn compare_string(x : String, y : String) -> Int {
  if x < y {
    -1
  } else if x == y {
    0
  } else {
    1
  }
}