///|
pub fn message_json(message : MailMessage) -> String {
  let mut out = "{\n"
  out = out + json_field("subject", message.subject, 2) + ",\n"
  out = out + json_field("date", message.date, 2) + ",\n"
  out = out + json_field("message_id", message.message_id, 2) + ",\n"
  out = out +
    indent(2) +
    "\"from\": " +
    address_array_json(message.from) +
    ",\n"
  out = out + indent(2) + "\"to\": " + address_array_json(message.to) + ",\n"
  out = out + indent(2) + "\"cc\": " + address_array_json(message.cc) + ",\n"
  out = out +
    indent(2) +
    "\"category\": \"" +
    category_name(classify_message(message)) +
    "\",\n"
  out = out +
    indent(2) +
    "\"parts\": " +
    part_count(message).to_string() +
    ",\n"
  out = out +
    indent(2) +
    "\"attachments\": " +
    attachments_json(message) +
    ",\n"
  out = out +
    indent(2) +
    "\"lint\": " +
    lint_json(lint_message(message)) +
    ",\n"
  out = out + indent(2) + "\"security\": " + security_json(message) + "\n"
  out = out + "}"
  out
}

///|
pub fn part_json(part : MailPart) -> String {
  let mut out = "{"
  out = out +
    "\"content_type\":\"" +
    escape_json(part.content_type.media_type + "/" + part.content_type.subtype) +
    "\","
  out = out + "\"charset\":\"" + escape_json(part.content_type.charset) + "\","
  out = out + "\"filename\":\"" + escape_json(part.filename) + "\","
  out = out + "\"disposition\":\"" + escape_json(part.disposition) + "\","
  out = out + "\"children\":" + part.children.length().to_string()
  out = out + "}"
  out
}

///|
fn address_array_json(items : Array[Address]) -> String {
  let mut out = "["
  let mut i = 0
  for item in items {
    if i > 0 {
      out = out + ", "
    }
    out = out +
      "{\"display\":\"" +
      escape_json(item.display) +
      "\",\"mailbox\":\"" +
      escape_json(item.mailbox) +
      "\"}"
    i = i + 1
  }
  out + "]"
}

///|
fn attachments_json(message : MailMessage) -> String {
  let mut out = "["
  let mut i = 0
  for item in attachment_infos(message) {
    if i > 0 {
      out = out + ", "
    }
    out = out + "{\"filename\":\"" + escape_json(item.filename) + "\","
    out = out + "\"content_type\":\"" + escape_json(item.content_type) + "\","
    out = out + "\"size\":" + item.size.to_string() + "}"
    i = i + 1
  }
  out + "]"
}

///|
fn lint_json(findings : Array[LintFinding]) -> String {
  let mut out = "["
  let mut i = 0
  for item in findings {
    if i > 0 {
      out = out + ", "
    }
    out = out + "{\"severity\":\"" + lint_severity_json(item.severity) + "\","
    out = out + "\"code\":\"" + escape_json(item.code) + "\","
    out = out + "\"message\":\"" + escape_json(item.message) + "\"}"
    i = i + 1
  }
  out + "]"
}

///|
fn security_json(message : MailMessage) -> String {
  let signals = security_signals(message)
  let mut out = "{"
  out = out + "\"score\":" + security_score(message).to_string() + ","
  out = out + "\"signals\":["
  let mut i = 0
  for item in signals {
    if i > 0 {
      out = out + ", "
    }
    out = out + "{\"code\":\"" + escape_json(item.code) + "\","
    out = out + "\"weight\":" + item.weight.to_string() + ","
    out = out + "\"detail\":\"" + escape_json(item.detail) + "\"}"
    i = i + 1
  }
  out = out + "]}"
  out
}

///|
fn lint_severity_json(severity : Severity) -> String {
  match severity {
    Info => "info"
    Warning => "warning"
    Error => "error"
  }
}

///|
fn json_field(name : String, value : String, spaces : Int) -> String {
  indent(spaces) +
  "\"" +
  escape_json(name) +
  "\": \"" +
  escape_json(value) +
  "\""
}

///|
fn indent(spaces : Int) -> String {
  let mut out = ""
  let mut i = 0
  while i < spaces {
    out = out + " "
    i = i + 1
  }
  out
}

///|
pub fn escape_json(input : String) -> String {
  let mut out = ""
  for ch in input {
    if ch == '"' {
      out = out + "\\\""
    } else if ch == '\\' {
      out = out + "\\\\"
    } else if ch == '\n' {
      out = out + "\\n"
    } else if ch == '\r' {
      out = out + "\\r"
    } else if ch == '\t' {
      out = out + "\\t"
    } else {
      out = out + ch.to_string()
    }
  }
  out
}

///|
pub fn corpus_json(messages : Array[MailMessage]) -> String {
  let mut out = "{\n"
  let stats = compute_stats(messages)
  out = out + indent(2) + "\"summary\": {\n"
  out = out + indent(4) + "\"messages\": " + stats.messages.to_string() + ",\n"
  out = out + indent(4) + "\"parts\": " + stats.parts.to_string() + ",\n"
  out = out +
    indent(4) +
    "\"attachments\": " +
    stats.attachments.to_string() +
    "\n"
  out = out + indent(2) + "},\n"
  out = out + indent(2) + "\"subjects\": ["
  let mut i = 0
  for message in messages {
    if i > 0 {
      out = out + ", "
    }
    out = out + "\"" + escape_json(message.subject) + "\""
    i = i + 1
  }
  out = out + "]\n"
  out = out + "}"
  out
}