/// RFC 6570 URI template machinery mirroring fastmcp/resources/template.py.
///
/// Supports the same subset as fastmcp: `{var}` simple path parameters,
/// `{var*}` wildcard path parameters and `{?a,b}` / `{?list*}` query blocks.

///|
/// RFC 3986 reserved characters, which stay as written in a template literal.
let reserved : String = ":/?#[]@!$&'()*+,;="

///|
fn is_unreserved_char(ch : Char) -> Bool {
  (ch >= 'A' && ch <= 'Z') ||
  (ch >= 'a' && ch <= 'z') ||
  (ch >= '0' && ch <= '9') ||
  ch == '-' ||
  ch == '.' ||
  ch == '_' ||
  ch == '~'
}

///|
fn hex_digit_value(ch : Char) -> Int32? {
  if ch >= '0' && ch <= '9' {
    Some(ch.to_int() - '0'.to_int())
  } else if ch >= 'a' && ch <= 'f' {
    Some(ch.to_int() - 'a'.to_int() + 10)
  } else if ch >= 'A' && ch <= 'F' {
    Some(ch.to_int() - 'A'.to_int() + 10)
  } else {
    None
  }
}

///|
fn is_hex_digit(ch : Char) -> Bool {
  hex_digit_value(ch) is Some(_)
}

///|
fn write_hex_byte(buf : StringBuilder, byte : Byte) -> Unit {
  let digits = "0123456789ABCDEF"
  buf.write_char('%')
  buf.write_char(digits.get_char((byte / 16).to_int()).unwrap())
  buf.write_char(digits.get_char((byte % 16).to_int()).unwrap())
}

///|
/// Percent-encode `text`, keeping unreserved characters plus `safe`.
pub fn pct_encode(text : String, safe~ : String = "") -> String {
  let buf = StringBuilder::StringBuilder()
  for ch in text {
    if is_unreserved_char(ch) ||
      (safe.length() > 0 && safe.contains(ch.to_string())) {
      buf.write_char(ch)
    } else {
      for byte in @utf8.encode(ch.to_string()) {
        write_hex_byte(buf, byte)
      }
    }
  }
  buf.to_string()
}

///|
/// Percent-encode literal template text per RFC 6570 section 3.1.
///
/// Existing `%XX` triplets pass through so an already-encoded literal is not
/// encoded twice; everything else keeps reserved and unreserved characters
/// and encodes the rest.
pub fn encode_literal(text : String) -> String {
  let chars = text.to_array()
  let buf = StringBuilder::StringBuilder()
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '%' &&
      i + 2 < chars.length() &&
      is_hex_digit(chars[i + 1]) &&
      is_hex_digit(chars[i + 2]) {
      buf.write_char(chars[i])
      buf.write_char(chars[i + 1])
      buf.write_char(chars[i + 2])
      i += 3
    } else {
      let ch = chars[i]
      if is_unreserved_char(ch) || reserved.contains(ch.to_string()) {
        buf.write_char(ch)
      } else {
        for byte in @utf8.encode(ch.to_string()) {
          write_hex_byte(buf, byte)
        }
      }
      i += 1
    }
  }
  buf.to_string()
}

///|
fn unquote(value : String) -> String {
  @percent.decode_lossy(value)
}

///|
fn unquote_plus(value : String) -> String {
  @percent.decode_lossy(value.replace(old="+", new="%20"))
}

///|
/// One parsed chunk of a template's path portion.
pub(all) enum TemplateSegment {
  Literal(Array[Char])
  PathVar(name~ : String, wildcard~ : Bool)
}

///|
fn strip_query_blocks(uri_template : String) -> String {
  let buf = StringBuilder::StringBuilder()
  let chars = uri_template.to_array()
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '{' && i + 1 < chars.length() && chars[i + 1] == '?' {
      let mut j = i + 2
      while j < chars.length() && chars[j] != '}' {
        j += 1
      }
      if j < chars.length() {
        i = j + 1
      } else {
        buf.write_char(chars[i])
        i += 1
      }
    } else {
      buf.write_char(chars[i])
      i += 1
    }
  }
  buf.to_string()
}

