///|
fn unexpected_kwargs() -> TemplateError {
  TemplateError::new(TooManyArguments, "unexpected keyword arguments")
}

///|
/// Keyword arguments passed to a filter, test or function.
///
/// Keyword arguments are represented as a special map value which is passed
/// as the last argument.  Use [`Args::kwargs`] to extract them, then fetch
/// individual arguments with [`Kwargs::get`] (or the typed getters) and
/// finally call [`Kwargs::assert_all_used`] so that unknown keyword
/// arguments are reported.
pub struct Kwargs {
  priv values : Map[Value, Value]
  priv used : Set[String]
}

///|
/// Creates keyword arguments from name/value pairs.
pub fn Kwargs::from_pairs(pairs : Array[(String, Value)]) -> Kwargs {
  let values : Map[Value, Value] = Map([])
  for pair in pairs {
    values[Value::from_string(pair.0)] = pair.1
  }
  { values, used: Set([]), }
}

///|
/// Converts the keyword arguments back into a value.
pub fn Kwargs::to_value(self : Kwargs) -> Value {
  Value::from_kwargs_map(self.values)
}

///|
/// Given a value, extracts the kwargs if there are any.
fn Kwargs::extract(value : Value) -> Kwargs? {
  match value {
    Object({ inner: Kwargs(values), .. }) => Some({ values, used: Set([]), })
    _ => None
  }
}

///|
/// Returns the raw value of a keyword argument without marking it used.
pub fn Kwargs::peek(self : Kwargs, key : String) -> Value? {
  self.values.get(Value::from_string(key))
}

///|
/// Gets a required keyword argument (any value) and marks it as used.
pub fn Kwargs::get(self : Kwargs, key : String) -> Value raise TemplateError {
  match self.values.get(Value::from_string(key)) {
    Some(v) => {
      if v.is_kwargs() {
        raise unexpected_kwargs()
      }
      self.used.add(key)
      v
    }
    None =>
      raise TemplateError::new(
        MissingArgument,
        "missing keyword argument '\{key}'",
      )
  }
}

///|
/// Gets an optional keyword argument and marks it as used.  Undefined and
/// none values are treated as missing (like `Option`).
pub fn Kwargs::get_opt(
  self : Kwargs,
  key : String,
) -> Value? raise TemplateError {
  let rv = self.peek_opt(key)
  self.used.add(key)
  rv
}

///|
fn Kwargs::peek_opt(self : Kwargs, key : String) -> Value? raise TemplateError {
  match self.values.get(Value::from_string(key)) {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => {
      if v.is_kwargs() {
        raise unexpected_kwargs()
      }
      Some(v)
    }
    None => None
  }
}

///|
/// Converts an optional keyword argument and marks it used only if the
/// conversion succeeded (like MiniJinja's `Kwargs::get`).
fn[T] Kwargs::get_converted(
  self : Kwargs,
  key : String,
  convert : (Value) -> T raise TemplateError,
) -> T? raise TemplateError {
  let rv = match self.peek_opt(key) {
    Some(v) => Some(convert(v))
    None => None
  }
  self.used.add(key)
  rv
}

///|
/// Gets an optional boolean keyword argument.
pub fn Kwargs::get_bool(
  self : Kwargs,
  key : String,
) -> Bool? raise TemplateError {
  self.get_converted(key, value_to_bool)
}

///|
/// Gets an optional integer keyword argument.
pub fn Kwargs::get_int(
  self : Kwargs,
  key : String,
) -> Int64? raise TemplateError {
  self.get_converted(key, value_to_i64)
}

///|
/// Gets an optional non-negative integer keyword argument.
pub fn Kwargs::get_usize(
  self : Kwargs,
  key : String,
) -> Int? raise TemplateError {
  self.get_converted(key, value_to_usize)
}

///|
/// Gets an optional string keyword argument (strict: must be a string).
pub fn Kwargs::get_str(
  self : Kwargs,
  key : String,
) -> String? raise TemplateError {
  self.get_converted(key, value_to_str)
}

///|
/// Checks if a keyword argument exists.
pub fn Kwargs::has(self : Kwargs, key : String) -> Bool {
  self.values.contains(Value::from_string(key))
}

///|
/// Returns the names of all passed keyword arguments.
pub fn Kwargs::args(self : Kwargs) -> Array[String] {
  let rv = []
  for k, _ in self.values {
    if k.as_str() is Some(s) {
      rv.push(s)
    }
  }
  rv
}

