///|
pub fn Value::to_display_string(self : Value) -> String {
  value_to_string(self)
}

///|
pub fn Value::as_string(self : Value) -> String? {
  match self {
    StrValue(value) | SafeStr(value) => Some(value)
    _ => None
  }
}

///|
pub fn Value::as_int(self : Value) -> Int? {
  match self {
    IntValue(value) => Some(value)
    _ => None
  }
}

///|
fn first_arg(args : Array[Value], fallback : Value) -> Value {
  match args {
    [value, ..] => value
    [] => fallback
  }
}

///|
fn builtin_upper(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    StrValue(text) => StrValue(text.to_upper())
    SafeStr(text) => SafeStr(text.to_upper())
    _ => raise RenderError("upper expects a string")
  }
}

///|
fn builtin_lower(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    StrValue(text) => StrValue(text.to_lower())
    SafeStr(text) => SafeStr(text.to_lower())
    _ => raise RenderError("lower expects a string")
  }
}

///|
fn builtin_trim(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    StrValue(text) => StrValue(trim_ws(text))
    SafeStr(text) => SafeStr(trim_ws(text))
    _ => raise RenderError("trim expects a string")
  }
}

///|
fn builtin_split(value : Value, args : Array[Value]) -> Value raise Error {
  let separator = first_arg(args, StrValue(",")).to_display_string()
  match value {
    StrValue(text) | SafeStr(text) =>
      ListValue(
        text.split(separator).map(part => StrValue(part.to_owned())).to_array(),
      )
    _ => raise RenderError("split expects a string")
  }
}

///|
fn builtin_safe(value : Value, _args : Array[Value]) -> Value {
  match value {
    SafeStr(text) => SafeStr(text)
    _ => SafeStr(value.to_display_string())
  }
}

///|
fn builtin_escape(value : Value, _args : Array[Value]) -> Value {
  match value {
    SafeStr(text) => SafeStr(text)
    _ => SafeStr(escape_html(value.to_display_string()))
  }
}

///|
fn builtin_length(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    StrValue(text) | SafeStr(text) => IntValue(text.char_length())
    ListValue(items) => IntValue(items.length())
    MapValue(entries) => IntValue(entries.length())
    _ => raise RenderError("length expects a string, list, or map")
  }
}

///|
fn builtin_default(value : Value, args : Array[Value]) -> Value {
  let use_boolean = match args {
    [_, BoolValue(flag), ..] => flag
    _ => false
  }
  match value {
    Undefined => first_arg(args, StrValue(""))
    _ if use_boolean && !is_truthy(value) => first_arg(args, StrValue(""))
    _ => value
  }
}

///|
fn builtin_capitalize(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value.as_string() is Some(text) else {
    raise RenderError("capitalize expects a string")
  }
  let chars = text.to_array()
  if chars.is_empty() {
    return StrValue("")
  }
  StrValue(
    chars[0].to_string().to_upper() +
    String::from_array(chars[1:].to_owned()).to_lower(),
  )
}

///|
fn builtin_title(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value.as_string() is Some(text) else {
    raise RenderError("title expects a string")
  }
  let output = StringBuilder()
  let mut start_word = true
  for char in text {
    if char == ' ' || char == '\t' || char == '\n' || char == '-' || char == '_' {
      output.write_char(char)
      start_word = true
    } else {
      output.write_string(
        if start_word {
          char.to_string().to_upper()
        } else {
          char.to_string().to_lower()
        },
      )
      start_word = false
    }
  }
  StrValue(output.to_string())
}

///|
fn builtin_wordcount(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value.as_string() is Some(text) else {
    raise RenderError("wordcount expects a string")
  }
  let mut count = 0
  let mut in_word = false
  for char in text {
    if is_ws(char) {
      in_word = false
    } else if !in_word {
      count += 1
      in_word = true
    }
  }
  IntValue(count)
}

///|
fn builtin_sum(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value is ListValue(items) else {
    raise RenderError("sum expects a list")
  }
  let mut total : Value = IntValue(0)
  for item in items {
    total = apply_binop(total, BinOp::Add, item)
  }
  total
}

///|
fn builtin_extreme(value : Value, maximum : Bool) -> Value raise Error {
  guard value is ListValue(items) else {
    raise RenderError(
      if maximum {
        "max expects a list"
      } else {
        "min expects a list"
      },
    )
  }
  guard items is [first, .. rest] else { return Undefined }
  let mut selected = first
  for item in rest {
    let comparison = apply_binop(
      item,
      if maximum {
        BinOp::Gt
      } else {
        BinOp::Lt
      },
      selected,
    )
    if (match comparison {
        BoolValue(true) => true
        _ => false
      }) {
      selected = item
    }
  }
  selected
}

///|
fn builtin_unique(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value is ListValue(items) else {
    raise RenderError("unique expects a list")
  }
  let unique : Array[Value] = []
  for item in items {
    if !unique.any(existing => primitive_values_equal(existing, item)) {
      unique.push(item)
    }
  }
  ListValue(unique)
}

