///|
/// Describes the kind of value.
pub(all) enum ValueKind {
  /// The value is undefined
  Undefined
  /// The value is the none singleton (`()`)
  Null
  /// The value is a [`Bool`]
  Bool
  /// The value is a number of a supported type.
  Number
  /// The value is a string.
  String
  /// The value is a byte array.
  Bytes
  /// The value is an array of other values.
  Seq
  /// The value is a key/value mapping.
  Map
  /// An iterable
  Iterable
  /// A plain object without specific behavior.
  Plain
  /// This value is invalid (holds an error).
  Invalid
} derive(Eq, Compare, Debug)

///|
pub fn ValueKind::to_string(self : ValueKind) -> String {
  match self {
    Undefined => "undefined"
    Null => "none"
    Bool => "bool"
    Number => "number"
    String => "string"
    Bytes => "bytes"
    Seq => "sequence"
    Map => "map"
    Iterable => "iterator"
    Plain => "plain object"
    Invalid => "invalid value"
  }
}

///|
pub impl Show for ValueKind with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
priv enum UndefinedType {
  Default
  Silent
}

///|
priv enum StringType {
  Normal
  Safe
}

///|
/// Represents a dynamically typed value in the template engine.
enum Value {
  Undefined(UndefinedType)
  NoneValue
  Bool(Bool)
  U64(UInt64)
  I64(Int64)
  F64(Double)
  U128(BigInt)
  I128(BigInt)
  Str(String, StringType)
  Bytes(Bytes)
  Invalid(TemplateError)
  Object(DynObject)
}

///|
/// The undefined value.
pub fn Value::undefined() -> Value {
  Undefined(Default)
}

///|
fn Value::silent_undefined() -> Value {
  Undefined(Silent)
}

///|
/// The `none` value.
pub fn Value::none() -> Value {
  NoneValue
}

///|
pub fn Value::from_bool(b : Bool) -> Value {
  Bool(b)
}

///|
pub fn Value::from_int(i : Int) -> Value {
  I64(i.to_int64())
}

///|
pub fn Value::from_int64(i : Int64) -> Value {
  I64(i)
}

///|
pub fn Value::from_uint64(i : UInt64) -> Value {
  U64(i)
}

///|
pub fn Value::from_double(f : Double) -> Value {
  F64(f)
}

///|
/// Creates a value from a big integer.
///
/// Like Rust's `Value::from(i128)` / `Value::from(u128)` the value is stored
/// as a 128 bit integer (signed if it fits, unsigned otherwise).  Values
/// outside of the 128 bit range become invalid values.
pub fn Value::from_bigint(i : BigInt) -> Value {
  if in_i128(i) {
    I128(i)
  } else if i >= 0N && i <= u128_max {
    U128(i)
  } else {
    Invalid(TemplateError::new(InvalidOperation, "integer out of range"))
  }
}

///|
pub fn Value::from_string(s : String) -> Value {
  Str(s, Normal)
}

///|
pub fn Value::from_char(c : Char) -> Value {
  Str(c.to_string(), Normal)
}

///|
/// Creates a value from a safe string.
///
/// A safe string is one that will bypass auto escaping.
pub fn Value::from_safe_string(s : String) -> Value {
  Str(s, Safe)
}

///|
pub fn Value::from_bytes(b : Bytes) -> Value {
  Bytes(b)
}

///|
/// Creates a sequence value from an array.
pub fn Value::from_array(items : Array[Value]) -> Value {
  Object(DynObject::new(Seq(items)))
}

///|
/// Creates a tuple value.
pub fn Value::from_tuple(items : Array[Value]) -> Value {
  Object(DynObject::new(Tuple(items)))
}

///|
/// Creates a map value.
pub fn Value::from_map(m : Map[Value, Value]) -> Value {
  Object(DynObject::new(Map(m)))
}

///|
/// Creates a map value from string keys.
pub fn Value::from_str_map(m : Map[String, Value]) -> Value {
  let rv : Map[Value, Value] = Map([], capacity=m.length())
  for k, v in m {
    rv[Value::from_string(k)] = v
  }
  Value::from_map(rv)
}