///|
/// Asserts that all kwargs were used.
pub fn Kwargs::assert_all_used(self : Kwargs) -> Unit raise TemplateError {
  for key, _ in self.values {
    match key.as_str() {
      Some(key) =>
        if !self.used.contains(key) {
          raise TemplateError::new(
            TooManyArguments,
            "unknown keyword argument '\{key}'",
          )
        }
      None =>
        raise TemplateError::new(
          InvalidOperation,
          "non string keys passed to kwargs",
        )
    }
  }
}

///|
/// Port of `bool::try_from(Value)`.
fn value_to_bool(v : Value) -> Bool raise TemplateError {
  match v {
    Bool(b) => b
    _ => raise unsupported_conversion(v.kind(), "bool")
  }
}

///|
/// Port of `i64::try_from(Value)`.
fn value_to_i64(v : Value) -> Int64 raise TemplateError {
  match v.to_i128() {
    Some(i) if in_i64(i) => i.to_int64()
    _ => raise unsupported_conversion(v.kind(), "i64")
  }
}

///|
/// Port of `usize::try_from(Value)` (bounded to `Int`).
fn value_to_usize(v : Value) -> Int raise TemplateError {
  match v.to_i128() {
    Some(i) if i >= 0N && i <= 0x7FFFFFFFN => i.to_int()
    _ => raise unsupported_conversion(v.kind(), "usize")
  }
}

///|
/// Port of `i32::try_from(Value)`.
fn value_to_i32(v : Value) -> Int raise TemplateError {
  match v.to_i128() {
    Some(i) if i >= -0x80000000N && i <= 0x7FFFFFFFN => i.to_int()
    _ => raise unsupported_conversion(v.kind(), "i32")
  }
}

///|
/// Port of `f64::try_from(Value)`.
fn value_to_f64(v : Value) -> Double raise TemplateError {
  match v {
    U64(x) => x.to_double()
    I64(x) => x.to_double()
    U128(x) | I128(x) => bigint_to_f64(x)
    F64(x) => x
    _ => raise unsupported_conversion(v.kind(), "f64")
  }
}

///|
/// Port of `<&str as ArgType>` (the value must be a string).
fn value_to_str(v : Value) -> String raise TemplateError {
  match v.as_str() {
    Some(s) => s
    None => raise TemplateError::new(InvalidOperation, "value is not a string")
  }
}

///|
/// Port of `value_to_string_cow` / `String` conversion.
fn value_to_string(v : Value) -> String raise TemplateError {
  match v {
    Str(s, _) => s
    _ => {
      if v.is_kwargs() {
        raise TemplateError::new(
          InvalidOperation,
          "cannot convert kwargs to string",
        )
      }
      v.to_string()
    }
  }
}

///|
/// A string coerced from a value together with its safety provenance.
pub struct StringInput {
  priv value : String
  priv safe : Bool
}

///|
/// Coerces a value into a string while retaining its safety provenance.
pub fn StringInput::new(
  state : State,
  value : Value,
) -> StringInput raise TemplateError {
  state.undefined_behavior().assert_value_not_undefined(value)
  { value: value_to_string(value), safe: value.is_safe(), }
}

///|
/// Returns the coerced string.
pub fn StringInput::as_str(self : StringInput) -> String {
  self.value
}

///|
/// Returns `true` if the original value was marked safe.
pub fn StringInput::is_safe(self : StringInput) -> Bool {
  self.safe
}

///|
/// Formats the string for insertion into a safe result.  Safe inputs are
/// returned unchanged, other inputs are escaped like the `escape` filter.
pub fn StringInput::format(
  self : StringInput,
  state : State,
) -> String raise TemplateError {
  if self.safe {
    self.value
  } else {
    filter_escape(state, Value::from_string(self.value)).to_string()
  }
}

///|
/// Wraps a derived string while preserving the input's safety.
pub fn StringInput::preserve_safety(
  self : StringInput,
  value : String,
) -> Value {
  if self.safe {
    Value::from_safe_string(value)
  } else {
    Value::from_string(value)
  }
}

///|
/// A cursor over the arguments passed to a filter, test or function.
///
/// The conversion rules follow MiniJinja's argument conversion: missing
/// required arguments raise `MissingArgument`, leftover arguments raise
/// `TooManyArguments` in [`Args::finish`], optional arguments treat
/// `undefined` and `none` as missing, and keyword arguments (if accepted)
/// have to be extracted first with [`Args::kwargs`].
pub struct Args {
  priv state : State
  priv values : Array[Value]
  priv mut end : Int
  priv mut idx : Int
}

///|
/// Creates an argument cursor.
pub fn Args::new(state : State, values : Array[Value]) -> Args {
  { state, values, end: values.length(), idx: 0, }
}

