///|
/// The cost of a single macro call against the stack limit.
let macro_recursion_cost = 4

///|
/// The cost of a single include against the stack limit.
let include_recursion_cost = 10

///|
priv struct MacroObject {
  name : Value
  arg_spec : Array[Value]
  instructions : Instructions
  offset : Int
  state_id : Int
  closure : Closure?
  caller_reference : Bool
}

///|
fn MacroObject::prepare_args(
  self : MacroObject,
  args : Array[Value],
) -> (Array[Value], Value?) raise TemplateError {
  let (args, kwargs) = match args.last() {
    Some(Object({ inner: Kwargs(kw), .. })) =>
      (args[0:args.length() - 1].to_owned(), Some(kw))
    _ => (args, None)
  }
  if args.length() > self.arg_spec.length() {
    raise TemplateError::from_kind(TooManyArguments)
  }
  let kwargs_used : Set[String] = Set([])
  let arg_values = []
  for idx, name in self.arg_spec {
    guard name.as_str() is Some(name) else {
      arg_values.push(Value::undefined())
      continue
    }
    let kwarg = match kwargs {
      Some(kw) => kw.get(Value::from_string(name))
      None => None
    }
    match (args.get(idx), kwarg) {
      (Some(_), Some(_)) =>
        raise TemplateError::new(
          TooManyArguments,
          "duplicate argument `\{name}`",
        )
      (Some(arg), None) => arg_values.push(arg)
      (None, Some(kwarg)) => {
        kwargs_used.add(name)
        arg_values.push(kwarg)
      }
      (None, None) => arg_values.push(Value::undefined())
    }
  }
  let caller = if self.caller_reference {
    kwargs_used.add("caller")
    Some(
      match kwargs {
        Some(kw) =>
          kw.get(Value::from_string("caller")).unwrap_or(Value::undefined())
        None => Value::undefined()
      },
    )
  } else {
    None
  }
  if kwargs is Some(kw) {
    for key, _ in kw {
      if key.as_str() is Some(key) && !kwargs_used.contains(key) {
        raise TemplateError::new(
          TooManyArguments,
          "unknown keyword argument `\{key}`",
        )
      }
    }
  }
  (arg_values, caller)
}

///|
impl Object for MacroObject with fn enumerate(_self) {
  Str(["name", "arguments", "caller"])
}

///|
impl Object for MacroObject with fn get_value(self, key) {
  match key.as_str() {
    Some("name") => Some(self.name)
    Some("arguments") => Some(Value::from_array(self.arg_spec.copy()))
    Some("caller") => Some(Value::from_bool(self.caller_reference))
    _ => None
  }
}

///|
impl Object for MacroObject with fn call(self, state, args) {
  if state.id != self.state_id {
    raise TemplateError::new(
      InvalidOperation,
      "cannot call this macro. template state went away.",
    )
  }
  let (arg_values, caller) = self.prepare_args(args)
  let buf = StringBuilder()
  eval_macro(
    state,
    self.instructions,
    self.offset,
    Output::new(buf),
    self.closure,
    caller,
    arg_values,
  )
  |> ignore
  if state.auto_escape is NoEscape {
    Value::from_string(buf.to_string())
  } else {
    Value::from_safe_string(buf.to_string())
  }
}

///|
impl Object for MacroObject with fn render(self) {
  Some("")
}

///|
/// The object behind `{% import ... as module %}`.
priv struct ModuleObject {
  values : Map[Value, Value]
  captured : Value
}

///|
impl Object for ModuleObject with fn get_value(self, key) {
  self.values.get(key)
}

///|
impl Object for ModuleObject with fn enumerate(self) {
  Values(self.values.keys().to_array())
}

///|
impl Object for ModuleObject with fn render(self) {
  Some(self.captured.to_string())
}