///|
/// Creates a map value from key/value pairs.
pub fn Value::from_pairs(pairs : Array[(String, Value)]) -> Value {
  let rv : Map[Value, Value] = Map([], capacity=pairs.length())
  for pair in pairs {
    rv[Value::from_string(pair.0)] = pair.1
  }
  Value::from_map(rv)
}

///|
/// Creates a value from a dynamic object.
pub fn[T : Object] Value::from_object(obj : T) -> Value {
  Object(DynObject::new(Custom(obj)))
}

///|
/// Creates a value from a dynamic object handle.
pub fn Value::from_dyn_object(obj : DynObject) -> Value {
  Object(obj)
}

///|
/// Creates a value from an error.  The value is invalid and raises the
/// error as soon as it is used by the engine.
pub fn Value::from_error(err : TemplateError) -> Value {
  Invalid(err)
}

///|
/// Creates an iterable that iterates over the iterator produced by `maker`.
/// The maker is invoked every time the value is iterated.
pub fn Value::make_iterable(maker : () -> Iter[Value]) -> Value {
  Value::from_object(IterableObject::{ maker, })
}

///|
/// Creates an iterable that can only be iterated over once.
pub fn Value::make_one_shot_iterator(iter : Iter[Value]) -> Value {
  let mut done = false
  let shared = Iter::new(() => {
    if done {
      return None
    }
    match iter.next() {
      Some(v) => Some(v)
      None => {
        done = true
        None
      }
    }
  })
  Value::make_iterable(() => Iter::new(() => shared.next()))
}

///|
/// Creates a map object from closures: `keys` enumerates the keys and `get`
/// looks up a value by key.
pub fn Value::make_object_map(
  keys : () -> Iter[Value],
  get : (Value) -> Value?,
) -> Value {
  Value::from_object(ProxyMapObject::{ keys, get, })
}

///|
/// Creates a callable value from a function.
pub fn Value::from_function(
  name : String,
  func : (State, Array[Value]) -> Value raise TemplateError,
) -> Value {
  Value::from_object(FunctionObject::{ name, func, })
}

///|
/// Returns the kind of the value.
pub fn Value::kind(self : Value) -> ValueKind {
  match self {
    Undefined(_) => Undefined
    Bool(_) => Bool
    U64(_) | I64(_) | F64(_) | U128(_) | I128(_) => Number
    NoneValue => Null
    Str(_, _) => String
    Bytes(_) => Bytes
    Invalid(_) => Invalid
    Object(obj) =>
      match obj.repr() {
        Map => Map
        Seq => Seq
        Iterable => Iterable
        Plain => Plain
      }
  }
}

///|
/// Returns `true` if the value is a number.
pub fn Value::is_number(self : Value) -> Bool {
  self is (U64(_) | I64(_) | F64(_) | U128(_) | I128(_))
}

///|
/// Returns `true` if the value is an integer.
pub fn Value::is_integer(self : Value) -> Bool {
  self is (U64(_) | I64(_) | U128(_) | I128(_))
}

///|
/// Returns `true` if the map represents keyword arguments.
pub fn Value::is_kwargs(self : Value) -> Bool {
  self is Object({ inner: Kwargs(_), .. })
}

///|
/// Returns `true` if the value is a tuple.
pub fn Value::is_tuple(self : Value) -> Bool {
  self is Object({ inner: Tuple(_), .. })
}

///|
/// Is this value considered true?
pub fn Value::is_true(self : Value) -> Bool {
  match self {
    Bool(b) => b
    U64(x) => x != 0
    I64(x) => x != 0
    U128(x) | I128(x) => !x.is_zero()
    F64(x) => x != 0.0
    Str(s, _) => !s.is_empty()
    Bytes(b) => b.length() != 0
    NoneValue | Undefined(_) | Invalid(_) => false
    Object(obj) => obj.is_true()
  }
}

///|
/// Returns `true` if this value is safe.
pub fn Value::is_safe(self : Value) -> Bool {
  self is Str(_, Safe)
}

///|
/// Returns `true` if this value is undefined.
pub fn Value::is_undefined(self : Value) -> Bool {
  self is Undefined(_)
}

///|
/// Returns `true` if this value is none.
pub fn Value::is_none(self : Value) -> Bool {
  self is NoneValue
}

///|
/// If the value is a string, return it.  Bytes are returned if they are
/// valid UTF-8.
pub fn Value::as_str(self : Value) -> String? {
  match self {
    Str(s, _) => Some(s)
    Bytes(b) => utf8_decode_strict(b)
    _ => None
  }
}

