// Copyright (c) 2024 LinZeming
// Released under the MIT License
//
// Filters — built-in filter functions and registry for the template engine.
//
// Filters are functions `(Value, Array[Value]) -> Result[Value, String]`
// registered by name.  Chain them with `|` in templates:
//   `{{ name | upper | trim }}`

// ---------------------------------------------------------------------------
// Filter type
// ---------------------------------------------------------------------------

/// A filter function: takes the input value and optional arguments,
/// returns the filtered value or an error.
type FilterFn = (Value, Array[Value]) -> Result[Value, String]

// ---------------------------------------------------------------------------
// Filter registry
// ---------------------------------------------------------------------------

/// Registry mapping filter names to their implementations.
pub(all) struct FilterRegistry {
  filters : Map[String, FilterFn]
}

/// Create an empty registry.
pub fn FilterRegistry::new() -> FilterRegistry {
  {
    filters: Map::new(),
  }
}

/// Register a filter function under *name*.
pub fn FilterRegistry::register(
  self : FilterRegistry,
  name : String,
  f : FilterFn
) -> FilterRegistry {
  let filters = self.filters
  filters[name] = f
  { filters: filters }
}

/// Look up a filter by name.
pub fn FilterRegistry::lookup(self : FilterRegistry, name : String) -> FilterFn? {
  for key in self.filters.keys() {
    if key == name {
      return Some(self.filters[key])
    }
  }
  None
}

///|
/// Create a registry pre-populated with all built-in filters.
pub fn FilterRegistry::default() -> FilterRegistry {
  FilterRegistry::new()
    .register("upper", filter_upper)
    .register("lower", filter_lower)
    .register("capitalize", filter_capitalize)
    .register("trim", filter_trim)
    .register("length", filter_length)
    .register("reverse", filter_reverse)
    .register("first", filter_first)
    .register("last", filter_last)
    .register("join", filter_join)
    .register("replace", filter_replace)
    .register("default", filter_default)
    .register("int", filter_int)
    .register("string", filter_string)
}

// ---------------------------------------------------------------------------
// String helpers
// ---------------------------------------------------------------------------

/// Extract a single character as a 1-char string via slicing.
fn char_slice(s : String, i : Int) -> String {
  if i >= 0 && i < s.length() {
    s[i:i + 1].to_owned()
  } else {
    ""
  }
}

/// Simple string replace.
fn string_replace(s : String, from : String, to : String) -> String {
  if from == "" {
    return s
  }
  let mut result = ""
  let mut i = 0
  let s_len = s.length()
  let from_len = from.length()
  while i < s_len {
    if i + from_len <= s_len {
      let mut matches = true
      let mut j = 0
      while j < from_len {
        if s[i + j] != from[j] {
          matches = false
          break
        }
        j = j + 1
      }
      if matches {
        result = result + to
        i = i + from_len
        continue
      }
    }
    result = result + char_slice(s, i)
    i = i + 1
  }
  result
}

/// Trim leading and trailing whitespace.
fn string_trim(s : String) -> String {
  let len = s.length()
  let mut start = 0
  while start < len {
    let c = s[start].to_int()
    if c == 32 || c == 9 || c == 13 || c == 10 {
      start = start + 1
    } else {
      break
    }
  }
  let mut end = len
  while end > start {
    let c = s[end - 1].to_int()
    if c == 32 || c == 9 || c == 13 || c == 10 {
      end = end - 1
    } else {
      break
    }
  }
  s[start:end].to_owned()
}

/// Parse a string to Int64.
fn filter_parse_int(s : String) -> Result[Int64, String] {
  if s.length() == 0 {
    return Err("empty")
  }
  let mut result = 0L
  let mut i = 0
  let len = s.length()
  let zero = '0'.to_int()
  while i < len {
    let ch = s[i].to_int()
    if ch >= zero && ch <= '9'.to_int() {
      let digit = (ch - zero).to_int64()
      result = result * 10L + digit
    } else {
      return Err("invalid character")
    }
    i = i + 1
  }
  Ok(result)
}

// ---------------------------------------------------------------------------
// Built-in filters
// ---------------------------------------------------------------------------

/// `upper` — convert string to uppercase.
fn filter_upper(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => Ok(Str(s.to_upper()))
    _ => Err("filter 'upper' expects a string, got: " + value.type_name())
  }
}

/// `lower` — convert string to lowercase.
fn filter_lower(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => Ok(Str(s.to_lower()))
    _ => Err("filter 'lower' expects a string, got: " + value.type_name())
  }
}

