/// Header value decoding: RFC 2047 encoded-words and RFC 2231 parameters.
///
/// RFC 2047 (MIME Part Three) defines encoded-words for representing
/// non-ASCII text in header fields:
///
///   encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
///
/// RFC 2231 (MIME Parameter Value and Encoded Word Extensions) defines
/// the parameter value continuation and character-set extensions used
/// in Content-Type / Content-Disposition headers:
///
///   filename*=UTF-8''%E4%B8%AD%E6%96%87.txt
///   filename*0*=UTF-8''%E4%B8%AD; filename*1*=%E6%96%87.txt
///
/// This module provides full decoding for both mechanisms.

///|
/// Decode all RFC 2047 encoded-words in a header value.
///
/// Adjacent encoded-words are concatenated without a separator,
/// while a regular space between encoded-words is collapsed.
/// Non-encoded text is passed through unchanged.
pub fn decode_header_value(input : String) -> String {
  let s = input.trim(chars=" \t\r\n").to_owned()
  let sb = StringBuilder::new()
  let len = s.length()
  let mut i = 0
  let mut last_was_encoded = false

  while i < len {
    // Check for encoded-word start
    if i + 2 < len &&
      s.get_char(i).unwrap() == '=' &&
      s.get_char(i + 1).unwrap() == '?' {
      // Find the closing ?=
      let end = find_encoded_end(s, i + 2)
      match end {
        None => {
          // No closing sequence: write rest as-is
          sb.write_string(s.unsafe_substring(start=i, end=len))
          break
        }
        Some(close_pos) => {
          let word = s.unsafe_substring(start=i, end=close_pos + 2)
          let decoded = decode_encoded_word(word)
          match decoded {
            Some(text) => {
              // Collapse a space before an encoded-word if the previous
              // token was also an encoded-word or the space is inside
              if !last_was_encoded && sb.to_string().has_suffix(" ") {
                // Remove the trailing space: handle in write below
              }
              sb.write_string(text)
              last_was_encoded = true
            }
            None => {
              sb.write_string(word)
              last_was_encoded = false
            }
          }
          i = close_pos + 2
          // Skip a single space between two encoded-words
          if i < len && s.get_char(i).unwrap() == ' ' {
            // Peek: if next is another encoded-word, skip the space
            if i + 1 < len &&
              s.get_char(i + 1).unwrap() == '=' &&
              s.get_char(i + 2).unwrap() == '?' {
              i = i + 1
            } else {
              sb.write_char(' ')
              i = i + 1
            }
          }
          continue
        }
      }
    }
    sb.write_char(s.get_char(i).unwrap())
    last_was_encoded = false
    i = i + 1
  }

  sb.to_string()
}

///|
/// Find the position of the closing "?=" of an encoded-word starting at
/// the "?..." part. Returns the index of the last '='.
fn find_encoded_end(s : String, start : Int) -> Int? {
  let len = s.length()
  let mut i = start
  while i + 1 < len {
    if s.get_char(i).unwrap() == '?' && s.get_char(i + 1).unwrap() == '=' {
      return Some(i)
    }
    i = i + 1
  }
  None
}

///|
/// Decode a single RFC 2231 extended parameter value.
///
/// Handles the charset and language prefix: "UTF-8''..." where the
/// remainder is percent-encoded.
pub fn decode_rfc2231_value(input : String) -> String? {
  let s = input.trim(chars=" \t").to_owned()
  // Look for the charset'language' prefix
  let q1 = s.find("'")
  match q1 {
    None =>
      // No charset prefix: percent-decode the whole value
      percent_decode(s)
    Some(p1) => {
      // charset before p1
      let charset = s.unsafe_substring(start=0, end=p1).to_upper()
      let rest = s.unsafe_substring(start=p1 + 1, end=s.length())
      let q2 = rest.find("'")
      match q2 {
        None => percent_decode(rest)
        Some(p2) => {
          let lang = rest.unsafe_substring(start=0, end=p2)
          ignore(lang)
          let encoded = rest.unsafe_substring(start=p2 + 1, end=rest.length())
          let decoded = percent_decode(encoded)
          match decoded {
            Some(text) => Some(text)
            None =>
              // Fall back to raw if charset is not UTF-8
              if charset == "UTF-8" {
                None
              } else {
                Some(encoded)
              }
          }
        }
      }
    }
  }
}

