///|
pub fn parse_header_block(input : String) -> Array[Header] {
  let lines = split_lines(input)
  let unfolded : Array[String] = []
  for line in lines {
    if is_header_continuation(line) && unfolded.length() > 0 {
      let last = unfolded[unfolded.length() - 1]
      unfolded[unfolded.length() - 1] = last + " " + trim_ascii(line)
    } else if line != "" {
      unfolded.push(line)
    }
  }
  let headers : Array[Header] = []
  for line in unfolded {
    match split_once(line, ':') {
      Some((name, value)) =>
        headers.push(header(trim_ascii(name), trim_ascii(value)))
      None => ()
    }
  }
  headers
}

///|
pub fn header_value(headers : Array[Header], name : String) -> String {
  let target = lower_ascii(name)
  for item in headers {
    if lower_ascii(item.name) == target {
      return item.value
    }
  }
  ""
}

///|
pub fn all_header_values(
  headers : Array[Header],
  name : String,
) -> Array[String] {
  let target = lower_ascii(name)
  let out : Array[String] = []
  for item in headers {
    if lower_ascii(item.name) == target {
      out.push(item.value)
    }
  }
  out
}

///|
pub fn parse_content_type(value : String) -> ContentType {
  let parts = split_char(value, ';')
  let main = if parts.length() > 0 {
    trim_ascii(parts[0])
  } else {
    "text/plain"
  }
  let media_parts = split_char(lower_ascii(main), '/')
  let media_type = if media_parts.length() > 0 {
    trim_ascii(media_parts[0])
  } else {
    "text"
  }
  let subtype = if media_parts.length() > 1 {
    trim_ascii(media_parts[1])
  } else {
    "plain"
  }
  let params : Array[(String, String)] = []
  let mut charset = if media_type == "text" { "us-ascii" } else { "" }
  let mut boundary = ""
  let mut name = ""
  let mut i = 1
  while i < parts.length() {
    match split_once(parts[i], '=') {
      Some((key, raw_value)) => {
        let k = lower_ascii(trim_ascii(key))
        let v = remove_quotes(raw_value)
        params.push((k, v))
        if k == "charset" {
          charset = v
        } else if k == "boundary" {
          boundary = v
        } else if k == "name" {
          name = v
        }
      }
      None => ()
    }
    i = i + 1
  }
  content_type(media_type, subtype, charset, boundary, name, params)
}

///|
pub fn content_type_from_headers(headers : Array[Header]) -> ContentType {
  let value = header_value(headers, "Content-Type")
  if value == "" {
    default_content_type()
  } else {
    parse_content_type(value)
  }
}

///|
pub fn parse_encoding(value : String) -> TransferEncoding {
  match lower_ascii(trim_ascii(value)) {
    "7bit" => SevenBit
    "8bit" => EightBit
    "binary" => Binary
    "base64" => Base64
    "quoted-printable" => QuotedPrintable
    "" => SevenBit
    other => UnknownEncoding(other)
  }
}

///|
pub fn encoding_from_headers(headers : Array[Header]) -> TransferEncoding {
  parse_encoding(header_value(headers, "Content-Transfer-Encoding"))
}

///|
pub fn disposition_from_headers(headers : Array[Header]) -> String {
  let value = header_value(headers, "Content-Disposition")
  if value == "" {
    ""
  } else {
    let parts = split_char(value, ';')
    if parts.length() > 0 {
      lower_ascii(trim_ascii(parts[0]))
    } else {
      ""
    }
  }
}

///|
pub fn filename_from_headers(
  headers : Array[Header],
  ct : ContentType,
) -> String {
  let disp = header_value(headers, "Content-Disposition")
  let parts = split_char(disp, ';')
  for part in parts {
    match split_once(part, '=') {
      Some((key, value)) =>
        if lower_ascii(trim_ascii(key)) == "filename" {
          return remove_quotes(value)
        }
      None => ()
    }
  }
  ct.name
}