/// `capitalize` — uppercase the first character.
fn filter_capitalize(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => {
      if s.length() == 0 {
        Ok(Str(""))
      } else {
        let first = char_slice(s, 0)
        let rest = if s.length() > 1 { s[1:s.length()].to_owned() } else { "" }
        Ok(Str(first.to_upper() + rest))
      }
    }
    _ => Err("filter 'capitalize' expects a string, got: " + value.type_name())
  }
}

/// `trim` — remove leading and trailing whitespace.
fn filter_trim(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => Ok(Str(string_trim(s)))
    _ => Err("filter 'trim' expects a string, got: " + value.type_name())
  }
}

/// `length` — return the length of a string, array, or object.
fn filter_length(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => Ok(Int(s.length().to_int64()))
    Array(arr) => Ok(Int(arr.length().to_int64()))
    Object(m) => Ok(Int(m.length().to_int64()))
    _ => Err("filter 'length' expects string, array, or object")
  }
}

/// `reverse` — reverse a string or array.
fn filter_reverse(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => {
      let len = s.length()
      let mut result = ""
      let mut i = len
      while i > 0 {
        i = i - 1
        result = result + char_slice(s, i)
      }
      Ok(Str(result))
    }
    Array(arr) => {
      let len = arr.length()
      let result : Array[Value] = Array::new()
      let mut i = len
      while i > 0 {
        i = i - 1
        result.push(arr[i])
      }
      Ok(Array(result))
    }
    _ => Err("filter 'reverse' expects a string or array")
  }
}

/// `first` — return the first character / element.
fn filter_first(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => {
      if s.length() == 0 {
        Ok(Str(""))
      } else {
        Ok(Str(char_slice(s, 0)))
      }
    }
    Array(arr) => {
      if arr.length() == 0 {
        Ok(Array([]))
      } else {
        Ok(arr[0])
      }
    }
    _ => Err("filter 'first' expects a string or array")
  }
}

/// `last` — return the last character / element.
fn filter_last(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => {
      let len = s.length()
      if len == 0 {
        Ok(Str(""))
      } else {
        Ok(Str(char_slice(s, len - 1)))
      }
    }
    Array(arr) => {
      let len = arr.length()
      if len == 0 {
        Ok(Array([]))
      } else {
        Ok(arr[len - 1])
      }
    }
    _ => Err("filter 'last' expects a string or array")
  }
}

/// `join(sep)` — join array elements with separator.
fn filter_join(value : Value, args : Array[Value]) -> Result[Value, String] {
  match value {
    Array(arr) => {
      let sep = if args.length() > 0 { args[0].to_string() } else { "" }
      if arr.length() == 0 {
        return Ok(Str(""))
      }
      let mut result = arr[0].to_string()
      for i = 1; i < arr.length(); i = i + 1 {
        result = result + sep + arr[i].to_string()
      }
      Ok(Str(result))
    }
    _ => Err("filter 'join' expects an array")
  }
}

/// `replace(from, to)` — replace substrings.
fn filter_replace(value : Value, args : Array[Value]) -> Result[Value, String] {
  match value {
    Str(s) => {
      if args.length() < 2 {
        return Err("filter 'replace' expects 2 arguments: from, to")
      }
      let from = args[0].to_string()
      let to = args[1].to_string()
      Ok(Str(string_replace(s, from, to)))
    }
    _ => Err("filter 'replace' expects a string")
  }
}

/// `default(val)` — return default if value is falsy.
fn filter_default(value : Value, args : Array[Value]) -> Result[Value, String] {
  if value.is_truthy() {
    Ok(value)
  } else {
    if args.length() > 0 {
      Ok(args[0])
    } else {
      Ok(Str(""))
    }
  }
}

/// `int` — convert to integer.
fn filter_int(value : Value, _args : Array[Value]) -> Result[Value, String] {
  match value {
    Int(_) => Ok(value)
    Float(f) => Ok(Int(f.to_int64()))
    Str(s) => match filter_parse_int(s) {
      Ok(i) => Ok(Int(i))
      Err(_) => Err("filter 'int': cannot parse '" + s + "' as integer")
    }
    Bool(b) => Ok(Int(if b { 1L } else { 0L }))
    _ => Err("filter 'int' cannot convert type: " + value.type_name())
  }
}

/// `string` — convert to string.
fn filter_string(value : Value, _args : Array[Value]) -> Result[Value, String] {
  Ok(Str(value.to_string()))
}