///|
/// Parse the path portion of a URI template into literal and variable
/// segments; query blocks are dropped for path matching.
pub fn parse_template(uri_template : String) -> Array[TemplateSegment] {
  let stripped = strip_query_blocks(uri_template)
  let segments : Array[TemplateSegment] = []
  let chars = stripped.to_array()
  let buf : Array[Char] = []
  fn flush(segments : Array[TemplateSegment], buf : Array[Char]) -> Unit {
    if buf.length() > 0 {
      segments.push(Literal(buf.copy()))
      buf.clear()
    }
  }
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '{' {
      let mut j = i + 1
      while j < chars.length() && chars[j] != '}' {
        j += 1
      }
      if j < chars.length() {
        flush(segments, buf)
        let name_buf = StringBuilder::StringBuilder()
        let mut k = i + 1
        while k < j {
          name_buf.write_char(chars[k])
          k += 1
        }
        let mut name = name_buf.to_string()
        let mut wildcard = false
        if name.has_suffix("*") {
          wildcard = true
          name = name[:name.length() - 1].to_owned()
        }
        segments.push(PathVar(name~, wildcard~))
        i = j + 1
        continue
      }
    }
    buf.push(chars[i])
    i += 1
  }
  flush(segments, buf)
  segments
}

///|
/// Extract query parameter names from RFC 6570 `{?param1,param2}` syntax.
///
/// The explode modifier is stripped, so `{?tags*}` yields `tags`.
pub fn extract_query_params(uri_template : String) -> Array[String] {
  let names : Array[String] = []
  let block = query_block(uri_template)
  guard block is Some(inner) else { return names }
  for raw in inner.split(",") {
    let name = raw.to_owned().trim()
    if name.has_suffix("*") {
      names.push(name[:name.length() - 1].to_owned())
    } else {
      names.push(name.to_owned())
    }
  }
  names
}

///|
/// Extract query parameter names declared with the explode modifier:
/// `{?tags*}` marks `tags` as repeatable.
pub fn extract_exploded_query_params(uri_template : String) -> Array[String] {
  let names : Array[String] = []
  let block = query_block(uri_template)
  guard block is Some(inner) else { return names }
  for raw in inner.split(",") {
    let name = raw.to_owned().trim()
    if name.has_suffix("*") {
      names.push(name[:name.length() - 1].to_owned())
    }
  }
  names
}

///|
fn query_block(uri_template : String) -> String? {
  let chars = uri_template.to_array()
  let mut i = 0
  while i < chars.length() {
    if chars[i] == '{' && i + 1 < chars.length() && chars[i + 1] == '?' {
      let mut j = i + 2
      while j < chars.length() && chars[j] != '}' {
        j += 1
      }
      if j < chars.length() {
        let buf = StringBuilder::StringBuilder()
        let mut k = i + 2
        while k < j {
          buf.write_char(chars[k])
          k += 1
        }
        return Some(buf.to_string())
      }
      return None
    }
    i += 1
  }
  None
}

