///|
/// Defensive limits applied while compiling untrusted rule text.
pub(all) struct CompileLimits {
  max_source_length : Int
  max_ast_nodes : Int
  max_ast_depth : Int
} derive(Eq, Debug, ToJson, FromJson)

///|
/// Conservative defaults suitable for API and CI use.
pub fn CompileLimits::default() -> CompileLimits {
  { max_source_length: 16384, max_ast_nodes: 4096, max_ast_depth: 128 }
}

///|
fn Expr::metrics(self : Expr) -> (Int, Int) {
  match self {
    Literal(_, _) | Variable(_, _) => (1, 1)
    Member(target, _, _) | OptionalMember(target, _, _) | Unary(_, target, _) => {
      let (nodes, depth) = target.metrics()
      (nodes + 1, depth + 1)
    }
    Index(target, index, _)
    | OptionalIndex(target, index, _)
    | Binary(target, _, index, _) => {
      let (left_nodes, left_depth) = target.metrics()
      let (right_nodes, right_depth) = index.metrics()
      (
        left_nodes + right_nodes + 1,
        (if left_depth > right_depth { left_depth } else { right_depth }) + 1,
      )
    }
    ArrayLiteral(items, _) | Call(_, items, _) => {
      let mut nodes = 1
      let mut depth = 1
      for item in items {
        let (child_nodes, child_depth) = item.metrics()
        nodes = nodes + child_nodes
        if child_depth + 1 > depth {
          depth = child_depth + 1
        }
      }
      (nodes, depth)
    }
  }
}

///|
fn validate_compile_limits(limits : CompileLimits) -> Result[Unit, Diagnostic] {
  if limits.max_source_length <= 0 ||
    limits.max_ast_nodes <= 0 ||
    limits.max_ast_depth <= 0 {
    Err(
      Diagnostic::new(
        Configure,
        "C010",
        "compile limits must be positive integers",
        Span::new(0, 0),
        hint="Set every compile limit to at least 1.",
      ),
    )
  } else {
    Ok(())
  }
}