///|
pub fn build_acceptance_evidence(input : AuditInput) -> EvidenceMatrix {
  evidence_from_report(input, audit_project(input))
}

///|
pub fn evidence_from_report(
  input : AuditInput,
  report : AuditReport,
) -> EvidenceMatrix {
  let manifest = parse_manifest(input.moon_mod)
  let readme = analyze_readme(input.readme)
  let workflow = analyze_workflow(input.ci)
  let license = analyze_license(input.license_text)
  let namespace_data = namespace_from_manifest(manifest)
  let items : Array[EvidenceItem] = []
  push_evidence(
    items,
    "moonbit-project",
    "Valid MoonBit project metadata",
    !report.has_errors() || manifest.has("name"),
    "moon.mod",
  )
  push_evidence(
    items,
    "package-name",
    "Mooncakes owner/package namespace",
    namespace_data.valid,
    "moon.mod name",
  )
  push_evidence(
    items,
    "version",
    "Semantic release version",
    parse_semver(report.version).valid,
    "moon.mod version",
  )
  push_evidence(
    items,
    "repository-url",
    "GitHub repository URL",
    report_has_code(report, "repository-url", Pass),
    "moon.mod repository",
  )
  push_evidence(
    items,
    "readme-present",
    "README exists",
    report_has_code(report, "readme-present", Pass),
    "README.md",
  )
  push_evidence(
    items,
    "readme-complete",
    "README covers install, usage, example and validation",
    readme.coverage_score() >= 80,
    "README.md",
  )
  push_evidence(
    items,
    "readme-commands",
    "README includes check/build/test/publish commands",
    readme.command_count >= 3,
    "README.md",
  )
  push_evidence(
    items,
    "example",
    "Runnable example is documented",
    report_has_code(report, "readme-example", Pass) || workflow.has_run,
    "README or CI",
  )
  push_evidence(
    items,
    "ci-present",
    "GitHub Actions workflow exists",
    report_has_code(report, "ci-present", Pass),
    ".github/workflows",
  )
  push_evidence(
    items,
    "ci-check",
    "CI runs moon check",
    workflow.has_check,
    ".github/workflows",
  )
  push_evidence(
    items,
    "ci-build",
    "CI runs moon build",
    workflow.has_build,
    ".github/workflows",
  )
  push_evidence(
    items,
    "ci-test",
    "CI runs moon test",
    workflow.has_test,
    ".github/workflows",
  )
  push_evidence(
    items,
    "ci-run",
    "CI runs an example",
    workflow.has_run,
    ".github/workflows",
  )
  push_evidence(
    items,
    "license",
    "OSI approved license is present",
    license.is_publishable(),
    "LICENSE",
  )
  push_evidence(
    items,
    "public-repo",
    "Repository is public",
    input.repository_public,
    "GitHub settings",
  )
  push_evidence(
    items,
    "commit-history",
    "Development history is traceable",
    input.commit_count >= 5,
    "git log",
  )
  push_evidence(
    items,
    "changelog",
    "Change log is present",
    trim_ascii(input.changelog).length() > 0,
    "CHANGELOG.md",
  )
  push_evidence(
    items,
    "mooncakes",
    "Package is published to mooncakes.io",
    input.package_published,
    "mooncakes.io",
  )
  evidence_matrix(
    items,
    evidence_present_count(items),
    evidence_missing_count(items),
  )
}

///|
pub fn EvidenceItem::status_text(self : EvidenceItem) -> String {
  if self.present {
    "present"
  } else {
    "missing"
  }
}

///|
pub fn EvidenceItem::markdown_row(self : EvidenceItem) -> String {
  "| `" +
  self.code +
  "` | " +
  self.label +
  " | " +
  self.status_text() +
  " | " +
  self.source +
  " |\n"
}

///|
pub fn EvidenceMatrix::coverage_percent(self : EvidenceMatrix) -> Int {
  let total = self.items.length()
  if total == 0 {
    0
  } else {
    self.present_count * 100 / total
  }
}

///|
pub fn EvidenceMatrix::is_complete(self : EvidenceMatrix) -> Bool {
  self.missing_count == 0 && self.items.length() > 0
}

///|
pub fn EvidenceMatrix::acceptance_label(self : EvidenceMatrix) -> String {
  if self.is_complete() {
    "complete"
  } else if self.coverage_percent() >= 85 {
    "nearly-complete"
  } else if self.coverage_percent() >= 65 {
    "partial"
  } else {
    "insufficient"
  }
}

///|
pub fn EvidenceMatrix::summary(self : EvidenceMatrix) -> String {
  "evidence=\{self.present_count}/\{self.items.length()}, missing=\{self.missing_count}, label=\{self.acceptance_label()}"
}