///|
/// Like `as_str` but bytes are decoded lossily.
pub fn Value::to_str(self : Value) -> String? {
  match self {
    Str(s, _) => Some(s)
    Bytes(b) => Some(utf8_decode_lossy(b))
    _ => None
  }
}

///|
/// If the value is bytes (or a string), return the UTF-8 bytes.
pub fn Value::as_bytes(self : Value) -> Bytes? {
  match self {
    Str(s, _) => Some(@utf8.encode(s))
    Bytes(b) => Some(b)
    _ => None
  }
}

///|
/// Returns the object handle if the value is an object.
pub fn Value::as_object(self : Value) -> DynObject? {
  match self {
    Object(o) => Some(o)
    _ => None
  }
}

///|
/// Returns the length of the contained value.
///
/// Strings return the number of Unicode scalar values, bytes the number of
/// bytes and objects the length of their enumerator (if known).
pub fn Value::len(self : Value) -> Int? {
  match self {
    Str(s, _) => Some(scalar_count(s))
    Bytes(b) => Some(b.length())
    Object(obj) => obj.enumerator_len()
    _ => None
  }
}

///|
/// Raises the error of an invalid value, otherwise returns the value.
fn Value::validate(self : Value) -> Value raise TemplateError {
  match self {
    Invalid(err) => raise err.internal_clone()
    _ => self
  }
}

///|
/// Looks up an attribute by attribute name.
///
/// This this returns undefined if an item does not exist, but it raises an
/// error if the value itself is undefined.
pub fn Value::get_attr(self : Value, key : String) -> Value raise TemplateError {
  match self {
    Undefined(_) => raise TemplateError::from_kind(UndefinedError)
    Object(obj) => obj.get_value_by_str(key).unwrap_or(Value::undefined())
    _ => Value::undefined()
  }
}

///|
fn Value::get_attr_fast(self : Value, key : String) -> Value? {
  match self {
    Object(obj) => obj.get_value_by_str(key)
    _ => None
  }
}

///|
/// Looks up an index of the value.
pub fn Value::get_item_by_index(
  self : Value,
  idx : Int,
) -> Value raise TemplateError {
  self.get_item(Value::from_int(idx))
}

///|
/// Looks up an item (or attribute) by key.
///
/// This returns undefined if the item does not exist but raises an error if
/// the value itself is undefined.
pub fn Value::get_item(self : Value, key : Value) -> Value raise TemplateError {
  match self {
    Undefined(_) => raise TemplateError::from_kind(UndefinedError)
    _ => self.get_item_opt(key).unwrap_or(Value::undefined())
  }
}

///|
/// Resolves a (possibly negative) index.
fn resolve_index(key : Value, len : () -> Int?) -> Int? {
  match key.as_i64() {
    Some(i) if i < 0L =>
      match len() {
        Some(l) => {
          let rv = l.to_int64() + i
          if rv < 0L {
            None
          } else {
            Some(rv.to_int())
          }
        }
        None => None
      }
    Some(i) => if i > 0x7FFFFFFFL { None } else { Some(i.to_int()) }
    None => None
  }
}

///|
fn Value::get_item_opt(self : Value, key : Value) -> Value? {
  match self {
    Object(obj) =>
      match obj.repr() {
        Map | Plain => obj.get_value(key)
        Iterable => {
          if obj.get_value(key) is Some(rv) {
            return Some(rv)
          }
          // The default behavior is to try to index into the iterable as if
          // nth() was called.  This lets one slice an array and then index
          // into it.
          if resolve_index(key, () => obj.enumerator_len()) is Some(idx) &&
            obj.try_iter() is Some(iter) {
            return iter.nth(idx)
          }
          None
        }
        Seq =>
          match resolve_index(key, () => obj.enumerator_len()) {
            Some(idx) => obj.get_value(Value::from_int(idx))
            None => obj.get_value(key)
          }
      }
    Str(s, _) => {
      guard resolve_index(key, () => Some(scalar_count(s))) is Some(idx) else {
        return None
      }
      let mut i = 0
      for c in s {
        if i == idx {
          return Some(Value::from_char(c))
        }
        i += 1
      }
      None
    }
    Bytes(b) => {
      guard resolve_index(key, () => Some(b.length())) is Some(idx) else {
        return None
      }
      if idx < b.length() {
        Some(U64(b[idx].to_uint64()))
      } else {
        None
      }
    }
    _ => None
  }
}