///|
/// Extracts the trailing keyword arguments.  Must be called before any
/// positional argument is read.
pub fn Args::kwargs(self : Args) -> Kwargs {
  if self.end > 0 && Kwargs::extract(self.values[self.end - 1]) is Some(kw) {
    self.end -= 1
    kw
  } else {
    { values: Map([]), used: Set([]), }
  }
}

///|
fn Args::next_raw(self : Args) -> Value? {
  let rv = if self.idx < self.end { Some(self.values[self.idx]) } else { None }
  self.idx += 1
  rv
}

///|
/// Returns `true` if there are positional arguments left.
pub fn Args::has_more(self : Args) -> Bool {
  self.idx < self.end
}

///|
/// A required argument of any type (keyword arguments are rejected).
pub fn Args::value(self : Args) -> Value raise TemplateError {
  match self.next_raw() {
    Some(v) => {
      if v.is_kwargs() {
        raise unexpected_kwargs()
      }
      v
    }
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional argument of any type.
pub fn Args::opt_value(self : Args) -> Value? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => {
      if v.is_kwargs() {
        raise unexpected_kwargs()
      }
      Some(v)
    }
    None => None
  }
}

///|
/// A required argument that may also be keyword arguments.
pub fn Args::value_or_kwargs(self : Args) -> Value raise TemplateError {
  match self.next_raw() {
    Some(v) => v
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// A required string argument (the value is converted to a string).
pub fn Args::string(self : Args) -> String raise TemplateError {
  match self.next_raw() {
    Some(v) => {
      self.state.undefined_behavior().assert_value_not_undefined(v)
      value_to_string(v)
    }
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional string argument (the value is converted to a string).
pub fn Args::opt_string(self : Args) -> String? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_string(v))
    None => None
  }
}

///|
/// A required strict string argument (must be a string value).
pub fn Args::str(self : Args) -> String raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_str(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional strict string argument.
pub fn Args::opt_str(self : Args) -> String? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_str(v))
    None => None
  }
}

///|
/// A required string argument that retains safety provenance.
pub fn Args::string_input(self : Args) -> StringInput raise TemplateError {
  match self.next_raw() {
    Some(v) => {
      self.state.undefined_behavior().assert_value_not_undefined(v)
      { value: value_to_string(v), safe: v.is_safe(), }
    }
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional string argument that retains safety provenance.
pub fn Args::opt_string_input(self : Args) -> StringInput? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some({ value: value_to_string(v), safe: v.is_safe(), })
    None => None
  }
}

///|
/// A required 64 bit integer argument.
pub fn Args::i64(self : Args) -> Int64 raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_i64(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional 64 bit integer argument.
pub fn Args::opt_i64(self : Args) -> Int64? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_i64(v))
    None => None
  }
}

///|
/// A required 32 bit integer argument.
pub fn Args::i32(self : Args) -> Int raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_i32(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional 32 bit integer argument.
pub fn Args::opt_i32(self : Args) -> Int? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_i32(v))
    None => None
  }
}

///|
/// A required non-negative integer argument.
pub fn Args::usize(self : Args) -> Int raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_usize(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional non-negative integer argument.
pub fn Args::opt_usize(self : Args) -> Int? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_usize(v))
    None => None
  }
}

///|
/// A required boolean argument.
pub fn Args::bool(self : Args) -> Bool raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_bool(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// An optional boolean argument.
pub fn Args::opt_bool(self : Args) -> Bool? raise TemplateError {
  match self.next_raw() {
    Some(v) if v.is_undefined() || v.is_none() => None
    Some(v) => Some(value_to_bool(v))
    None => None
  }
}

///|
/// A required float argument.
pub fn Args::f64(self : Args) -> Double raise TemplateError {
  match self.next_raw() {
    Some(v) => value_to_f64(v)
    None => raise TemplateError::from_kind(MissingArgument)
  }
}

///|
/// All remaining positional arguments (keyword arguments are rejected).
pub fn Args::rest(self : Args) -> Array[Value] raise TemplateError {
  let rv = []
  while self.idx < self.end {
    let v = self.values[self.idx]
    if v.is_kwargs() {
      raise unexpected_kwargs()
    }
    rv.push(v)
    self.idx += 1
  }
  rv
}

///|
/// All remaining arguments including keyword arguments.
pub fn Args::rest_with_kwargs(self : Args) -> Array[Value] {
  let rv = []
  while self.idx < self.end {
    rv.push(self.values[self.idx])
    self.idx += 1
  }
  rv
}

///|
/// Ensures that all arguments were consumed.
pub fn Args::finish(self : Args) -> Unit raise TemplateError {
  if self.idx < self.end {
    raise TemplateError::from_kind(TooManyArguments)
  }
}