///|
pub fn EvidenceMatrix::missing_items(
  self : EvidenceMatrix,
) -> Array[EvidenceItem] {
  let missing : Array[EvidenceItem] = []
  let mut index = 0
  while index < self.items.length() {
    if !self.items[index].present {
      missing.push(self.items[index])
    }
    index += 1
  }
  missing
}

///|
pub fn EvidenceMatrix::present_items(
  self : EvidenceMatrix,
) -> Array[EvidenceItem] {
  let present : Array[EvidenceItem] = []
  let mut index = 0
  while index < self.items.length() {
    if self.items[index].present {
      present.push(self.items[index])
    }
    index += 1
  }
  present
}

///|
pub fn EvidenceMatrix::missing_codes(self : EvidenceMatrix) -> Array[String] {
  let codes : Array[String] = []
  let missing = self.missing_items()
  let mut index = 0
  while index < missing.length() {
    codes.push(missing[index].code)
    index += 1
  }
  codes
}

///|
pub fn EvidenceMatrix::has(self : EvidenceMatrix, code : String) -> Bool {
  let mut index = 0
  while index < self.items.length() {
    if self.items[index].code == code && self.items[index].present {
      return true
    }
    index += 1
  }
  false
}

///|
pub fn EvidenceMatrix::missing_label_text(self : EvidenceMatrix) -> String {
  let missing = self.missing_items()
  if missing.length() == 0 {
    return "none"
  }
  let mut out = ""
  let mut index = 0
  while index < missing.length() {
    if index > 0 {
      out = out + ", "
    }
    out = out + missing[index].label
    index += 1
  }
  out
}

///|
pub fn EvidenceMatrix::to_markdown(self : EvidenceMatrix) -> String {
  let mut out = "## Acceptance Evidence\n\n"
  out = out + "- Coverage: " + self.coverage_percent().to_string() + "%\n"
  out = out + "- Present: " + self.present_count.to_string() + "\n"
  out = out + "- Missing: " + self.missing_count.to_string() + "\n"
  out = out + "- Label: " + self.acceptance_label() + "\n\n"
  out = out +
    "| Code | Evidence | Status | Source |\n| --- | --- | --- | --- |\n"
  let mut index = 0
  while index < self.items.length() {
    out = out + self.items[index].markdown_row()
    index += 1
  }
  out
}

///|
pub fn EvidenceMatrix::to_json(self : EvidenceMatrix) -> String {
  let mut out = "{"
  out = out + "\"coverage\":" + self.coverage_percent().to_string() + ","
  out = out + "\"present\":" + self.present_count.to_string() + ","
  out = out + "\"missing\":" + self.missing_count.to_string() + ","
  out = out + "\"items\":["
  let mut index = 0
  while index < self.items.length() {
    if index > 0 {
      out = out + ","
    }
    let item = self.items[index]
    out = out + "{"
    out = out + "\"code\":\"" + json_escape(item.code) + "\","
    out = out + "\"label\":\"" + json_escape(item.label) + "\","
    out = out + "\"present\":" + bool_json(item.present) + ","
    out = out + "\"source\":\"" + json_escape(item.source) + "\""
    out = out + "}"
    index += 1
  }
  out + "]}"
}

///|
pub fn compare_evidence(
  before : EvidenceMatrix,
  after : EvidenceMatrix,
) -> AuditDiff {
  let fixed : Array[String] = []
  let new_problems : Array[String] = []
  let mut index = 0
  while index < after.items.length() {
    let item = after.items[index]
    let was_present = before.has(item.code)
    if item.present && !was_present {
      fixed.push(item.code)
    }
    if !item.present && before.has(item.code) {
      new_problems.push(item.code)
    }
    index += 1
  }
  audit_diff(
    before.coverage_percent(),
    after.coverage_percent(),
    after.coverage_percent() - before.coverage_percent(),
    fixed,
    new_problems,
  )
}

///|
pub fn evidence_markdown_summary(input : AuditInput) -> String {
  build_acceptance_evidence(input).to_markdown()
}

///|
fn push_evidence(
  items : Array[EvidenceItem],
  code : String,
  label : String,
  present : Bool,
  source : String,
) -> Unit {
  items.push(evidence_item(code, label, present, source))
}

///|
fn evidence_present_count(items : Array[EvidenceItem]) -> Int {
  let mut count = 0
  let mut index = 0
  while index < items.length() {
    if items[index].present {
      count += 1
    }
    index += 1
  }
  count
}

///|
fn evidence_missing_count(items : Array[EvidenceItem]) -> Int {
  items.length() - evidence_present_count(items)
}

///|
fn report_has_code(
  report : AuditReport,
  code : String,
  severity : Severity,
) -> Bool {
  let mut index = 0
  while index < report.checks.length() {
    let item = report.checks[index]
    if item.code == code && item.severity == severity {
      return true
    }
    index += 1
  }
  false
}

///|
fn bool_json(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}