///|
/// Iterates over the value.
///
/// Depending on the kind of the value the iterator has a different behavior:
/// maps yield their keys, sequences and iterables their items, strings
/// their characters and undefined/none values nothing.
pub fn Value::try_iter(self : Value) -> Iter[Value] raise TemplateError {
  match self {
    NoneValue | Undefined(_) => Iter::empty()
    Str(s, _) => {
      let chars = s.iter()
      let len = scalar_count(s)
      Iter::new(() => chars.next().map(Value::from_char), size_hint=len)
    }
    Object(obj) =>
      match obj.try_iter() {
        Some(iter) => iter
        None =>
          raise TemplateError::new(
            InvalidOperation,
            "\{self.kind()} is not iterable",
          )
      }
    _ =>
      raise TemplateError::new(
        InvalidOperation,
        "\{self.kind()} is not iterable",
      )
  }
}

///|
/// Returns a reversed view of this value.
///
/// This is implemented for the following types with the following
/// behaviors: undefined or none (returns itself), strings (reverses the
/// characters), bytes, sequences and iterables.
pub fn Value::reverse(self : Value) -> Value raise TemplateError {
  match self {
    Undefined(_) | NoneValue => self
    Str(s, _) => {
      let chars = s.iter().to_array()
      chars.rev_in_place()
      Value::from_string(String::from_array(chars))
    }
    Bytes(b) => {
      let arr = b.to_array()
      arr.rev_in_place()
      Value::from_bytes(Bytes::from_array(arr))
    }
    Object(o) =>
      match o.enumerate() {
        NonEnumerable =>
          raise TemplateError::new(
            InvalidOperation,
            "cannot reverse values of type \{self.kind()}",
          )
        Empty => Value::make_iterable(() => Iter::empty())
        Seq(l) =>
          Value::make_iterable(() => {
            let mut idx = l
            Iter::new(
              () => {
                if idx > 0 {
                  idx -= 1
                  Some(
                    o
                    .get_value(Value::from_int(idx))
                    .unwrap_or(Value::undefined()),
                  )
                } else {
                  None
                }
              },
              size_hint=l,
            )
          })
        Iter(iter) => {
          let v = iter.to_array()
          v.rev_in_place()
          Value::make_iterable(() => v.iter())
        }
        KeyValueIter(iter) => {
          let v = if o.repr() is Map {
            iter.map(pair => pair.0).to_array()
          } else {
            iter.map(pair => Value::from_tuple([pair.0, pair.1])).to_array()
          }
          v.rev_in_place()
          Value::make_iterable(() => v.iter())
        }
        Str(strs) => {
          let v = strs.map(Value::from_string)
          v.rev_in_place()
          Value::make_iterable(() => v.iter())
        }
        Values(values) => {
          let v = values.copy()
          v.rev_in_place()
          Value::make_iterable(() => v.iter())
        }
      }
    _ =>
      raise TemplateError::new(
        InvalidOperation,
        "cannot reverse values of type \{self.kind()}",
      )
  }
}

///|
/// Calls the value directly.
pub fn Value::call(
  self : Value,
  state : State,
  args : Array[Value],
) -> Value raise TemplateError {
  match self {
    Object(obj) => obj.call(state, args)
    _ =>
      raise TemplateError::new(
        InvalidOperation,
        "value of type \{self.kind()} is not callable",
      )
  }
}

///|
/// Calls a method on the value.
pub fn Value::call_method(
  self : Value,
  state : State,
  name : String,
  args : Array[Value],
) -> Value raise TemplateError {
  let rv = try {
    match self {
      Object(obj) => obj.call_method(state, name, args)
      _ => raise TemplateError::from_kind(UnknownMethod)
    }
  } catch {
    err => self.call_method_fallback(state, name, args, err)
  }
  rv
}

