///|
/// Trie node for path-based routing
/// Uses SparseArray128 indexed by first character for O(1) lookup
/// T is the payload type stored at leaf nodes
priv struct Trie[T] {
  segment : StringView // Path segment (actual path for static nodes, param name for param nodes)
  children : FixedArray[Array[Trie[T]]?] // Child nodes indexed by first character
  mut param_child : Trie[T]? // Parameter child node :id
  mut catch_all_child : Trie[T]? // Catch-all child node *rest
  mut payload : T?
}

///|
fn[T] Trie::new(segment : StringView) -> Trie[T] {
  {
    segment,
    children: FixedArray::make(128, None),
    param_child: None,
    catch_all_child: None,
    payload: None,
  }
}

///|
/// Create a new root trie node
fn[T] Trie::root() -> Trie[T] {
  Trie::new("")
}

///|
/// Find or create a static child node
fn[T] Trie::find_or_create_static_child(
  self : Trie[T],
  segment : StringView,
) -> Trie[T] {
  if segment.length() == 0 {
    abort("segment cannot be empty")
  }

  // Use first character as index
  let first_char = segment.code_unit_at(0).to_int()

  // Find existing static child node
  match self.children[first_char] {
    Some(nodes) => {
      // Find exact segment match among nodes with same first character
      for node in nodes {
        if node.segment == segment {
          return node
        }
      }
      // Not found, create new node
      let child = Trie::new(segment)
      nodes.push(child)
      child
    }
    None => {
      // No nodes under this first character, create new array
      let child = Trie::new(segment)
      let nodes : Array[Trie[T]] = [child]
      self.children[first_char] = Some(nodes)
      child
    }
  }
}

///|
/// Find or create a param child node
fn[T] Trie::find_or_create_param_child(
  self : Trie[T],
  param_name : StringView,
) -> Trie[T] {
  match self.param_child {
    Some(child) =>
      // If param name differs, we allow it but use the existing node
      // The new param name will be used during routing
      return child
    None => {
      let child = Trie::new(param_name)
      self.param_child = Some(child)
      child
    }
  }
}

///|
/// Find or create a catch-all child node
fn[T] Trie::find_or_create_catch_all_child(
  self : Trie[T],
  param_name : StringView,
) -> Trie[T] {
  match self.catch_all_child {
    Some(child) => child
    None => {
      let child = Trie::new(param_name)
      self.catch_all_child = Some(child)
      child
    }
  }
}

///|
/// Parse path into segments
fn parse_path(path : StringView) -> Array[StringView] {
  let segments : Array[StringView] = []
  let len = path.length()
  let mut i = 0

  // Skip leading slash
  if len > 0 && path.code_unit_at(0).to_int() == '/'.to_int() {
    i = 1
  }
  let mut start = i
  while i < len {
    if path.code_unit_at(i).to_int() == '/'.to_int() {
      if i > start {
        segments.push(path[start:i])
      }
      i = i + 1
      start = i
    } else {
      i = i + 1
    }
  }

  // Add last segment
  if i > start {
    segments.push(path[start:i])
  }
  segments
}

///|
/// Insert a payload at the given path
/// Supports :param for path parameters and *rest for catch-all
fn[T] Trie::insert(self : Trie[T], path : StringView, payload : T) -> Unit {
  let segments = parse_path(path)
  let mut current = self
  for segment in segments {
    if segment.length() > 0 && segment.code_unit_at(0).to_int() == '*'.to_int() {
      // Catch-all parameter *rest
      let param_name = if segment.length() > 1 { segment[1:] } else { "rest" }
      current = current.find_or_create_catch_all_child(param_name)
      // Catch-all must be the last segment
      break
    } else if segment.length() > 0 &&
      segment.code_unit_at(0).to_int() == ':'.to_int() {
      // Parameter :id
      let param_name = segment[1:]
      current = current.find_or_create_param_child(param_name)
    } else {
      // Static segment
      current = current.find_or_create_static_child(segment)
    }
  }
  current.payload = Some(payload)
}

///|
/// Match result from trie search
priv struct MatchResult[T] {
  payload : T
  params : Map[StringView, StringView]
}

///|
/// Search for a path in the trie
/// Returns the payload and extracted parameters if found
fn[T] Trie::search(self : Trie[T], path : StringView) -> MatchResult[T]? {
  let segments = parse_path(path)
  let params : Map[StringView, StringView] = Map([])
  match self.search_recursive(segments, params) {
    Some(node) =>
      match node.payload {
        Some(payload) => Some({ payload, params })
        None => None
      }
    None => None
  }
}

///|
/// Recursive search helper
/// Returns the matched node if found
fn[T] Trie::search_recursive(
  self : Trie[T],
  segments : ArrayView[StringView],
  params : Map[StringView, StringView],
) -> Trie[T]? {
  if segments.length() == 0 {
    // Reached end of path
    if self.payload is Some(_) {
      return Some(self)
    }
    return None
  }
  let segment = segments[0]
  let remaining = segments[1:]

  // 1. Try static match first (highest priority)
  if segment.length() > 0 {
    let first_char = segment.code_unit_at(0).to_int()
    match self.children[first_char] {
      Some(nodes) =>
        for node in nodes {
          if node.segment == segment {
            match node.search_recursive(remaining, params) {
              Some(result) => return Some(result)
              None => ()
            }
          }
        }
      None => ()
    }
  }

  // 2. Try parameter match :id
  match self.param_child {
    Some(child) => {
      // Save parameter value
      let old_value = params.get(child.segment)
      params.set(child.segment, segment)
      match child.search_recursive(remaining, params) {
        Some(result) => return Some(result)
        None =>
          // Backtrack: restore parameter
          match old_value {
            Some(v) => params.set(child.segment, v)
            None => params.remove(child.segment) |> ignore
          }
      }
    }
    None => ()
  }

  // 3. Try catch-all match *rest (lowest priority)
  match self.catch_all_child {
    Some(child) => {
      // Catch-all captures all remaining path segments
      let rest_path = segments
        .iter()
        .intersperse("/")
        .fold(init="", fn(acc, s) { acc + s.to_owned() })
      params.set(child.segment, rest_path)
      if child.payload is Some(_) {
        return Some(child)
      }
    }
    None => ()
  }
  None
}