///|
/// Percent-decode a string: %XX sequences become their byte values.
/// Multi-byte UTF-8 sequences are decoded into Unicode characters.
pub fn percent_decode(input : String) -> String? {
  let bytes : Array[Int] = []
  let len = input.length()
  let mut i = 0
  while i < len {
    let c = input.get_char(i).unwrap()
    if c == '%' {
      if i + 2 < len {
        let h1 = input.get_char(i + 1).unwrap()
        let h2 = input.get_char(i + 2).unwrap()
        let byte = hex_pair(h1, h2)
        match byte {
          Some(b) => {
            bytes.push(b)
            i = i + 3
            continue
          }
          None => ()
        }
      }
      bytes.push(c.to_int())
      i = i + 1
    } else {
      bytes.push(c.to_int())
      i = i + 1
    }
  }
  Some(utf8_decode(bytes))
}

///|
/// Convert two hex characters to a byte value.
fn hex_pair(h1 : Char, h2 : Char) -> Int? {
  let hi = hex_char_to_int(h1)
  let lo = hex_char_to_int(h2)
  match (hi, lo) {
    (Some(a), Some(b)) => Some(a * 16 + b)
    _ => None
  }
}

///|
/// Merge RFC 2231 parameter continuations into complete parameter values.
///
/// Handles both:
///   - Single-part extended values: name*=charset'lang'value
///   - Continued values: name*0*=..., name*1*=..., name*2=...
pub fn merge_extended_params(
  params : Array[(String, String)],
) -> Array[(String, String)] {
  let results : Array[(String, String)] = []

  // First pass: collect regular (non-extended) parameters
  let mut i = 0
  while i < params.length() {
    let (k, v) = params[i]
    if !k.contains("*") {
      results.push((k, v))
    }
    i = i + 1
  }

  // Second pass: collect extended parameters by base name
  let seen : Array[String] = []
  let mut j = 0
  while j < params.length() {
    let (k, _) = params[j]
    if k.contains("*") {
      // Extract base name: strip *N* or * suffixes
      let base = base_param_name(k)
      if !seen.contains(base) {
        seen.push(base)
        let value = join_continuations(params, base)
        match value {
          Some(decoded) => results.push((base, decoded))
          None => ()
        }
      }
    }
    j = j + 1
  }

  results
}

///|
/// Extract the base parameter name from an extended name.
/// "filename*0*" -> "filename", "name*" -> "name"
fn base_param_name(key : String) -> String {
  let star = key.find("*")
  match star {
    None => key
    Some(pos) => key.unsafe_substring(start=0, end=pos)
  }
}

///|
/// Join all continuation segments of an extended parameter and decode.
fn join_continuations(
  params : Array[(String, String)],
  base : String,
) -> String? {
  // Collect segments with their sequence numbers
  let segments : Array[(Int, String)] = []

  let mut i = 0
  while i < params.length() {
    let (k, v) = params[i]
    let kk = k.to_lower()
    if kk == base + "*" {
      // Single-part extended value (no segment number)
      segments.push((-1, v))
    } else if kk.has_prefix(base + "*") {
      // Segmented: base*N* or base*N
      let rest = kk.unsafe_substring(start=base.length() + 1, end=kk.length())
      let num_parts = split_char(rest, '*')
      if num_parts.length() >= 1 {
        let num = num_parts[0]
        match parse_num(num) {
          Some(n) => segments.push((n, v))
          None => ()
        }
      }
    }
    i = i + 1
  }

  if segments.length() == 0 {
    return None
  }

  // Sort by segment number
  sort_segments(segments)

  // Concatenate raw parts
  let raw = StringBuilder::new()
  for si = 0; si < segments.length(); si = si + 1 {
    raw.write_string(segments[si].1)
  }

  // Decode: first segment may carry the charset prefix
  let combined = raw.to_string()
  let first = segments[0]
  if first.0 == -1 || segments.length() == 1 {
    decode_rfc2231_value(combined)
  } else {
    // First segment may have charset'lang' prefix; decode whole
    decode_rfc2231_value(combined)
  }
}