///|
/// Handles a failed method call: tries the unknown method callback and
/// attribute fallback, then raises `err` with a helpful message.
fn Value::call_method_fallback(
  self : Value,
  state : State,
  name : String,
  args : Array[Value],
  err : TemplateError,
) -> Value raise TemplateError {
  let mut err = err
  if err.kind() == UnknownMethod {
    if state.env().unknown_method_callback is Some(callback) {
      let res = try callback(state, self, name, args) catch {
        callback_err => {
          if callback_err.kind() != UnknownMethod {
            raise callback_err
          }
          err = callback_err
          Value::undefined()
        }
      } noraise {
        result => return result
      }
      ignore(res)
    }
    // Calling values stored on objects by using method syntax is
    // supported as a fallback.
    if self.as_object() is Some(obj) &&
      obj.get_value(Value::from_string(name)) is Some(value) {
      return value.call(state, args)
    }
    if err.detail() is None {
      err.set_detail("\{self.kind()} has no method named \{name}")
    }
  }
  raise err
}

///|
/// Looks up a dotted path (`foo.0.bar`).
fn Value::get_path(self : Value, path : String) -> Value raise TemplateError {
  let mut rv = self
  for part in path.split(".") {
    match parse_usize(part) {
      Some(num) => rv = rv.get_item_by_index(num)
      None => rv = rv.get_attr(part.to_owned())
    }
  }
  rv
}

///|
fn Value::get_path_or_default(
  self : Value,
  path : String,
  default : Value,
) -> Value {
  let val = self.get_path(path) catch { _ => return default }
  if val.is_undefined() {
    default
  } else {
    val
  }
}

///|
/// Parses a non-negative decimal integer like Rust's `str::parse::`.
fn parse_usize(s : StringView) -> Int? {
  // like Rust, a single leading `+` is accepted
  let s = if s.length() > 1 && s[0] == '+' { s.view(start_offset=1) } else { s }
  if s.length() == 0 {
    return None
  }
  let mut rv = 0L
  for c in s {
    if c < '0' || c > '9' {
      return None
    }
    rv = rv * 10L + (c.to_int() - '0'.to_int()).to_int64()
    if rv > 0x7FFFFFFFL {
      return None
    }
  }
  Some(rv.to_int())
}

///|
/// Formats the value like MiniJinja's `Display` (what `{{ value }}`
/// renders).
pub fn Value::to_string(self : Value) -> String {
  match self {
    Str(s, _) => s
    I64(v) => v.to_string()
    U64(v) => v.to_string()
    Bool(b) => if b { "True" } else { "False" }
    NoneValue => "None"
    Undefined(_) => ""
    _ => {
      let f = @rfmt.Formatter::new()
      self.fmt_display(f)
      f.to_string()
    }
  }
}

///|
pub impl Show for Value with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
fn Value::fmt_display(self : Value, f : @rfmt.Formatter) -> Unit {
  match self {
    Undefined(_) => ()
    Bool(b) => f.write_str(if b { "True" } else { "False" })
    U64(v) => f.write_str(v.to_string())
    I64(v) => f.write_str(v.to_string())
    F64(v) => f.write_str(format_f64_display(v))
    NoneValue => f.write_str("None")
    Invalid(err) => f.write_str("")
    I128(v) | U128(v) => f.write_str(v.to_string())
    Str(s, _) => f.write_str(s)
    Bytes(b) => f.write_str(utf8_decode_lossy(b))
    Object(obj) => obj.render(f)
  }
}

///|
/// Formats a float like `{{ value }}` renders it.
fn format_f64_display(v : Double) -> String {
  if v.is_nan() {
    "NaN"
  } else if v.is_inf() {
    if v > 0.0 {
      "inf"
    } else {
      "-inf"
    }
  } else {
    let num = @rfmt.f64_display(v)
    if num.contains(".") {
      num
    } else {
      num + ".0"
    }
  }
}

///|
/// Formats the value like MiniJinja's `Debug` (a Python like repr).
pub fn Value::debug_string(self : Value, pretty? : Bool = false) -> String {
  let f = @rfmt.Formatter::new(alternate=pretty)
  self.fmt_debug(f)
  f.to_string()
}

