///|
pub(all) struct ReleaseEvidence {
  applicant_name : String
  github_owner : String
  repository_name : String
  mooncakes_package : String
  version : String
  ci_status : String
  issue_records : Array[String]
  test_records : Array[String]
  release_notes : String
  design_notes : String
  license : String
} derive(Eq, Debug)

///|
pub fn ReleaseEvidence::ReleaseEvidence(
  applicant_name? : String = "",
  github_owner? : String = "",
  repository_name? : String = "",
  mooncakes_package? : String = "",
  version? : String = "",
  ci_status? : String = "",
  issue_records? : Array[String] = [],
  test_records? : Array[String] = [],
  release_notes? : String = "",
  design_notes? : String = "",
  license? : String = "",
) -> ReleaseEvidence {
  {
    applicant_name,
    github_owner,
    repository_name,
    mooncakes_package,
    version,
    ci_status,
    issue_records,
    test_records,
    release_notes,
    design_notes,
    license,
  }
}

///|
pub(all) struct EvidenceReport {
  score : Int
  verdict : String
  findings : Array[RolloutFinding]
} derive(Eq, Debug)

///|
pub fn release_evidence_review(evidence : ReleaseEvidence) -> EvidenceReport {
  let findings = Array::new(capacity=12)
  push_evidence_identity(findings, evidence)
  push_evidence_ci(findings, evidence)
  push_evidence_records(findings, evidence)
  push_evidence_docs(findings, evidence)
  let score = evidence_score(findings)
  { score, verdict: evidence_verdict(findings, score), findings }
}

///|
fn push_evidence_identity(
  findings : Array[RolloutFinding],
  evidence : ReleaseEvidence,
) -> Unit {
  if evidence.applicant_name == "张雨晴" {
    findings.push(
      finding_pass(
        "evidence.applicant",
        IdentityGate,
        "Applicant identity is fixed",
        "张雨晴",
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.applicant",
        IdentityGate,
        "Applicant identity is not aligned",
        evidence.applicant_name,
        "Use the applicant name from the official form.",
        penalty=18,
      ),
    )
  }
  if evidence.github_owner == "EJJ-ai-nb" &&
    evidence.repository_name == "canaryring" {
    findings.push(
      finding_pass(
        "evidence.github",
        IdentityGate,
        "GitHub owner and repository are aligned",
        "EJJ-ai-nb/canaryring",
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.github",
        IdentityGate,
        "GitHub owner or repository is not aligned",
        evidence.github_owner + "/" + evidence.repository_name,
        "Use EJJ-ai-nb/canaryring for the new non-overlapping project.",
        penalty=18,
      ),
    )
  }
  if evidence.mooncakes_package == "EJJ-ai-nb/canaryring" {
    findings.push(
      finding_pass(
        "evidence.mooncakes",
        IdentityGate,
        "Mooncakes package is aligned",
        evidence.mooncakes_package,
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.mooncakes",
        IdentityGate,
        "Mooncakes package is not aligned",
        evidence.mooncakes_package,
        "Publish as EJJ-ai-nb/canaryring.",
        penalty=18,
      ),
    )
  }
}

///|
fn push_evidence_ci(
  findings : Array[RolloutFinding],
  evidence : ReleaseEvidence,
) -> Unit {
  let status = lower(evidence.ci_status)
  if status == "success" || status == "passed" {
    findings.push(
      finding_pass(
        "evidence.ci",
        MetricGate,
        "CI status is successful",
        evidence.ci_status,
      ),
    )
  } else if status == "" {
    findings.push(
      finding_warn(
        "evidence.ci",
        MetricGate,
        "CI status is not recorded",
        "unknown",
        "Record the latest main-branch GitHub Actions result.",
        penalty=8,
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.ci",
        MetricGate,
        "CI status is not successful",
        evidence.ci_status,
        "Fix CI before submitting or accepting the project.",
        penalty=20,
      ),
    )
  }
}