///|
fn builtin_sort(value : Value, _args : Array[Value]) -> Value raise Error {
  guard value is ListValue(items) else {
    raise RenderError("sort expects a list")
  }
  let sorted = items.copy()
  for right in 1.. 0 &&
          (match apply_binop(sorted[index - 1], BinOp::Gt, sorted[index]) {
            BoolValue(true) => true
            _ => false
          }) {
      let temporary = sorted[index - 1]
      sorted[index - 1] = sorted[index]
      sorted[index] = temporary
      index -= 1
    }
  }
  ListValue(sorted)
}

///|
fn builtin_map_entries(value : Value, mode : String) -> Value raise Error {
  guard value is MapValue(entries) else {
    raise RenderError(mode + " expects a map")
  }
  let result : Array[Value] = []
  for key, item in entries {
    match mode {
      "keys" => result.push(StrValue(key))
      "values" => result.push(item)
      _ => result.push(ListValue([StrValue(key), item]))
    }
  }
  ListValue(result)
}

///|
fn builtin_join(value : Value, args : Array[Value]) -> Value raise Error {
  let separator = first_arg(args, StrValue("")).to_display_string()
  match value {
    ListValue(items) => StrValue(items.map(value_to_string).join(separator))
    _ => raise RenderError("join expects a list")
  }
}

///|
fn builtin_replace(value : Value, args : Array[Value]) -> Value raise Error {
  guard args is [old, new, ..] else {
    raise RenderError("replace expects old and new values")
  }
  match value {
    StrValue(text) =>
      StrValue(
        text.replace_all(
          old=old.to_display_string(),
          new=new.to_display_string(),
        ),
      )
    SafeStr(text) =>
      SafeStr(
        text.replace_all(
          old=old.to_display_string(),
          new=new.to_display_string(),
        ),
      )
    _ => raise RenderError("replace expects a string")
  }
}

///|
fn builtin_first(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    ListValue([first, ..]) => first
    ListValue([]) => Undefined
    _ => raise RenderError("first expects a list")
  }
}

///|
fn builtin_last(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    ListValue(items) =>
      match items.last() {
        Some(last) => last
        None => Undefined
      }
    _ => raise RenderError("last expects a list")
  }
}

///|
fn builtin_reverse(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    ListValue(items) => ListValue(items.rev())
    _ => raise RenderError("reverse expects a list")
  }
}

///|
fn builtin_abs(value : Value, _args : Array[Value]) -> Value raise Error {
  match value {
    IntValue(number) => IntValue(if number < 0 { -number } else { number })
    DoubleValue(number) =>
      DoubleValue(if number < 0.0 { -number } else { number })
    _ => raise RenderError("abs expects a number")
  }
}

///|
fn builtin_string(value : Value, _args : Array[Value]) -> Value {
  StrValue(value.to_display_string())
}

///|
fn builtin_range(args : Array[Value], max_items : Int) -> Value raise Error {
  let (start, stop, step) = match args {
    [IntValue(stop)] => (0, stop, 1)
    [IntValue(start), IntValue(stop)] => (start, stop, 1)
    [IntValue(start), IntValue(stop), IntValue(step)] => (start, stop, step)
    _ => raise RenderError("range expects one to three integer arguments")
  }
  if step == 0 {
    raise RenderError("range step cannot be zero")
  }
  let result : Array[Value] = []
  if step > 0 {
    for current = start; current < stop; current = current + step {
      if result.length() >= max_items {
        raise RenderError("range exceeds the configured item limit")
      }
      result.push(IntValue(current))
    }
  } else {
    for current = start; current > stop; current = current + step {
      if result.length() >= max_items {
        raise RenderError("range exceeds the configured item limit")
      }
      result.push(IntValue(current))
    }
  }
  ListValue(result)
}

///|
fn builtin_list(args : Array[Value]) -> Value {
  ListValue(args)
}

///|
fn builtin_test_defined(value : Value, _args : Array[Value]) -> Bool {
  match value {
    Undefined => false
    _ => true
  }
}

///|
fn builtin_test_number(value : Value, _args : Array[Value]) -> Bool {
  match value {
    IntValue(_) | DoubleValue(_) => true
    _ => false
  }
}

///|
fn builtin_test_string(value : Value, _args : Array[Value]) -> Bool {
  match value {
    StrValue(_) | SafeStr(_) => true
    _ => false
  }
}

///|
fn builtin_test_iterable(value : Value, _args : Array[Value]) -> Bool {
  match value {
    StrValue(_) | SafeStr(_) | ListValue(_) | MapValue(_) => true
    _ => false
  }
}