///|
fn Value::fmt_debug(self : Value, f : @rfmt.Formatter) -> Unit {
  match self {
    Undefined(_) => f.write_str("undefined")
    Bool(b) => f.write_str(if b { "True" } else { "False" })
    U64(v) => f.write_str(v.to_string())
    I64(v) => f.write_str(v.to_string())
    F64(v) => f.write_str(@rfmt.f64_debug(v))
    NoneValue => f.write_str("None")
    Invalid(err) => f.write_str("")
    I128(v) | U128(v) => f.write_str(v.to_string())
    Str(s, _) => f.write_str(python_string_repr(s))
    Bytes(b) => {
      let sb = StringBuilder()
      sb.write_string("b'")
      for byte in b {
        let c = byte.to_int()
        match c {
          0x22 => sb.write_char('"')
          0x09 => sb.write_string("\\t")
          0x0A => sb.write_string("\\n")
          0x0D => sb.write_string("\\r")
          0x27 => sb.write_string("\\'")
          0x5C => sb.write_string("\\\\")
          _ =>
            if c >= 0x20 && c < 0x7f {
              sb.write_char(c.unsafe_to_char())
            } else {
              sb.write_string("\\x")
              let hex = c.to_string(radix=16)
              if hex.length() < 2 {
                sb.write_char('0')
              }
              sb.write_string(hex)
            }
        }
      }
      sb.write_char('\'')
      f.write_str(sb.to_string())
    }
    Object(obj) => obj.render(f)
  }
}

///|
/// Formats a string like Python's `repr`.
fn python_string_repr(value : String) -> String {
  let quote = if value.contains("'") && !value.contains("\"") {
    '"'
  } else {
    '\''
  }
  let sb = StringBuilder()
  sb.write_char(quote)
  for ch in value {
    match ch {
      '\'' if quote == '\'' => sb.write_string("\\'")
      '"' if quote == '"' => sb.write_string("\\\"")
      '\\' => sb.write_string("\\\\")
      '\n' => sb.write_string("\\n")
      '\r' => sb.write_string("\\r")
      '\t' => sb.write_string("\\t")
      _ => {
        let cp = ch.to_int()
        if is_control_char(cp) {
          if cp <= 0xff {
            let hex = cp.to_string(radix=16)
            sb.write_string("\\x")
            if hex.length() < 2 {
              sb.write_char('0')
            }
            sb.write_string(hex)
          } else {
            let hex = cp.to_string(radix=16)
            sb.write_string("\\u")
            for _ in hex.length()..<4 {
              sb.write_char('0')
            }
            sb.write_string(hex)
          }
        } else {
          sb.write_char(ch)
        }
      }
    }
  }
  sb.write_char(quote)
  sb.to_string()
}

///|
/// Port of Rust's `char::is_control` (general category Cc).
fn is_control_char(cp : Int) -> Bool {
  cp < 0x20 || (cp >= 0x7f && cp < 0xa0)
}

///|
/// Decodes UTF-8 bytes, returning `None` for invalid input.
fn utf8_decode_strict(b : Bytes) -> String? {
  try @utf8.decode(b) catch {
    _ => None
  } noraise {
    s => Some(s)
  }
}

///|
/// Decodes UTF-8 bytes replacing invalid sequences with U+FFFD.
fn utf8_decode_lossy(b : Bytes) -> String {
  @utf8.decode_lossy(b)
}

///|
/// Wraps a map into keyword arguments.
fn Value::from_kwargs_map(m : Map[Value, Value]) -> Value {
  Object(DynObject::new(Kwargs(m)))
}

///|
pub impl @debug.Debug for Value with fn to_repr(self) {
  @debug.Repr::Repr(self.debug_string())
}

///|
/// Returns the boolean if the value is a boolean (no truthiness check).
pub fn Value::as_bool(self : Value) -> Bool? {
  match self {
    Bool(b) => Some(b)
    _ => None
  }
}

///|
/// Returns the value as `UInt64` if it is a non-negative integer (or an
/// integral float) that fits.
pub fn Value::as_uint64(self : Value) -> UInt64? {
  match self {
    U64(v) => Some(v)
    _ =>
      match self.to_i128() {
        Some(v) if v >= 0N && v <= max_u64_big => Some(v.to_uint64())
        _ => None
      }
  }
}

///|
/// Returns the value as a big integer if it is an integer (or an integral
/// float).  Covers the full 128 bit range.
pub fn Value::as_bigint(self : Value) -> BigInt? {
  match self {
    U128(v) => Some(v)
    _ => self.to_i128()
  }
}