///|
pub fn parse_message(raw : String) -> ParseResult {
  let split = split_header_body(raw)
  let headers = parse_header_block(split.0)
  let root = parse_part_from_headers(headers, split.1)
  let msg = {
    headers,
    from: parse_address_list(header_value(headers, "From")),
    to: parse_address_list(header_value(headers, "To")),
    cc: parse_address_list(header_value(headers, "Cc")),
    subject: normalize_subject(header_value(headers, "Subject")),
    date: header_value(headers, "Date"),
    message_id: header_value(headers, "Message-ID"),
    root,
  }
  { message: msg, issues: collect_message_issues(msg) }
}

///|
fn split_header_body(raw : String) -> (String, String) {
  let lines = split_lines(raw)
  let mut header_text = ""
  let mut body_text = ""
  let mut in_body = false
  for line in lines {
    if !in_body && line == "" {
      in_body = true
    } else if in_body {
      body_text = body_text + line + "\n"
    } else {
      header_text = header_text + line + "\n"
    }
  }
  (header_text, body_text)
}

///|
fn parse_part(raw : String) -> MailPart {
  let split = split_header_body(raw)
  let headers = parse_header_block(split.0)
  parse_part_from_headers(headers, split.1)
}

///|
fn parse_part_from_headers(headers : Array[Header], body : String) -> MailPart {
  let ct = content_type_from_headers(headers)
  let enc = encoding_from_headers(headers)
  let disp = disposition_from_headers(headers)
  let filename = filename_from_headers(headers, ct)
  if ct.media_type == "multipart" && ct.boundary != "" {
    let children = parse_multipart(body, ct.boundary)
    mail_part(headers, ct, enc, disp, filename, body, "", children)
  } else {
    mail_part(headers, ct, enc, disp, filename, body, decode_body(body, enc), [])
  }
}

///|
fn parse_multipart(body : String, boundary : String) -> Array[MailPart] {
  let marker = "--" + boundary
  let closing = marker + "--"
  let lines = split_lines(body)
  let parts : Array[MailPart] = []
  let mut current = ""
  let mut inside = false
  for line in lines {
    if line == marker {
      if inside && current != "" {
        parts.push(parse_part(current))
      }
      current = ""
      inside = true
    } else if line == closing {
      if inside && current != "" {
        parts.push(parse_part(current))
      }
      inside = false
      current = ""
    } else if inside {
      current = current + line + "\n"
    }
  }
  parts
}

///|
fn collect_message_issues(message : MailMessage) -> Array[ParseIssue] {
  let issues : Array[ParseIssue] = []
  if message.from.length() == 0 {
    issues.push(parse_issue("missing.from", "message has no From header"))
  }
  if message.to.length() == 0 {
    issues.push(parse_issue("missing.to", "message has no To header"))
  }
  if message.subject == "" {
    issues.push(parse_issue("missing.subject", "message has no Subject header"))
  }
  collect_part_issues(message.root, issues)
  issues
}

///|
fn collect_part_issues(part : MailPart, issues : Array[ParseIssue]) -> Unit {
  if part.content_type.media_type == "multipart" &&
    part.content_type.boundary == "" {
    issues.push(
      parse_issue("missing.boundary", "multipart part has no boundary"),
    )
  }
  for child in part.children {
    collect_part_issues(child, issues)
  }
}