///|
fn func_range(state : State, args : Array[Value]) -> Value raise TemplateError {
  let a = Args::new(state, args)
  let lower = a.i64()
  let upper = a.opt_i64()
  let step = a.opt_i64()
  a.finish()
  let (start, end) = match upper {
    Some(upper) => (lower, upper)
    None => (0L, lower)
  }
  let step = step.unwrap_or(1L)
  if step == 0L {
    raise TemplateError::new(
      InvalidOperation,
      "cannot create range with step of 0",
    )
  }
  // compute the length with unsigned arithmetic so that large ranges do
  // not overflow
  let len : UInt64 = if step > 0L {
    if end > start {
      let distance = (end - start).reinterpret_as_uint64()
      let step = step.reinterpret_as_uint64()
      distance / step + (if distance % step != 0UL { 1UL } else { 0UL })
    } else {
      0UL
    }
  } else if start <= end {
    0UL
  } else {
    let distance = (start - end).reinterpret_as_uint64()
    let step = (-step).reinterpret_as_uint64()
    distance / step + (if distance % step != 0UL { 1UL } else { 0UL })
  }
  if len > 100000UL {
    raise TemplateError::new(InvalidOperation, "range has too many elements")
  }
  let len = len.to_int()
  Value::make_iterable(() => {
    let mut i = 0
    Iter::new(
      () => {
        if i < len {
          let rv = start + i.to_int64() * step
          i += 1
          Some(Value::from_int64(rv))
        } else {
          None
        }
      },
      size_hint=len,
    )
  })
}

///|
fn func_dict(state : State, args : Array[Value]) -> Value raise TemplateError {
  let a = Args::new(state, args)
  let update_with = a.kwargs()
  let value = a.opt_value()
  a.finish()
  let rv : Map[Value, Value] = Map([])
  match value {
    None => ()
    Some(Undefined(_)) => ()
    Some(Object(obj)) if obj.repr() is Map =>
      if obj.try_iter_pairs() is Some(iter) {
        for pair in iter {
          rv[pair.0] = pair.1
        }
      }
    Some(_) => raise TemplateError::from_kind(InvalidOperation)
  }
  for k, v in update_with.values {
    rv[k] = v
  }
  Value::from_map(rv)
}

///|
fn func_debug(state : State, args : Array[Value]) -> Value raise TemplateError {
  let a = Args::new(state, args)
  let args = a.rest()
  let f = @rfmt.Formatter::new(alternate=true)
  if args.is_empty() {
    state.fmt_debug(f)
  } else if args.length() == 1 {
    args[0].fmt_debug(f)
  } else {
    let l = f.debug_list()
    for v in args {
      l.entry(f => v.fmt_debug(f)) |> ignore
    }
    l.finish()
  }
  Value::from_string(f.to_string())
}

///|
fn func_namespace(
  state : State,
  args : Array[Value],
) -> Value raise TemplateError {
  let a = Args::new(state, args)
  let defaults = match a.next_raw() {
    Some(v) if !(v.is_undefined() || v.is_none()) => Some(v)
    _ => None
  }
  a.finish()
  let ns : Map[String, Value] = Map([])
  if defaults is Some(defaults) {
    match defaults {
      Object(obj) if obj.repr() is Map =>
        match obj.try_iter_pairs() {
          Some(pairs) =>
            for pair in pairs {
              if pair.0.as_str() is Some(key) {
                ns[key] = pair.1
              }
            }
          None =>
            raise TemplateError::new(
              InvalidOperation,
              "expected object or keyword arguments, got \{defaults.kind()}",
            )
        }
      _ =>
        raise TemplateError::new(
          InvalidOperation,
          "expected object or keyword arguments, got \{defaults.kind()}",
        )
    }
  }
  Object(DynObject::new(Namespace(ns)))
}

///|
fn register_builtin_globals(rv : Map[String, Value]) -> Unit {
  let b = "minijinja::functions::builtins::"
  rv["range"] = Value::from_function(b + "range", func_range)
  rv["dict"] = Value::from_function(b + "dict", func_dict)
  rv["debug"] = Value::from_function(b + "debug", func_debug)
  rv["namespace"] = Value::from_function(b + "namespace", func_namespace)
}