///|
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::new()
  if bytes.length() == 0 {
    return res
  }

  // Split by '&'
  let mut start = 0
  let len = bytes.length()
  for i = 0; i < len; i = i + 1 {
    if bytes[i] == b'&' {
      let part = bytes[start:i]
      parse_kv(part, res)
      start = i + 1
    }
  }
  if start < len {
    parse_kv(bytes[start:len], res)
  }
  res
}

///|
fn parse_kv(part : BytesView, map : Map[String, String]) -> Unit {
  let len = part.length()
  let mut eq_idx = -1
  for i = 0; i < len; i = i + 1 {
    if part[i] == b'=' {
      eq_idx = i
      break
    }
  }
  if eq_idx != -1 {
    let key_bytes = part[0:eq_idx]
    let val_bytes = part[eq_idx + 1:len]
    let key = url_decode(key_bytes)
    let val = url_decode(val_bytes)
    if key != "" {
      map.set(key, val)
    }
  } else {
    // No '=', treat as key with empty value? Or ignore?
    // Usually key without value.
    let key = url_decode(part)
    if key != "" {
      map.set(key, "")
    }
  }
}

///|
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 = 0; i < bytes.length(); i = i + 1 {
    let b = bytes[i]
    if is_unreserved(b) {
      match b.to_int().to_char() {
        Some(c) => 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 not(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 = start_index; i <= len - boundary_len; i = i + 1 {
    let mut match_found = true
    for j = 0; j < boundary_len; j = j + 1 {
      if bytes[i + j] != boundary[j] {
        match_found = false
        break
      }
    }
    if match_found {
      return i
    }
  }
  -1
}

///|
pub fn parse_multipart(
  bytes : BytesView,
  boundary : String,
) -> Map[String, MultipartFormValue] {
  let res = Map::new()
  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 = 0; i < len - 3; i = i + 1 {
    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 = 0; i < directives.length(); i = i + 1 {
          let directive = directives[i].trim()
          if directive.has_prefix("name=") {
            let d_str = directive.to_string()
            let substr = d_str[6:d_str.length() - 1]
            name = substr.to_string()
          } else if directive.has_prefix("filename=") {
            let d_str = directive.to_string()
            let substr = d_str[10:d_str.length() - 1]
            filename = Some(substr.to_string())
          }
        }
      } else if key == "content-type" {
        content_type = Some(val.to_string())
      }
    }
  }
  if name != "" {
    res.set(name, { filename, content_type, data: body_bytes })
  }
}