///|
pub(all) enum Severity {
  Info
  Warning
  Error
} derive(Eq, Debug)

///|
pub struct LintFinding {
  severity : Severity
  code : String
  message : String
} derive(Eq, Debug)

///|
pub fn lint_message(message : MailMessage) -> Array[LintFinding] {
  let out : Array[LintFinding] = []
  if message.from.length() == 0 {
    out.push(
      finding(
        Error,
        "header.from.missing",
        "From header is required for most mailbox workflows",
      ),
    )
  }
  if message.to.length() == 0 && message.cc.length() == 0 {
    out.push(
      finding(
        Warning,
        "recipient.missing",
        "message has neither To nor Cc recipients",
      ),
    )
  }
  if message.subject == "" {
    out.push(finding(Warning, "subject.empty", "message subject is empty"))
  }
  if plain_text(message) == "" && html_text(message) == "" {
    out.push(
      finding(
        Warning,
        "body.empty",
        "message has no readable text or html body",
      ),
    )
  }
  lint_part(message.root, out)
  out
}

///|
fn lint_part(part : MailPart, out : Array[LintFinding]) -> Unit {
  if part.content_type.media_type == "multipart" &&
    part.content_type.boundary == "" {
    out.push(
      finding(
        Error,
        "mime.boundary.missing",
        "multipart part is missing boundary parameter",
      ),
    )
  }
  if is_attachment(part) && part.filename == "" {
    out.push(
      finding(
        Warning,
        "attachment.filename.missing",
        "attachment has no filename",
      ),
    )
  }
  if is_attachment(part) && is_suspicious_filename(part.filename) {
    out.push(
      finding(
        Warning,
        "attachment.filename.suspicious",
        "attachment filename has a risky extension",
      ),
    )
  }
  for child in part.children {
    lint_part(child, out)
  }
}

///|
pub fn finding(
  severity : Severity,
  code : String,
  message : String,
) -> LintFinding {
  { severity, code, message }
}

///|
pub fn has_lint_code(findings : Array[LintFinding], code : String) -> Bool {
  for item in findings {
    if item.code == code {
      return true
    }
  }
  false
}

///|
pub fn warning_count(findings : Array[LintFinding]) -> Int {
  let mut count = 0
  for item in findings {
    if item.severity == Warning {
      count = count + 1
    }
  }
  count
}

///|
pub fn error_count(findings : Array[LintFinding]) -> Int {
  let mut count = 0
  for item in findings {
    if item.severity == Error {
      count = count + 1
    }
  }
  count
}

///|
pub fn is_suspicious_filename(filename : String) -> Bool {
  let lower = lower_ascii(filename)
  ends_with(lower, ".exe") ||
  ends_with(lower, ".bat") ||
  ends_with(lower, ".cmd") ||
  ends_with(lower, ".scr") ||
  ends_with(lower, ".js") ||
  ends_with(lower, ".vbs")
}

///|
pub fn ends_with(input : String, suffix : String) -> Bool {
  if suffix.length() > input.length() {
    false
  } else {
    slice(input, input.length() - suffix.length(), input.length()) == suffix
  }
}