///|
/// Parse a small integer from a string.
fn parse_num(s : String) -> Int? {
  if s.length() == 0 {
    return None
  }
  let mut result = 0
  for c in s {
    if c >= '0' && c <= '9' {
      result = result * 10 + (c.to_int() - '0'.to_int())
    } else {
      return None
    }
  }
  Some(result)
}

///|
/// Sort (Int, String) pairs by the integer in ascending order (insertion sort).
fn sort_segments(segments : Array[(Int, String)]) -> Unit {
  let mut i = 1
  while i < segments.length() {
    let mut j = i
    while j > 0 && segments[j - 1].0 > segments[j].0 {
      let tmp = segments[j - 1]
      segments[j - 1] = segments[j]
      segments[j] = tmp
      j = j - 1
    }
    i = i + 1
  }
}

///|
/// Decode a Content-Type header with RFC 2231 extended parameters.
pub fn decode_content_type(value : String) -> ContentType? {
  let ct = parse_content_type(value)
  match ct {
    None => None
    Some(c) => {
      let merged = merge_extended_params(c.params)
      Some(ContentType::{
        media_type: c.media_type,
        sub_type: c.sub_type,
        params: merged,
      })
    }
  }
}

///|
/// Decode a Content-Disposition header with RFC 2231 extended parameters.
pub fn decode_content_disposition(value : String) -> ContentDisposition? {
  let cd = parse_content_disposition(value)
  match cd {
    None => None
    Some(d) => {
      let merged = merge_extended_params(d.params)
      Some(ContentDisposition::{ disposition: d.disposition, params: merged })
    }
  }
}

///|
/// Get a decoded filename from a Content-Disposition, handling RFC 2231.
pub fn decoded_filename(cd : ContentDisposition) -> String? {
  // Prefer the extended filename parameter
  let mut found : String? = None
  for i = 0; i < cd.params.length(); i = i + 1 {
    let (k, v) = cd.params[i]
    if k == "filename" {
      found = Some(v)
    }
  }
  found
}

///|
/// Check whether a header value contains any RFC 2047 encoded-word.
pub fn has_encoded_words(input : String) -> Bool {
  input.contains("=?") && input.contains("?=")
}

///|
/// Count the number of encoded-words in a header value.
pub fn count_encoded_words(input : String) -> Int {
  let mut count = 0
  let len = input.length()
  let mut i = 0
  while i + 1 < len {
    if input.get_char(i).unwrap() == '=' &&
      input.get_char(i + 1).unwrap() == '?' {
      // Skip past the encoded-word
      let end = find_encoded_end(input, i + 2)
      match end {
        Some(close) => {
          count = count + 1
          i = close + 2
          continue
        }
        None => ()
      }
    }
    i = i + 1
  }
  count
}

///|
/// Strip comments (parenthesized text) from a header value.
pub fn strip_comments(input : String) -> String {
  let sb = StringBuilder::new()
  let mut depth = 0
  let mut in_quote = false
  for c in input {
    if c == '"' && depth == 0 {
      in_quote = !in_quote
      sb.write_char(c)
    } else if c == '(' && !in_quote {
      depth = depth + 1
    } else if c == ')' && !in_quote {
      if depth > 0 {
        depth = depth - 1
      }
    } else if depth == 0 {
      sb.write_char(c)
    }
  }
  collapse_ws(sb.to_string())
}