///|
/// Limits applied while executing a compiled rule.
///
/// The step budget makes evaluation time predictable for rules accepted from
/// external users. A step is consumed whenever an AST node is visited.
pub(all) struct EvaluationLimits {
  max_steps : Int
} derive(Eq, Debug, ToJson, FromJson)

///|
/// A conservative budget that is far above ordinary API validation rules.
pub fn EvaluationLimits::default() -> EvaluationLimits {
  { max_steps: 100000 }
}

///|
priv struct EvaluationBudget {
  max_steps : Int
  mut consumed_steps : Int
}

///|
fn EvaluationBudget::new(limits : EvaluationLimits) -> EvaluationBudget {
  { max_steps: limits.max_steps, consumed_steps: 0 }
}

///|
fn EvaluationBudget::consume(
  self : EvaluationBudget,
  span : Span,
) -> Unit raise RuleFailure {
  self.consumed_steps = self.consumed_steps + 1
  if self.consumed_steps > self.max_steps {
    eval_fail(
      "E024",
      "evaluation exceeded the configured step limit",
      span,
      hint="Simplify the expression or explicitly raise max_steps for trusted rules.",
    )
  }
}

///|
fn validate_evaluation_limits(
  limits : EvaluationLimits,
) -> Result[Unit, Diagnostic] {
  if limits.max_steps <= 0 {
    Err(
      Diagnostic::new(
        Configure,
        "C011",
        "evaluation step limit must be a positive integer",
        Span::new(0, 0),
        hint="Set max_steps to at least 1.",
      ),
    )
  } else {
    Ok(())
  }
}