/// Email/MIME serializer: converts Message AST back to RFC 5322 text.

///|
/// Serialize a Message to RFC 5322 format text.
pub fn message_to_string(msg : Message) -> String {
  let sb = StringBuilder::new()

  // Write headers
  for header in msg.headers {
    sb.write_string(header.name)
    sb.write_string(": ")
    sb.write_string(header.value)
    sb.write_string("\r\n")
  }

  // Empty line separating headers from body
  sb.write_string("\r\n")

  // Write body or MIME part
  match msg.mime_part {
    Some(part) => serialize_mime_part(part, sb)
    None =>
      match msg.body {
        Some(b) => {
          sb.write_string(b)
          // Ensure trailing newline
          if !b.has_suffix("\n") {
            sb.write_string("\r\n")
          }
        }
        None => ()
      }
  }

  sb.to_string()
}

///|
/// Serialize a MIME part to text.
fn serialize_mime_part(part : MimePart, sb : StringBuilder) -> Unit {
  match part.body {
    Body::Text(text) => {
      // Write part headers
      for h in part.headers {
        sb.write_string(h.name)
        sb.write_string(": ")
        sb.write_string(h.value)
        sb.write_string("\r\n")
      }
      sb.write_string("\r\n")
      sb.write_string(text)
      if !text.has_suffix("\n") {
        sb.write_string("\r\n")
      }
    }
    Body::Binary(bytes) => {
      for h in part.headers {
        sb.write_string(h.name)
        sb.write_string(": ")
        sb.write_string(h.value)
        sb.write_string("\r\n")
      }
      sb.write_string("\r\n")
      // Write base64-encoded binary content
      let encoded = base64_encode(bytes)
      sb.write_string(encoded)
      sb.write_string("\r\n")
    }
    Body::Multipart(parts) => {
      // Find the boundary from Content-Type header
      let boundary = find_boundary(part.headers)
      let bd = match boundary {
        Some(b) => b
        None => "----=_moonbit_boundary_"
      }

      match boundary {
        Some(_) => () // Content-Type already written in message headers
        None => {
          // Write Content-Type with boundary
          sb.write_string("Content-Type: multipart/mixed; boundary=\"")
          sb.write_string(bd)
          sb.write_string("\"\r\n")
          sb.write_string("\r\n")
        }
      }

      // Write each part
      for p in parts {
        sb.write_string("--")
        sb.write_string(bd)
        sb.write_string("\r\n")
        serialize_mime_part(p, sb)
      }

      // Closing boundary
      sb.write_string("--")
      sb.write_string(bd)
      sb.write_string("--\r\n")
    }
  }
}

///|
/// Find the boundary parameter from MIME headers.
fn find_boundary(headers : Array[Header]) -> String? {
  for h in headers {
    if h.name.to_lower() == "content-type" {
      let ct = parse_content_type(h.value)
      match ct {
        Some(c) => {
          for i = 0; i < c.params.length(); i = i + 1 {
            let (k, v) = c.params[i]
            if k == "boundary" {
              return Some(v)
            }
          }
          return None
        }
        None => ()
      }
    }
  }
  None
}

///|
/// Simple base64 encoder for Bytes.
fn base64_encode(data : Bytes) -> String {
  let table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let result = StringBuilder::new()
  let len = data.length()
  let mut i = 0
  while i < len {
    let b1 = data[i].to_int() & 0xFF
    let b2 = if i + 1 < len { data[i + 1].to_int() & 0xFF } else { 0 }
    let b3 = if i + 2 < len { data[i + 2].to_int() & 0xFF } else { 0 }

    let c1 = (b1 >> 2) & 0x3F
    let c2 = ((b1 << 4) | (b2 >> 4)) & 0x3F
    let c3 = if i + 1 < len { ((b2 << 2) | (b3 >> 6)) & 0x3F } else { 64 }
    let c4 = if i + 2 < len { b3 & 0x3F } else { 64 }

    result.write_char(table.get_char(c1).unwrap())
    result.write_char(table.get_char(c2).unwrap())
    result.write_char(
      if i + 1 < len {
        table.get_char(c3).unwrap()
      } else {
        '='
      },
    )
    result.write_char(
      if i + 2 < len {
        table.get_char(c4).unwrap()
      } else {
        '='
      },
    )

    i = i + 3
  }
  result.to_string()
}

///|
/// Serialize an EmailAddress to string.
pub fn address_to_string(addr : EmailAddress) -> String {
  let local_part = addr.local_part
  // Check if local part needs quoting
  let needs_quote = local_part.contains("@") ||
    local_part.contains(" ") ||
    local_part.contains("<") ||
    local_part.contains(">") ||
    local_part.contains(",") ||
    local_part.contains(";") ||
    local_part.contains(":") ||
    local_part.contains("\\") ||
    local_part.contains("\"") ||
    local_part.contains("(") ||
    local_part.contains(")") ||
    local_part.contains("[") ||
    local_part.contains("]")

  let local_str = if needs_quote {
    "\"" + local_part + "\""
  } else {
    local_part
  }

  local_str + "@" + addr.domain
}