///|
pub struct MessageDraft {
  from : Address
  to : Array[Address]
  cc : Array[Address]
  subject : String
  date : String
  message_id : String
  text_body : String
  html_body : String
  attachments : Array[DraftAttachment]
} derive(Eq, Debug)

///|
pub struct DraftAttachment {
  filename : String
  content_type : String
  body : String
  encoding : TransferEncoding
} derive(Eq, Debug)

///|
pub fn draft(
  from : Address,
  to : Array[Address],
  subject : String,
) -> MessageDraft {
  {
    from,
    to,
    cc: [],
    subject,
    date: "",
    message_id: "",
    text_body: "",
    html_body: "",
    attachments: [],
  }
}

///|
pub fn with_cc(input : MessageDraft, cc : Array[Address]) -> MessageDraft {
  {
    from: input.from,
    to: input.to,
    cc,
    subject: input.subject,
    date: input.date,
    message_id: input.message_id,
    text_body: input.text_body,
    html_body: input.html_body,
    attachments: input.attachments,
  }
}

///|
pub fn with_text(input : MessageDraft, text : String) -> MessageDraft {
  {
    from: input.from,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    date: input.date,
    message_id: input.message_id,
    text_body: text,
    html_body: input.html_body,
    attachments: input.attachments,
  }
}

///|
pub fn with_html(input : MessageDraft, html : String) -> MessageDraft {
  {
    from: input.from,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    date: input.date,
    message_id: input.message_id,
    text_body: input.text_body,
    html_body: html,
    attachments: input.attachments,
  }
}

///|
pub fn with_date(input : MessageDraft, date : String) -> MessageDraft {
  {
    from: input.from,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    date,
    message_id: input.message_id,
    text_body: input.text_body,
    html_body: input.html_body,
    attachments: input.attachments,
  }
}

///|
pub fn with_message_id(
  input : MessageDraft,
  message_id : String,
) -> MessageDraft {
  {
    from: input.from,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    date: input.date,
    message_id,
    text_body: input.text_body,
    html_body: input.html_body,
    attachments: input.attachments,
  }
}

///|
pub fn draft_attachment(
  filename : String,
  content_type : String,
  body : String,
) -> DraftAttachment {
  { filename, content_type, body, encoding: SevenBit }
}

///|
pub fn draft_attachment_encoded(
  filename : String,
  content_type : String,
  body : String,
  encoding : TransferEncoding,
) -> DraftAttachment {
  { filename, content_type, body, encoding }
}

///|
pub fn with_attachment(
  input : MessageDraft,
  attachment : DraftAttachment,
) -> MessageDraft {
  let out : Array[DraftAttachment] = []
  for item in input.attachments {
    out.push(item)
  }
  out.push(attachment)
  {
    from: input.from,
    to: input.to,
    cc: input.cc,
    subject: input.subject,
    date: input.date,
    message_id: input.message_id,
    text_body: input.text_body,
    html_body: input.html_body,
    attachments: out,
  }
}

///|
pub fn render_message(input : MessageDraft) -> String {
  let headers = render_common_headers(input)
  if input.attachments.length() > 0 {
    render_mixed(headers, input)
  } else if input.html_body != "" && input.text_body != "" {
    render_alternative(headers, input)
  } else if input.html_body != "" {
    headers +
    "Content-Type: text/html; charset=utf-8\r\n\r\n" +
    input.html_body +
    "\r\n"
  } else {
    headers +
    "Content-Type: text/plain; charset=utf-8\r\n\r\n" +
    input.text_body +
    "\r\n"
  }
}

///|
fn render_common_headers(input : MessageDraft) -> String {
  let mut out = ""
  out = out + "From: " + format_address(input.from) + "\r\n"
  out = out + "To: " + join_address_header(input.to) + "\r\n"
  if input.cc.length() > 0 {
    out = out + "Cc: " + join_address_header(input.cc) + "\r\n"
  }
  out = out + "Subject: " + input.subject + "\r\n"
  if input.date != "" {
    out = out + "Date: " + input.date + "\r\n"
  }
  if input.message_id != "" {
    out = out + "Message-ID: " + input.message_id + "\r\n"
  }
  out
}

///|
fn render_alternative(headers : String, input : MessageDraft) -> String {
  let boundary = "mailkit-alt-boundary"
  headers +
  "Content-Type: multipart/alternative; boundary=\"" +
  boundary +
  "\"\r\n\r\n" +
  "--" +
  boundary +
  "\r\n" +
  "Content-Type: text/plain; charset=utf-8\r\n\r\n" +
  input.text_body +
  "\r\n" +
  "--" +
  boundary +
  "\r\n" +
  "Content-Type: text/html; charset=utf-8\r\n\r\n" +
  input.html_body +
  "\r\n" +
  "--" +
  boundary +
  "--\r\n"
}

///|
fn render_mixed(headers : String, input : MessageDraft) -> String {
  let boundary = "mailkit-mixed-boundary"
  let mut out = headers +
    "Content-Type: multipart/mixed; boundary=\"" +
    boundary +
    "\"\r\n\r\n"
  out = out + "--" + boundary + "\r\n"
  if input.html_body != "" && input.text_body != "" {
    out = out +
      "Content-Type: multipart/alternative; boundary=\"mailkit-inner-alt\"\r\n\r\n"
    out = out +
      "--mailkit-inner-alt\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n"
    out = out + input.text_body + "\r\n"
    out = out +
      "--mailkit-inner-alt\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"
    out = out + input.html_body + "\r\n--mailkit-inner-alt--\r\n"
  } else if input.html_body != "" {
    out = out +
      "Content-Type: text/html; charset=utf-8\r\n\r\n" +
      input.html_body +
      "\r\n"
  } else {
    out = out +
      "Content-Type: text/plain; charset=utf-8\r\n\r\n" +
      input.text_body +
      "\r\n"
  }
  for item in input.attachments {
    out = out + "--" + boundary + "\r\n"
    out = out +
      "Content-Type: " +
      item.content_type +
      "; name=\"" +
      item.filename +
      "\"\r\n"
    out = out +
      "Content-Disposition: attachment; filename=\"" +
      item.filename +
      "\"\r\n"
    out = out +
      "Content-Transfer-Encoding: " +
      render_encoding(item.encoding) +
      "\r\n\r\n"
    out = out + item.body + "\r\n"
  }
  out + "--" + boundary + "--\r\n"
}

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

///|
fn join_address_header(items : Array[Address]) -> String {
  let mut out = ""
  let mut i = 0
  for item in items {
    if i > 0 {
      out = out + ", "
    }
    out = out + format_address(item)
    i = i + 1
  }
  out
}