///|
/// A validated program with fixed source identity. Its AST is isolated and can be
/// reused across evaluations; values, imports and resource counters are not shared.
pub struct Program {
  priv expression : Expr
  priv filename : String
  priv anonymous : Bool
  priv source_bytes : Int
}

///|
/// Parse and statically validate source without evaluating it or resolving imports.
/// Creation enforces source and parser/AST depth limits. Evaluation rechecks source
/// bytes and AST depth against its own limits, without reparsing the source.
///
/// ```mbt check
/// test {
///   let program = @moonjsonnet.Program::parse("std.extVar('x') * 2")
///   assert_eq(program.evaluate(ext_vars=Map([("x", 21)])), 42)
///   assert_eq(program.evaluate(ext_vars=Map([("x", 3)])), 6)
/// }
/// ```
pub fn Program::parse(
  source : String,
  filename? : String = "",
  anonymous? : Bool = false,
  limits? : Limits = Limits::default(),
) -> Program raise JsonnetError {
  let loc : Location = { file: filename, line: 1, column: 1, }
  try {
    check_limits(limits, loc)
    let source_bytes = @utf8.encode(source).length()
    if source_bytes > limits.source_bytes {
      raise problem(Limit, loc, "source byte limit exceeded")
    }
    {
      expression: parse(source, filename, limits.depth),
      filename,
      anonymous,
      source_bytes,
    }
  } catch {
    Error(d) => raise Error(d)
    e =>
      raise Error({
        kind: Runtime,
        location: loc,
        message: e.to_string(),
        context: [],
        trace: [],
      })
  }
}

///|
/// Return an editable deep copy of the program's semantic AST. Mutating this
/// tree cannot alter the program, imported caches, or another returned copy.
pub fn Program::ast(self : Program) -> Ast {
  export_ast(self.expression)
}

///|
/// Copy and validate a manually constructed or edited AST without execution.
/// Depth and steps bound conversion, including cycles in caller-owned arrays.
/// There is no source text byte count for a synthetic AST. Evaluation retains
/// all ordinary runtime limits. Filename defaults to the root location's file;
/// individual node locations are retained for diagnostics.
///
/// ```mbt check
/// test {
///   let tree = @moonjsonnet.Program::parse("21").ast()
///   let edited = { ..tree, kind: @moonjsonnet.AstKind::Binary("*", tree, tree), }
///   assert_eq(@moonjsonnet.Program::from_ast(edited).evaluate(), 441)
/// }
/// ```
pub fn Program::from_ast(
  tree : Ast,
  filename? : String,
  anonymous? : Bool = false,
  limits? : Limits = Limits::default(),
  assertion_ranges? : Map[String, SourceRange] = Map([]),
) -> Program raise JsonnetError {
  try {
    check_limits(limits, tree.loc)
    let reader : AstReader = { limits, nodes: 0, assertion_ranges, }
    let expression = reader.read(tree, 0)
    validate(expression, ["std"], false)
    {
      expression,
      filename: filename.unwrap_or(tree.loc.file),
      anonymous,
      source_bytes: 0,
    }
  } catch {
    Error(d) => raise Error(d)
    e => raise problem(Runtime, tree.loc, e.to_string())
  }
}

///|
/// Copy a tree (including a subtree with free variables), checking shape and
/// depth/node budgets. Scope validation happens when constructing a Program.
pub fn Ast::clone(
  self : Ast,
  limits? : Limits = Limits::default(),
) -> Ast raise JsonnetError {
  check_limits(limits, self.loc)
  let reader : AstReader = { limits, nodes: 0, assertion_ranges: Map([]), }
  export_ast(reader.read(self, 0))
}