///|
/// Compile a MoonRule expression into an immutable program.
pub fn compile(source : String) -> Result[Program, Diagnostic] {
  compile_with_limits(source, CompileLimits::default())
}

///|
/// Compile an expression while enforcing limits suitable for untrusted input.
pub fn compile_with_limits(
  source : String,
  limits : CompileLimits,
) -> Result[Program, Diagnostic] {
  match validate_compile_limits(limits) {
    Err(diagnostic) => return Err(diagnostic)
    Ok(_) => ()
  }
  if source.length() > limits.max_source_length {
    return Err(
      Diagnostic::new(
        Parse,
        "P020",
        "rule source exceeds the configured length limit",
        Span::new(0, source.length()),
        hint="Shorten the expression or raise max_source_length explicitly.",
      ),
    )
  }
  let parsed : Result[Program, Diagnostic] = Ok(parse_program(source)) catch {
    RuleFailure(diagnostic) => Err(diagnostic)
  }
  match parsed {
    Err(diagnostic) => Err(diagnostic)
    Ok(program) =>
      if program.node_count() > limits.max_ast_nodes {
        Err(
          Diagnostic::new(
            Parse,
            "P021",
            "rule AST exceeds the configured node limit",
            program.span(),
            hint="Simplify the expression or raise max_ast_nodes explicitly.",
          ),
        )
      } else if program.ast_depth() > limits.max_ast_depth {
        Err(
          Diagnostic::new(
            Parse,
            "P022",
            "rule AST exceeds the configured depth limit",
            program.span(),
            hint="Reduce expression nesting or raise max_ast_depth explicitly.",
          ),
        )
      } else {
        Ok(program)
      }
  }
}

///|
/// Evaluate a compiled program against a JSON context.
pub fn evaluate(program : Program, context : Json) -> Result[Json, Diagnostic] {
  evaluate_with_limits(program, context, EvaluationLimits::default())
}

///|
/// Evaluate a compiled program with an explicit execution-step budget.
pub fn evaluate_with_limits(
  program : Program,
  context : Json,
  limits : EvaluationLimits,
) -> Result[Json, Diagnostic] {
  match validate_evaluation_limits(limits) {
    Err(diagnostic) => return Err(diagnostic)
    Ok(_) => ()
  }
  let budget = EvaluationBudget::new(limits)
  Ok(evaluate_expr(program.root, context, budget)) catch {
    RuleFailure(diagnostic) => Err(diagnostic)
  }
}

///|
/// Compile and evaluate a rule that must produce a boolean decision.
pub fn check(source : String, context : Json) -> Result[Bool, Diagnostic] {
  match compile(source) {
    Err(diagnostic) => Err(diagnostic)
    Ok(program) =>
      match evaluate(program, context) {
        Err(diagnostic) => Err(diagnostic)
        Ok(True) => Ok(true)
        Ok(False) => Ok(false)
        Ok(other) =>
          Err(
            Diagnostic::new(
              Evaluate,
              "E022",
              "rule returned \{value_kind(other)} instead of boolean",
              program.span(),
              hint="Finish the expression with a comparison or boolean operator.",
            ),
          )
      }
  }
}