/// Email/MIME parser: converts raw email text into Message AST.
///
/// Parses RFC 5322 message format using a simple line-by-line approach.
/// Handles headers, body, MIME content types, and email addresses.

///|
/// Parse a full email message from raw text.
/// Splits headers from body at the first blank line.
pub fn parse_message(input : String) -> Message {
  let headers : Array[Header] = []
  let mut body_start = 0
  let mut i = 0
  let len = input.length()

  // Parse headers line by line
  let mut in_fold = false
  let mut current_name = ""
  let mut current_value = StringBuilder::new()

  while i < len {
    let rest = input.unsafe_substring(start=i, end=len)

    // Check for CRLF or LF
    let nl_pos = match rest.find("\r\n") {
      Some(p) => p
      None =>
        match rest.find("\n") {
          Some(p) => p
          None => len - i // no newline, read to end
        }
    }

    let line = if nl_pos > 0 {
      rest.unsafe_substring(start=0, end=nl_pos)
    } else {
      ""
    }

    let nl_len = if i + nl_pos + 2 <= len &&
      rest.unsafe_substring(start=nl_pos, end=nl_pos + 2) == "\r\n" {
      2
    } else if i + nl_pos < len && rest.get_char(nl_pos).unwrap() == '\n' {
      1
    } else {
      0
    }

    // Check if line is empty (blank line = end of headers)
    let trimmed_line = line.trim(chars=" \t").to_owned()
    if trimmed_line.length() == 0 && !in_fold {
      // Empty line: end of headers
      i = i + nl_pos + nl_len
      body_start = i
      break
    }

    // Check for header continuation (folding): line starts with WSP
    if in_fold {
      // Append to current header value
      current_value.write_char(' ')
      current_value.write_string(trimmed_line)
      i = i + nl_pos + nl_len
      // Check if next line is also folded
      let next_pos = i
      if next_pos < len {
        let next_ch = input.get_char(next_pos).unwrap()
        in_fold = next_ch == ' ' || next_ch == '\t'
      } else {
        in_fold = false
      }
      if !in_fold {
        // End of folded header
        headers.push(Header::{
          name: current_name,
          value: current_value.to_string(),
        })
        current_name = ""
        current_value = StringBuilder::new()
      }
      continue
    }

    // Look for colon separator: header name ends at first ':'
    let colon_pos = line.find(":")
    match colon_pos {
      None => {
        // No colon, skip this line as malformed
        i = i + nl_pos + nl_len
        continue
      }
      Some(cp) => {
        let name = line
          .unsafe_substring(start=0, end=cp)
          .trim_end(chars=" \t")
          .to_owned()
        let value_start = cp + 1
        let value = if value_start < line.length() {
          line
          .unsafe_substring(start=value_start, end=line.length())
          .trim(chars=" \t")
          .to_owned()
        } else {
          ""
        }
        current_name = name
        current_value = StringBuilder::new()
        current_value.write_string(value)
        i = i + nl_pos + nl_len

        // Check if next line is folded
        let next_pos = i
        if next_pos < len {
          let next_ch = input.get_char(next_pos).unwrap()
          in_fold = next_ch == ' ' || next_ch == '\t'
        } else {
          in_fold = false
        }
        if !in_fold {
          headers.push(Header::{
            name: current_name,
            value: current_value.to_string(),
          })
          current_name = ""
          current_value = StringBuilder::new()
        }
      }
    }
  }

  // Store any pending header
  if current_name.length() > 0 {
    headers.push(Header::{
      name: current_name,
      value: current_value.to_string(),
    })
  }

  // Parse body
  let body = if body_start < len {
    let body_text = input.unsafe_substring(start=body_start, end=len)
    if body_text.trim(chars=" \t\r\n").to_owned().length() > 0 {
      Some(body_text)
    } else {
      None
    }
  } else {
    None
  }

  Message::{ headers, body, mime_part: None }
}

///|
/// Parse a single email address from a string.
/// Supports: user@domain, , "Name" 
pub fn parse_address(input : String) -> EmailAddress? {
  let trimmed = input.trim(chars=" \t\r\n").to_owned()
  if trimmed.length() == 0 {
    return None
  }

  let addr_part = if trimmed.has_prefix("<") {
    let end = trimmed.find(">")
    match end {
      Some(pos) => trimmed.unsafe_substring(start=1, end=pos)
      None => return None
    }
  } else if trimmed.contains("<") && trimmed.has_suffix(">") {
    let start = trimmed.find("<")
    match start {
      Some(pos) =>
        trimmed.unsafe_substring(start=pos + 1, end=trimmed.length() - 1)
      None => return None
    }
  } else {
    trimmed
  }

  let at_pos = addr_part.find("@")
  match at_pos {
    None => None
    Some(pos) => {
      let lp = addr_part.unsafe_substring(start=0, end=pos)
      let domain = addr_part.unsafe_substring(
        start=pos + 1,
        end=addr_part.length(),
      )
      let clean_local = if lp.has_prefix("\"") && lp.has_suffix("\"") {
        lp.unsafe_substring(start=1, end=lp.length() - 1)
      } else {
        lp
      }
      if clean_local.length() > 0 && domain.length() > 0 {
        Some(EmailAddress::{ local_part: clean_local, domain })
      } else {
        None
      }
    }
  }
}