///|
fn push_evidence_records(
  findings : Array[RolloutFinding],
  evidence : ReleaseEvidence,
) -> Unit {
  if evidence.issue_records.length() >= 1 {
    findings.push(
      finding_pass(
        "evidence.issues",
        PlanShape,
        "Issue or work records are retained",
        evidence.issue_records.length().to_string() + " records",
      ),
    )
  } else {
    findings.push(
      finding_warn(
        "evidence.issues",
        PlanShape,
        "Issue or work records are missing",
        "0",
        "Keep at least one work item record for the new project.",
        penalty=6,
      ),
    )
  }
  if evidence.test_records.length() >= 1 {
    findings.push(
      finding_pass(
        "evidence.tests",
        PlanShape,
        "Test records are retained",
        evidence.test_records.length().to_string() + " records",
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.tests",
        PlanShape,
        "Test records are missing",
        "0",
        "Record local and CI test results.",
        penalty=15,
      ),
    )
  }
}

///|
fn push_evidence_docs(
  findings : Array[RolloutFinding],
  evidence : ReleaseEvidence,
) -> Unit {
  if has_text(evidence.release_notes) {
    findings.push(
      finding_pass(
        "evidence.release-notes",
        PlanShape,
        "Release notes are retained",
        "present",
      ),
    )
  } else {
    findings.push(
      finding_warn(
        "evidence.release-notes",
        PlanShape,
        "Release notes are missing",
        "empty",
        "Add CHANGELOG.md or release notes.",
        penalty=5,
      ),
    )
  }
  if has_text(evidence.design_notes) {
    findings.push(
      finding_pass(
        "evidence.design",
        PlanShape,
        "Design notes are retained",
        "present",
      ),
    )
  } else {
    findings.push(
      finding_warn(
        "evidence.design",
        PlanShape,
        "Design notes are missing",
        "empty",
        "Add a design boundary note.",
        penalty=5,
      ),
    )
  }
  if evidence.license == "MIT" || evidence.license == "Apache-2.0" {
    findings.push(
      finding_pass(
        "evidence.license",
        PlanShape,
        "Open-source license is clear",
        evidence.license,
      ),
    )
  } else {
    findings.push(
      finding_fail(
        "evidence.license",
        PlanShape,
        "Open-source license is unclear",
        evidence.license,
        "Use an OSI-recognized license and keep LICENSE at repository root.",
        penalty=20,
      ),
    )
  }
}

///|
fn evidence_score(findings : Array[RolloutFinding]) -> Int {
  let mut score = 100
  for finding in findings {
    score -= finding.penalty
  }
  clamp(score, 0, 100)
}

///|
fn evidence_verdict(findings : Array[RolloutFinding], score : Int) -> String {
  let mut fails = 0
  for finding in findings {
    if finding.severity == Fail {
      fails += 1
    }
  }
  if fails > 0 {
    "blocked"
  } else if score >= 90 {
    "ready"
  } else {
    "needs-notes"
  }
}

///|
pub fn EvidenceReport::to_markdown(self : EvidenceReport) -> String {
  let out = StringBuilder()
  out.write_string("# CanaryRing Release Evidence\n\n")
  out.write_string("- Verdict: " + self.verdict + "\n")
  out.write_string("- Score: " + self.score.to_string() + "/100\n\n")
  for finding in self.findings {
    out.write_string("- [" + severity_text(finding.severity) + "] ")
    out.write_string(finding.id + ": " + finding.title)
    if finding.evidence != "" {
      out.write_string(" -- " + finding.evidence)
    }
    out.write_string("\n")
  }
  out.to_string()
}

///|
pub fn example_release_evidence() -> ReleaseEvidence {
  ReleaseEvidence(
    applicant_name="张雨晴",
    github_owner="EJJ-ai-nb",
    repository_name="canaryring",
    mooncakes_package="EJJ-ai-nb/canaryring",
    version="0.1.0",
    ci_status="success",
    issue_records=["scope: deterministic canary rollout planner"],
    test_records=["moon test --deny-warn"],
    release_notes="0.1.0 initial canary rollout simulator",
    design_notes="Pure MoonBit, no network or shell access in core library",
    license="MIT",
  )
}