///|
fn hex_digit(b : Byte) -> Int? {
  if b >= b'0' && b <= b'9' {
    Some(b.to_int() - b'0'.to_int())
  } else if b >= b'a' && b <= b'f' {
    Some(b.to_int() - b'a'.to_int() + 10)
  } else if b >= b'A' && b <= b'F' {
    Some(b.to_int() - b'A'.to_int() + 10)
  } else {
    None
  }
}

///|
pub fn url_decode(bytes : BytesView) -> String {
  let len = bytes.length()
  let res = Array::make(len, b'\x00')
  let mut w = 0
  let mut r = 0
  while r < len {
    let b = bytes[r]
    if b == b'+' {
      res[w] = b' '
      w = w + 1
      r = r + 1
    } else if b == b'%' && r + 2 < len {
      match (hex_digit(bytes[r + 1]), hex_digit(bytes[r + 2])) {
        (Some(h1), Some(h2)) => {
          res[w] = (h1 * 16 + h2).to_byte()
          w = w + 1
          r = r + 3
        }
        _ => {
          res[w] = b'%'
          w = w + 1
          r = r + 1
        }
      }
    } else {
      res[w] = b
      w = w + 1
      r = r + 1
    }
  }
  let final_bytes = Bytes::from_array(res)
  let view = final_bytes[0:w]
  @utf8.decode(view) catch {
    _ => ""
  }
}

///|
pub fn parse_form_data(bytes : BytesView) -> Map[String, String] {
  let res = Map([])
  if bytes.length() == 0 {
    return res
  }

  // Split by '&'
  let mut start = 0
  let len = bytes.length()
  for i in 0.. Unit {
  let len = part.length()
  let mut eq_idx = -1
  for i in 0.. String {
  let buf = StringBuilder::new()
  for c in s {
    match c {
      '&' => buf.write_string("&")
      '<' => buf.write_string("<")
      '>' => buf.write_string(">")
      '"' => buf.write_string(""")
      '\'' => buf.write_string("'")
      _ => buf.write_char(c)
    }
  }
  buf.to_string()
}

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

///|
pub fn url_encode(s : String) -> String {
  let bytes = @utf8.encode(s)
  let buf = StringBuilder::new()
  for i in 0.. buf.write_char(c)
        None => ()
      }
    } else {
      buf.write_char('%')
      let hex_bytes = b"0123456789ABCDEF"
      let h1 = (b.to_int() >> 4) & 0xF
      let h2 = b.to_int() & 0xF
      match hex_bytes[h1].to_int().to_char() {
        Some(c) => buf.write_char(c)
        None => ()
      }
      match hex_bytes[h2].to_int().to_char() {
        Some(c) => buf.write_char(c)
        None => ()
      }
    }
  }
  buf.to_string()
}

///|
pub fn form_encode(map : Map[String, String]) -> String {
  let buf = StringBuilder::new()
  let mut first = true
  map.each(fn(k, v) {
    if !first {
      buf.write_char('&')
    }
    first = false
    buf.write_string(url_encode(k))
    buf.write_char('=')
    buf.write_string(url_encode(v))
  })
  buf.to_string()
}

///|
fn find_boundary(
  bytes : BytesView,
  boundary : BytesView,
  start_index : Int,
) -> Int {
  let len = bytes.length()
  let boundary_len = boundary.length()
  if start_index + boundary_len > len {
    return -1
  }
  for i in start_index..<=(len - boundary_len) {
    let mut match_found = true
    for j in 0.. Map[String, MultipartFormValue] {
  let res = Map([])
  let boundary_bytes = @utf8.encode("--" + boundary)
  let boundary_len = boundary_bytes.length()
  let len = bytes.length()
  let mut start = 0
  // Find first boundary
  let first_boundary = find_boundary(bytes, boundary_bytes[:], start)
  if first_boundary == -1 {
    return res
  }
  start = first_boundary + boundary_len
  while start < len {
    // Check for end boundary "--"
    if start + 2 <= len && bytes[start] == b'-' && bytes[start + 1] == b'-' {
      break
    }

    // Skip CRLF after boundary
    if start + 2 <= len && bytes[start] == b'\r' && bytes[start + 1] == b'\n' {
      start = start + 2
    } else {
      // Malformed or unexpected char, try to find next boundary
      let next = find_boundary(bytes, boundary_bytes[:], start)
      if next == -1 {
        break
      }
      start = next + boundary_len
      continue
    }

    // Find next boundary
    let next_boundary = find_boundary(bytes, boundary_bytes[:], start)
    if next_boundary == -1 {
      break
    }

    // Part content is between start and next_boundary - 2 (CRLF)
    let part_end = next_boundary - 2
    if part_end < start {
      // Should not happen if format is correct
      break
    }
    let part_bytes = bytes[start:part_end]
    parse_multipart_part(part_bytes, res)
    start = next_boundary + boundary_len
  }
  res
}

///|
fn parse_multipart_part(
  part : BytesView,
  res : Map[String, MultipartFormValue],
) -> Unit {
  // Find double CRLF separating headers from body
  let len = part.length()
  let mut body_start = -1
  for i in 0..<(len - 3) {
    if part[i] == b'\r' &&
      part[i + 1] == b'\n' &&
      part[i + 2] == b'\r' &&
      part[i + 3] == b'\n' {
      body_start = i + 4
      break
    }
  }
  if body_start == -1 {
    return
  }
  let headers_bytes = part[0:body_start - 4]
  let body_bytes = part[body_start:len]
  let headers_str = @utf8.decode(headers_bytes) catch { _ => return }
  let headers = headers_str.split("\r\n")
  let mut name = ""
  let mut filename : String? = None
  let mut content_type : String? = None
  for header in headers {
    let parts = header.split(":").to_array()
    if parts.length() >= 2 {
      let key = parts[0].trim().to_lower()
      let val = parts[1].trim()
      if key == "content-disposition" {
        // form-data; name="field"; filename="file.txt"
        let directives = val.split(";").to_array()
        for i in 0..