///|
pub fn analyze_domain(
  domain : String,
  spf_text : String,
  dmarc_text : String,
) -> MailPolicyReport {
  let spf = parse_spf(spf_text)
  let dmarc = parse_dmarc(dmarc_text)
  let findings = Array::new(
    capacity=spf.findings.length() + dmarc.findings.length() + 1,
  )
  if domain.trim().is_empty() {
    findings.push(
      fail(
        "domain.name", "Domain name is missing", "Reports should identify the domain being assessed.",
        "", "Pass a stable domain label to analyze_domain.",
      ),
    )
  } else {
    findings.push(pass("domain.name", "Domain name is present", domain))
  }
  for item in spf.findings {
    findings.push(item)
  }
  for item in dmarc.findings {
    findings.push(item)
  }
  let score = score_findings(findings)
  {
    domain,
    spf,
    dmarc,
    score,
    verdict: verdict_findings(findings, score),
    findings,
  }
}

///|
pub fn report_markdown(
  domain : String,
  spf_text : String,
  dmarc_text : String,
) -> String {
  analyze_domain(domain, spf_text, dmarc_text).to_markdown()
}

///|
pub fn report_json(
  domain : String,
  spf_text : String,
  dmarc_text : String,
) -> String {
  analyze_domain(domain, spf_text, dmarc_text).to_json()
}

///|
pub fn MailPolicyReport::has_failures(self : MailPolicyReport) -> Bool {
  for finding in self.findings {
    if finding.severity == Fail {
      return true
    }
  }
  false
}

///|
pub fn MailPolicyReport::warning_count(self : MailPolicyReport) -> Int {
  let mut count = 0
  for finding in self.findings {
    if finding.severity == Warn {
      count += 1
    }
  }
  count
}

///|
pub fn MailPolicyReport::to_markdown(self : MailPolicyReport) -> String {
  let out = StringBuilder()
  out.write_string("# MailShield Report\n\n")
  out.write_string("- Domain: " + self.domain + "\n")
  out.write_string("- Verdict: " + self.verdict + "\n")
  out.write_string("- Score: " + self.score.to_string() + "/100\n")
  out.write_string(
    "- SPF DNS lookups: " + self.spf.dns_lookup_count.to_string() + "\n",
  )
  out.write_string("- DMARC policy: " + self.dmarc.policy + "\n\n")
  out.write_string("## Findings\n\n")
  for item in self.findings {
    out.write_string("- [" + severity_text(item.severity) + "] ")
    out.write_string(item.id + ": " + item.title)
    if item.evidence != "" {
      out.write_string(" -- " + item.evidence)
    }
    if item.severity != Pass && item.repair != "" {
      out.write_string(" | repair: " + item.repair)
    }
    out.write_string("\n")
  }
  out.to_string()
}

///|
pub fn MailPolicyReport::to_json(self : MailPolicyReport) -> String {
  let out = StringBuilder()
  out.write_string("{")
  out.write_string("\"domain\":\"" + json_escape(self.domain) + "\",")
  out.write_string("\"verdict\":\"" + json_escape(self.verdict) + "\",")
  out.write_string("\"score\":" + self.score.to_string() + ",")
  out.write_string(
    "\"spf_dns_lookup_count\":" + self.spf.dns_lookup_count.to_string() + ",",
  )
  out.write_string(
    "\"dmarc_policy\":\"" + json_escape(self.dmarc.policy) + "\",",
  )
  out.write_string("\"findings\":[")
  for i in 0.. 0 {
      out.write_string(",")
    }
    out.write_string("{")
    out.write_string("\"id\":\"" + json_escape(item.id) + "\",")
    out.write_string("\"severity\":\"" + severity_text(item.severity) + "\",")
    out.write_string("\"title\":\"" + json_escape(item.title) + "\"")
    out.write_string("}")
  }
  out.write_string("]}")
  out.to_string()
}

///|
fn score_findings(findings : Array[PolicyFinding]) -> Int {
  let mut score = 100
  for item in findings {
    score -= item.penalty
  }
  clamp_score(score)
}

///|
fn verdict_findings(findings : Array[PolicyFinding], score : Int) -> String {
  let mut has_fail = false
  let mut has_warn = false
  for item in findings {
    if item.severity == Fail {
      has_fail = true
    } else if item.severity == Warn {
      has_warn = true
    }
  }
  if has_fail {
    "risky"
  } else if has_warn || score < 90 {
    "monitor"
  } else {
    "protected"
  }
}