///|
fn chars_to_string(u : Array[Char], start : Int32, end_ : Int32) -> String {
  let buf = StringBuilder::StringBuilder()
  for i in start.. Int32? {
  let count = want.length()
  guard pos + 3 * count <= u.length() else { return None }
  for i in 0.. Map[String, String]? {
  if k == lit.length() {
    return match_segments(segs, seg_idx + 1, u, pos, params)
  }
  let want = @utf8.encode(lit[k].to_string())
  // Option 1: the character as written.
  if pos < u.length() && u[pos] == lit[k] {
    let matched = match_literal(segs, seg_idx, lit, k + 1, u, pos + 1, params)
    if matched is Some(_) {
      return matched
    }
  }
  // Option 2: the character percent-encoded, hex digits in either case.
  match match_percent_encoded(u, pos, want) {
    Some(next) => match_literal(segs, seg_idx, lit, k + 1, u, next, params)
    None => None
  }
}

///|
fn match_segments(
  segs : Array[TemplateSegment],
  seg_idx : Int32,
  u : Array[Char],
  pos : Int32,
  params : Map[String, String],
) -> Map[String, String]? {
  if seg_idx == segs.length() {
    return if pos == u.length() { Some(params) } else { None }
  }
  match segs[seg_idx] {
    Literal(lit) => match_literal(segs, seg_idx, lit, 0, u, pos, params)
    PathVar(name~, wildcard~) => {
      let mut result : Map[String, String]? = None
      if wildcard {
        // `.+` captures across segments; longest candidate first.
        let mut end_ = u.length()
        while end_ >= pos + 1 && result is None {
          let next_params = params.copy()
          next_params[name] = chars_to_string(u, pos, end_)
          let matched = match_segments(segs, seg_idx + 1, u, end_, next_params)
          if matched is Some(_) {
            result = matched
          }
          end_ -= 1
        }
      } else {
        // `[^/]+` captures within one segment.
        let mut end_ = pos + 1
        while end_ <= u.length() && result is None {
          if u[end_ - 1] == '/' {
            break
          }
          let next_params = params.copy()
          next_params[name] = chars_to_string(u, pos, end_)
          let matched = match_segments(segs, seg_idx + 1, u, end_, next_params)
          if matched is Some(_) {
            result = matched
          }
          end_ += 1
        }
      }
      result
    }
  }
}

///|
fn json_of_param(value : String) -> Json {
  Json::string(value)
}

///|
/// Match a URI against a template and extract both path and query
/// parameters, percent-decoded.
///
/// `list_params` names non-exploded query parameters that hold lists; their
/// first value is comma-split per RFC 6570 section 3.2.8. Pass pre-parsed
/// `segments` when the caller already holds them.
pub fn match_uri_template(
  uri : String,
  uri_template : String,
  list_params~ : Array[String] = [],
  segments? : Array[TemplateSegment],
) -> Map[String, Json]? {
  // A fragment is not part of the resource identity; drop it before
  // splitting off the query unless the template spells out a literal `#`.
  let uri = if uri_template.contains("#") {
    uri
  } else {
    match uri.find("#") {
      Some(index) => uri[:index].to_owned()
      None => uri
    }
  }
  let mut path_part = uri
  let mut query_part = ""
  match uri.find("?") {
    Some(index) => {
      path_part = uri[:index].to_owned()
      query_part = uri[index + 1:].to_owned()
    }
    None => ()
  }
  let segs = match segments {
    Some(segs) => segs
    None => parse_template(uri_template)
  }
  let u = path_part.to_array()
  guard match_segments(segs, 0, u, 0, {}) is Some(raw_params) else {
    return None
  }
  // Percent-decode captured path values.
  let params : Map[String, Json] = Map([])
  for name, value in raw_params {
    params[name] = json_of_param(unquote(value))
  }
  if query_part.length() > 0 {
    // Repeated keys collect; blank values are preserved so callers can
    // distinguish "explicitly empty" from "missing".
    let parsed_query : Map[String, Array[String]] = Map([])
    let raw_query : Map[String, String] = Map([])
    for pair in query_part.split("&") {
      let pair_str = pair.to_owned()
      if pair_str.is_empty() {
        continue
      }
      let mut name = ""
      let mut value = ""
      match pair_str.find("=") {
        Some(index) => {
          name = unquote_plus(pair_str[:index].to_owned())
          value = pair_str[index + 1:].to_owned()
        }
        None => {
          name = unquote_plus(pair_str)
          value = ""
        }
      }
      let decoded = unquote_plus(value)
      match parsed_query.get(name) {
        Some(existing) => existing.push(decoded)
        None => parsed_query[name] = [decoded]
      }
      if raw_query.get(name) is None {
        raw_query[name] = value
      }
    }
    let exploded = extract_exploded_query_params(uri_template)
    let query_names = extract_query_params(uri_template)
    for name in query_names {
      guard parsed_query.get(name) is Some(values) else { continue }
      // Hyphens normalise to underscores; path params win.
      let key = name.replace(old="-", new="_")
      if params.get(key) is Some(_) {
        continue
      }
      let is_exploded = {
        let mut found = false
        for candidate in exploded {
          if candidate == name {
            found = true
            break
          }
        }
        found
      }
      let is_list = {
        let mut found = false
        for candidate in list_params {
          if candidate == name {
            found = true
            break
          }
        }
        found
      }
      if is_exploded {
        params[key] = Json::array(values.map(Json::string))
      } else if is_list {
        let raw = raw_query.get(name).unwrap_or("")
        if raw.is_empty() {
          params[key] = Json::array([])
        } else {
          let items : Array[Json] = []
          for item in raw.split(",") {
            items.push(Json::string(unquote_plus(item.to_owned())))
          }
          params[key] = Json::array(items)
        }
      } else {
        params[key] = Json::string(values[0])
      }
    }
  }
  Some(params)
}

///|
fn json_param_to_string(value : Json) -> String {
  match value {
    String(text) => text
    _ => value.stringify()
  }
}

///|
/// Expand a URI template with parameters — the inverse of
/// `match_uri_template`. Params use underscored keys but templates may use
/// hyphens, so both spellings are tried.
pub fn expand_uri_template(
  uri_template : String,
  params : Map[String, Json],
) -> String {
  let chars = uri_template.to_array()
  let buf = StringBuilder::StringBuilder()
  let lit = StringBuilder::StringBuilder()

  fn flush_literal(buf : StringBuilder, lit : StringBuilder) -> Unit {
    buf.write_string(encode_literal(lit.to_string()))
    lit.reset()
  }

  fn expand_query(inner : String) -> Unit {
    let parts : Array[String] = []
    for raw in inner.split(",") {
      let mut name = raw.to_owned().trim().to_owned()
      let exploded = name.has_suffix("*")
      if exploded {
        name = name[:name.length() - 1].to_owned()
      }
      let underscored = name.replace(old="-", new="_")
      let value = match params.get(name) {
        Some(value) => Some(value)
        None => params.get(underscored)
      }
      guard value is Some(value) else { continue }
      let encoded_name = pct_encode(name)
      match value {
        Array(values) =>
          if exploded {
            for v in values {
              parts.push(
                "\{encoded_name}=\{pct_encode(json_param_to_string(v))}",
              )
            }
          } else if values.length() > 0 {
            let joined = values
              .map(fn(v) { pct_encode(json_param_to_string(v)) })
              .join(",")
            parts.push("\{encoded_name}=\{joined}")
          }
        value => {
          let text = json_param_to_string(value)
          parts.push("\{encoded_name}=\{pct_encode(text)}")
        }
      }
    }
    if parts.length() > 0 {
      let query = parts.join("&")
      buf.write_string("?\{query}")
    }
  }

  let mut i = 0
  while i < chars.length() {
    if chars[i] == '{' {
      // Find the closing brace.
      let mut j = i + 1
      while j < chars.length() && chars[j] != '}' {
        j += 1
      }
      if j < chars.length() {
        let inner_buf = StringBuilder::StringBuilder()
        let mut k = i + 1
        while k < j {
          inner_buf.write_char(chars[k])
          k += 1
        }
        let inner = inner_buf.to_string()
        if inner.has_prefix("?") {
          flush_literal(buf, lit)
          expand_query(inner[1:].to_owned())
          i = j + 1
          continue
        }
        flush_literal(buf, lit)
        let mut name = inner
        let mut wildcard = false
        if name.has_suffix("*") {
          wildcard = true
          name = name[:name.length() - 1].to_owned()
        }
        let underscored = name.replace(old="-", new="_")
        let hyphenated = name.replace(old="_", new="-")
        let forms : Array[String] = [name]
        if underscored != name {
          forms.push(underscored)
        }
        if hyphenated != name {
          forms.push(hyphenated)
        }
        let mut substituted = false
        for form in forms {
          if substituted {
            break
          }
          match params.get(form) {
            Some(value) => {
              let text = json_param_to_string(value)
              buf.write_string(
                if wildcard {
                  pct_encode(text, safe="/")
                } else {
                  pct_encode(text)
                },
              )
              substituted = true
            }
            None => ()
          }
        }
        if !substituted {
          buf.write_char('{')
          buf.write_string(name)
          if wildcard {
            buf.write_char('*')
          }
          buf.write_char('}')
        }
        i = j + 1
        continue
      }
    }
    lit.write_char(chars[i])
    i += 1
  }
  flush_literal(buf, lit)
  buf.to_string()
}

///|
/// Build the URI a forwarding layer (mount or proxy) sends to the server
/// behind it: the path is expanded from the template, but the query string
/// is carried over from the incoming URI byte for byte.
pub fn forward_uri(
  uri_template : String,
  params : Map[String, Json],
  uri : String,
) -> String {
  let path_template = strip_query_blocks(uri_template)
  let forwarded = expand_uri_template(path_template, params)
  match uri.find("?") {
    Some(index) => {
      let query = uri[index + 1:].to_owned()
      let result = "\{forwarded}?\{query}"
      result
    }
    None => forwarded
  }
}