///|
/// Parse multiple email addresses from a header value.
pub fn parse_address_list(input : String) -> Array[EmailAddress] {
  let results : Array[EmailAddress] = []
  let trimmed = input.trim(chars=" \t\r\n").to_owned()
  if trimmed.length() == 0 {
    return results
  }

  let parts = split_addr_list(trimmed)
  for part in parts {
    let addr = parse_address(part)
    match addr {
      Some(a) => results.push(a)
      None => ()
    }
  }
  results
}

///|
/// Split address list by commas, respecting quotes and angle brackets.
fn split_addr_list(input : String) -> Array[String] {
  let results : Array[String] = []
  let mut start = 0
  let mut in_quote = false
  let mut in_angle = false
  let mut i = 0

  while i < input.length() {
    let c = input.get_char(i).unwrap()
    if c == '"' && !in_angle {
      in_quote = !in_quote
    } else if c == '<' && !in_quote {
      in_angle = true
    } else if c == '>' && !in_quote {
      in_angle = false
    } else if c == ',' && !in_quote && !in_angle {
      let part = input
        .unsafe_substring(start~, end=i)
        .trim(chars=" \t\r\n")
        .to_owned()
      if part.length() > 0 {
        results.push(part)
      }
      start = i + 1
    }
    i = i + 1
  }

  let last = input
    .unsafe_substring(start~, end=input.length())
    .trim(chars=" \t\r\n")
    .to_owned()
  if last.length() > 0 {
    results.push(last)
  }
  results
}

///|
/// Parse a MIME Content-Type header value.
pub fn parse_content_type(value : String) -> ContentType? {
  let trimmed = value.trim(chars=" \t").to_owned()
  if trimmed.length() == 0 {
    return None
  }

  let parts = split_semi(trimmed)
  if parts.length() == 0 {
    return None
  }

  let type_sub = parts[0]
  let slash_pos = type_sub.find("/")
  let (media_type, sub_type) = match slash_pos {
    None => (type_sub.to_lower(), "")
    Some(pos_slash) =>
      (
        type_sub.unsafe_substring(start=0, end=pos_slash).to_lower(),
        type_sub
        .unsafe_substring(start=pos_slash + 1, end=type_sub.length())
        .to_lower(),
      )
  }

  let params : Array[(String, String)] = []
  let mut pi = 1
  while pi < parts.length() {
    let param = parts[pi]
    let eq_pos = param.find("=")
    match eq_pos {
      None => ()
      Some(pos_eq) => {
        let key = param
          .unsafe_substring(start=0, end=pos_eq)
          .trim(chars=" \t")
          .to_lower()
          .to_owned()
        let val_raw = param
          .unsafe_substring(start=pos_eq + 1, end=param.length())
          .trim(chars=" \t")
          .to_owned()
        let val = if val_raw.has_prefix("\"") && val_raw.has_suffix("\"") {
          val_raw.unsafe_substring(start=1, end=val_raw.length() - 1)
        } else {
          val_raw
        }
        params.push((key, val))
      }
    }
    pi = pi + 1
  }

  Some(ContentType::{ media_type, sub_type, params })
}

///|
/// Parse a MIME Content-Disposition header value.
pub fn parse_content_disposition(value : String) -> ContentDisposition? {
  let trimmed = value.trim(chars=" \t").to_owned()
  if trimmed.length() == 0 {
    return None
  }

  let parts = split_semi(trimmed)
  if parts.length() == 0 {
    return None
  }

  let disposition = parts[0].to_lower()
  let params : Array[(String, String)] = []
  let mut pi = 1
  while pi < parts.length() {
    let param = parts[pi]
    let eq_pos = param.find("=")
    match eq_pos {
      None => ()
      Some(pos_eq) => {
        let key = param
          .unsafe_substring(start=0, end=pos_eq)
          .trim(chars=" \t")
          .to_lower()
          .to_owned()
        let val_raw = param
          .unsafe_substring(start=pos_eq + 1, end=param.length())
          .trim(chars=" \t")
          .to_owned()
        let val = if val_raw.has_prefix("\"") && val_raw.has_suffix("\"") {
          val_raw.unsafe_substring(start=1, end=val_raw.length() - 1)
        } else {
          val_raw
        }
        params.push((key, val))
      }
    }
    pi = pi + 1
  }

  Some(ContentDisposition::{ disposition, params })
}