///|
fn Environment::install_builtins(self : Environment) -> Unit {
  self.add_filter("upper", (value, args) => {
    Ok(builtin_upper(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("lower", (value, args) => {
    Ok(builtin_lower(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("trim", (value, args) => {
    Ok(builtin_trim(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("split", (value, args) => {
    Ok(builtin_split(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("safe", (value, args) => Ok(builtin_safe(value, args)))
  self.add_filter("escape", (value, args) => Ok(builtin_escape(value, args)))
  self.add_filter("e", (value, args) => Ok(builtin_escape(value, args)))
  self.add_filter("length", (value, args) => {
    Ok(builtin_length(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("default", (value, args) => Ok(builtin_default(value, args)))
  self.add_filter("capitalize", (value, args) => {
    Ok(builtin_capitalize(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("title", (value, args) => {
    Ok(builtin_title(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("wordcount", (value, args) => {
    Ok(builtin_wordcount(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("sum", (value, args) => {
    Ok(builtin_sum(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("min", (value, _args) => {
    Ok(builtin_extreme(value, false)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("max", (value, _args) => {
    Ok(builtin_extreme(value, true)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("unique", (value, args) => {
    Ok(builtin_unique(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("sort", (value, args) => {
    Ok(builtin_sort(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("keys", (value, _args) => {
    Ok(builtin_map_entries(value, "keys")) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("values", (value, _args) => {
    Ok(builtin_map_entries(value, "values")) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("items", (value, _args) => {
    Ok(builtin_map_entries(value, "items")) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("join", (value, args) => {
    Ok(builtin_join(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("replace", (value, args) => {
    Ok(builtin_replace(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("first", (value, args) => {
    Ok(builtin_first(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("last", (value, args) => {
    Ok(builtin_last(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("reverse", (value, args) => {
    Ok(builtin_reverse(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("abs", (value, args) => {
    Ok(builtin_abs(value, args)) catch {
      error => Err("\{error}")
    }
  })
  self.add_filter("string", (value, args) => Ok(builtin_string(value, args)))
  self.add_function("range", args => {
    Ok(builtin_range(args, self.options().max_range_size)) catch {
      error => Err("\{error}")
    }
  })
  self.add_function("list", args => Ok(builtin_list(args)))
  self.add_context_function("dict", (_, args, kwargs) => {
    let result : Map[String, Value] = Map([])
    match args {
      [] => ()
      [MapValue(entries)] =>
        for key, value in entries {
          result[key] = value
        }
      _ => return Err("dict expects zero arguments or one map")
    }
    for key, value in kwargs {
      result[key] = value
    }
    Ok(MapValue(result))
  })
  self.add_context_function("namespace", (_, args, kwargs) => {
    if !args.is_empty() {
      Err("namespace only accepts keyword arguments")
    } else {
      Ok(MapValue(kwargs))
    }
  })
  self.add_test("defined", (value, args) => {
    Ok(builtin_test_defined(value, args))
  })
  self.add_test("undefined", (value, args) => {
    Ok(!builtin_test_defined(value, args))
  })
  self.add_test("number", (value, args) => Ok(builtin_test_number(value, args)))
  self.add_test("string", (value, args) => Ok(builtin_test_string(value, args)))
  self.add_test("iterable", (value, args) => {
    Ok(builtin_test_iterable(value, args))
  })
  self.add_test("mapping", (value, _args) => {
    Ok(
      match value {
        MapValue(_) => true
        _ => false
      },
    )
  })
  self.add_test("none", (value, _args) => {
    Ok(
      match value {
        Null => true
        _ => false
      },
    )
  })
  self.add_test("odd", (value, _args) => {
    match value {
      IntValue(number) => Ok(number % 2 != 0)
      _ => Err("odd expects an integer")
    }
  })
  self.add_test("even", (value, _args) => {
    match value {
      IntValue(number) => Ok(number % 2 == 0)
      _ => Err("even expects an integer")
    }
  })
  self.add_test("boolean", (value, _args) => {
    Ok(
      match value {
        BoolValue(_) => true
        _ => false
      },
    )
  })
  self.add_test("integer", (value, _args) => {
    Ok(
      match value {
        IntValue(_) => true
        _ => false
      },
    )
  })
  self.add_test("float", (value, _args) => {
    Ok(
      match value {
        DoubleValue(_) => true
        _ => false
      },
    )
  })
  self.add_test("sequence", (value, _args) => {
    Ok(
      match value {
        StrValue(_) | SafeStr(_) | ListValue(_) => true
        _ => false
      },
    )
  })
  self.add_test("equalto", (value, args) => {
    Ok(
      match args {
        [other, ..] => primitive_values_equal(value, other)
        [] => false
      },
    )
  })
  self.add_test("sameas", (value, args) => {
    Ok(
      match args {
        [other, ..] => primitive_values_equal(value, other)
        [] => false
      },
    )
  })
  self.add_test("divisibleby", (value, args) => {
    match (value, args) {
      (IntValue(number), [IntValue(divisor), ..]) if divisor != 0 =>
        Ok(number % divisor == 0)
      _ => Err("divisibleby expects an integer divisor")
    }
  })
}