///|
/// Transfer-encoding choice for a body part (RFC 2045 section 6.1).
pub(all) enum ContentTransferEncoding {
  /// 7bit ASCII only.
  SevenBit
  /// 8bit (raw) bytes.
  EightBit
  /// Base64.
  Base64
  /// Quoted-Printable.
  QuotedPrintable
  /// Choose automatically during render.
  Auto
} derive(Eq, Debug)

///|
pub fn ContentTransferEncoding::to_string(
  self : ContentTransferEncoding,
) -> String {
  match self {
    SevenBit => "7bit"
    EightBit => "8bit"
    Base64 => "base64"
    QuotedPrintable => "quoted-printable"
    Auto => "auto"
  }
}

///|
pub impl Show for ContentTransferEncoding with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
/// True when every byte of `text` is ASCII (<= 127).
pub fn is_pure_ascii(s : String) -> Bool {
  for b in string_to_bytes(s) {
    if b.to_int() > 127 {
      return false
    }
  }
  true
}

///|
/// Check a 7bit body: every byte must be <= 127 (MM_ENCOD_003).
pub fn check_seven_bit(s : String) -> Unit raise MailFailure {
  guard is_pure_ascii(s) else {
    raise MailFailure::of(
      Encode,
      MM_ENCOD_003,
      "7bit body contains non-ASCII bytes",
    )
  }
}

///|
/// Encode text content with an explicit transfer encoding.
/// Returns the encoded body (which already uses CRLF line endings) together
/// with the effective encoding that was actually applied (relevant for `Auto`).
pub fn encode_body(
  content : String,
  encoding : ContentTransferEncoding,
) -> (String, ContentTransferEncoding) raise MailFailure {
  match encoding {
    SevenBit => {
      check_seven_bit(content)
      (normalize_crlf(content), SevenBit)
    }
    EightBit => (normalize_crlf(content), EightBit)
    Base64 => (base64_mime_encode(string_to_bytes(content)), Base64)
    QuotedPrintable => (qp_encode(content), QuotedPrintable)
    Auto => auto_encode_text(content)
  }
}

///|
/// Pick a deterministic encoding for text: 7bit when the content is pure ASCII
/// and every line fits in 76 characters, otherwise Quoted-Printable.
fn auto_encode_text(content : String) -> (String, ContentTransferEncoding) {
  if is_pure_ascii(content) && max_line_bytes(content) <= 76 {
    (normalize_crlf(content), SevenBit)
  } else {
    (qp_encode(content), QuotedPrintable)
  }
}

///|
/// Maximum byte length of a single line in `content`.
fn max_line_bytes(content : String) -> Int {
  let lines = to_lines(content)
  let mut m = 0
  for line in lines {
    let l = string_to_bytes(line).length()
    if l > m {
      m = l
    }
  }
  m
}