///|
/// Split a string by semicolons, respecting quotes.
fn split_semi(input : String) -> Array[String] {
  let parts : Array[String] = []
  let mut start = 0
  let mut in_quote = false
  let mut i = 0
  while i < input.length() {
    let c = input.get_char(i).unwrap()
    if c == '"' {
      in_quote = !in_quote
    } else if c == ';' && !in_quote {
      let part = input
        .unsafe_substring(start~, end=i)
        .trim(chars=" \t")
        .to_owned()
      parts.push(part)
      start = i + 1
    }
    i = i + 1
  }
  let last = input
    .unsafe_substring(start~, end=input.length())
    .trim(chars=" \t")
    .to_owned()
  if last.length() > 0 {
    parts.push(last)
  }
  parts
}

///|
/// Decode an RFC 2047 encoded-word.
/// Format: =?charset?encoding?encoded_text?=
pub fn decode_encoded_word(input : String) -> String? {
  let s = input.trim(chars=" \t").to_owned()
  if !s.has_prefix("=?") || !s.has_suffix("?=") {
    return None
  }

  let inner = s.unsafe_substring(start=2, end=s.length() - 2)
  let q1 = inner.find("?")
  match q1 {
    None => None
    Some(p1) => {
      let after_charset = inner.unsafe_substring(
        start=p1 + 1,
        end=inner.length(),
      )
      let q2 = after_charset.find("?")
      match q2 {
        None => None
        Some(p2) => {
          let encoding = after_charset
            .unsafe_substring(start=0, end=p2)
            .to_upper()
          let encoded = after_charset.unsafe_substring(
            start=p2 + 1,
            end=after_charset.length(),
          )
          match encoding {
            "B" => decode_b64_word(encoded)
            "Q" => decode_q_word(encoded)
            _ => None
          }
        }
      }
    }
  }
}

///|
fn decode_b64_word(input : String) -> String? {
  let decoded = base64_decode(input)
  match decoded {
    Some(s) => Some(s)
    None => None
  }
}

///|
fn decode_q_word(input : String) -> String? {
  let result = StringBuilder::new()
  let len = input.length()
  let mut i = 0
  while i < len {
    let c = input.get_char(i).unwrap()
    if c == '_' {
      result.write_char(' ')
    } else 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_byte(h1, h2)
        match byte {
          Some(b) => result.write_char(Int::unsafe_to_char(b))
          None => ()
        }
        i = i + 2
      }
    } else {
      result.write_char(c)
    }
    i = i + 1
  }
  Some(result.to_string())
}

///|
fn hex_byte(h1 : Char, h2 : Char) -> Int? {
  fn hex_val(c : Char) -> Int? {
    if c >= '0' && c <= '9' {
      Some(c.to_int() - '0'.to_int())
    } else if c >= 'A' && c <= 'F' {
      Some(c.to_int() - 'A'.to_int() + 10)
    } else if c >= 'a' && c <= 'f' {
      Some(c.to_int() - 'a'.to_int() + 10)
    } else {
      None
    }
  }
  match (hex_val(h1), hex_val(h2)) {
    (Some(hi), Some(lo)) => Some(hi * 16 + lo)
    _ => None
  }
}

///|
fn base64_decode(input : String) -> String? {
  let table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let len = input.length()
  if len % 4 != 0 {
    return None
  }
  let result = StringBuilder::new()
  let mut i = 0
  while i < len {
    let c1 = input.get_char(i).unwrap()
    let c2 = input.get_char(i + 1).unwrap()
    let c3 = input.get_char(i + 2).unwrap()
    let c4 = input.get_char(i + 3).unwrap()

    let v1 = b64_index(table, c1)
    let v2 = b64_index(table, c2)
    let v3 = b64_index(table, c3)
    let v4 = b64_index(table, c4)

    match (v1, v2, v3, v4) {
      (Some(b1), Some(b2), Some(b3), Some(b4)) => {
        let byte1 = (b1 << 2) | (b2 >> 4)
        result.write_char(Int::unsafe_to_char(byte1))
        if c3 != '=' {
          let byte2 = ((b2 & 0xF) << 4) | (b3 >> 2)
          result.write_char(Int::unsafe_to_char(byte2))
        }
        if c4 != '=' {
          let byte3 = ((b3 & 0x3) << 6) | b4
          result.write_char(Int::unsafe_to_char(byte3))
        }
      }
      _ => return None
    }
    i = i + 4
  }
  Some(result.to_string())
}

///|
fn b64_index(table : String, c : Char) -> Int? {
  let mut i = 0
  while i < table.length() {
    if table.get_char(i).unwrap() == c {
      return Some(i)
    }
    i = i + 1
  }
  if c == '=' {
    return Some(0)
  }
  None
}