///|
/// Named policy presets let the same manifest be reviewed for different stages
/// of a project: local development, a release candidate, or a strict audit.
pub(all) enum PolicyProfile {
  Development
  Release
  Strict
} derive(Eq, Debug)

///|
pub(all) struct ValidationPolicy {
  name : String
  minimum_capabilities : Int
  minimum_requirements : Int
  minimum_evidence : Int
  require_out_of_scope : Bool
  require_verified_test : Bool
  require_verified_example : Bool
  require_verified_ci : Bool
  require_verified_doc : Bool
  require_verified_release : Bool
  reject_pending_evidence : Bool
  require_should_evidence : Bool
  require_unique_targets : Bool
  require_lowercase_ids : Bool
  require_capability_scope : Bool
  require_capability_constraints : Bool
  minimum_project_length : Int
  minimum_purpose_length : Int
} derive(Eq, Debug)

///|
pub fn PolicyProfile::label(self : PolicyProfile) -> String {
  match self {
    Development => "development"
    Release => "release"
    Strict => "strict"
  }
}

///|
pub fn development_policy() -> ValidationPolicy {
  {
    name: "development",
    minimum_capabilities: 1,
    minimum_requirements: 1,
    minimum_evidence: 1,
    require_out_of_scope: false,
    require_verified_test: true,
    require_verified_example: false,
    require_verified_ci: false,
    require_verified_doc: false,
    require_verified_release: false,
    reject_pending_evidence: false,
    require_should_evidence: false,
    require_unique_targets: false,
    require_lowercase_ids: false,
    require_capability_scope: false,
    require_capability_constraints: false,
    minimum_project_length: 1,
    minimum_purpose_length: 1,
  }
}

///|
pub fn release_policy() -> ValidationPolicy {
  {
    name: "release",
    minimum_capabilities: 2,
    minimum_requirements: 3,
    minimum_evidence: 4,
    require_out_of_scope: true,
    require_verified_test: true,
    require_verified_example: true,
    require_verified_ci: true,
    require_verified_doc: true,
    require_verified_release: false,
    reject_pending_evidence: false,
    require_should_evidence: true,
    require_unique_targets: true,
    require_lowercase_ids: true,
    require_capability_scope: true,
    require_capability_constraints: true,
    minimum_project_length: 3,
    minimum_purpose_length: 24,
  }
}

///|
pub fn strict_policy() -> ValidationPolicy {
  {
    name: "strict",
    minimum_capabilities: 3,
    minimum_requirements: 4,
    minimum_evidence: 6,
    require_out_of_scope: true,
    require_verified_test: true,
    require_verified_example: true,
    require_verified_ci: true,
    require_verified_doc: true,
    require_verified_release: true,
    reject_pending_evidence: true,
    require_should_evidence: true,
    require_unique_targets: true,
    require_lowercase_ids: true,
    require_capability_scope: true,
    require_capability_constraints: true,
    minimum_project_length: 3,
    minimum_purpose_length: 40,
  }
}

///|
pub fn policy_for(profile : PolicyProfile) -> ValidationPolicy {
  match profile {
    Development => development_policy()
    Release => release_policy()
    Strict => strict_policy()
  }
}

///|
/// A complete offline fixture for release-policy examples, integration tests,
/// and documentation. It includes distinct artifacts for tests, CI, docs, and
/// a package release, so it also satisfies the strict profile.
pub fn strict_ready_manifest() -> String {
  [
    "project: CapsuleTrace Release", "purpose: Review release evidence for a deterministic MoonBit traceability library.",
    "scope: offline manifest validation", "scope: stable review report generation",
    "out-of-scope: network and filesystem access at runtime", "capability: parse | Parse line manifests | text | boundary spec | offline",
    "capability: policy | Apply release policy | boundary spec | report | deterministic",
    "capability: matrix | Build evidence matrix | boundary spec | matrix | stable order",
    "requirement: parser.accepts | must | Parser accepts supported records. | parse",
    "requirement: policy.release | must | Policy reports release blockers. | policy",
    "requirement: matrix.coverage | should | Matrix shows requirement coverage. | matrix",
    "requirement: report.json | may | Reports support structured export. | policy,matrix",
    "evidence: test.parser | test | parser_wbtest.mbt | verified | parser.accepts | Parser tests pass.",
    "evidence: test.policy | test | policy_wbtest.mbt | verified | policy.release | Policy tests pass.",
    "evidence: test.matrix | test | matrix_wbtest.mbt | verified | matrix.coverage | Matrix tests pass.",
    "evidence: example.audit | example | cmd/audit | verified | policy.release,matrix.coverage | Runnable audit example.",
    "evidence: ci.check | ci | .github/workflows/ci.yml | verified | parser.accepts,policy.release | CI verifies the package.",
    "evidence: doc.readme | doc | README.md | verified | matrix.coverage | README documents the API.",
    "evidence: release.package | release | mooncakes.io | verified | report.json | Package release is recorded.",
    "evidence: design.policy | design | docs/design.md | verified | policy.release | Policy design is documented.",
  ].join("\n")
}

///|
/// Apply the standard structural checks together with a caller-supplied policy.
/// The policy never mutates the source specification, which makes it safe to run
/// multiple policy profiles against one parsed manifest in CI.
pub fn validate_spec_with_policy(
  spec : BoundarySpec,
  policy : ValidationPolicy,
) -> TraceReport {
  let baseline = validate_spec(spec)
  let findings = baseline.findings.copy()
  audit_policy_counts(spec, policy, findings)
  audit_policy_summary(spec, policy, findings)
  audit_policy_capabilities(spec, policy, findings)
  audit_policy_requirements(spec, policy, findings)
  audit_policy_evidence(spec, policy, findings)
  let score = score_findings(findings)
  {
    score,
    grade: grade_score(score),
    ready: report_is_ready(findings),
    findings,
    spec,
  }
}

///|
pub fn validate_manifest_with_policy(
  raw : StringView,
  policy : ValidationPolicy,
) -> TraceReport {
  validate_spec_with_policy(parse_manifest(raw), policy)
}

///|
pub fn TraceReport::policy_summary(
  self : TraceReport,
  policy : ValidationPolicy,
) -> String {
  let lines : Array[String] = []
  lines.push("Policy: " + policy.name)
  lines.push("Ready: " + bool_label(self.ready))
  lines.push("Score: " + self.score.to_string() + " (" + self.grade + ")")
  lines.push("Critical: " + self.count_by_severity(Critical).to_string())
  lines.push("High: " + self.count_by_severity(High).to_string())
  lines.push("Medium: " + self.count_by_severity(Medium).to_string())
  lines.push("Low: " + self.count_by_severity(Low).to_string())
  lines.join("\n")
}

///|
pub fn TraceReport::to_policy_markdown(
  self : TraceReport,
  policy : ValidationPolicy,
) -> String {
  let lines : Array[String] = []
  lines.push("# CapsuleTrace Policy Report")
  lines.push("")
  lines.push("- Policy: " + policy.name)
  lines.push("- Project: " + printable(self.spec.project))
  lines.push("- Ready: " + bool_label(self.ready))
  lines.push("- Score: " + self.score.to_string() + " (" + self.grade + ")")
  lines.push("")
  lines.push("## Findings")
  lines.push("")
  lines.push("| Severity | Rule | Subject | Remediation |")
  lines.push("| --- | --- | --- | --- |")
  if self.findings.is_empty() {
    lines.push("| info | clean | all | No policy violations. |")
  } else {
    for finding in self.findings {
      lines.push(
        "| " +
        finding.severity.label() +
        " | " +
        markdown_cell(finding.id) +
        " | " +
        markdown_cell(finding.subject) +
        " | " +
        markdown_cell(finding.remediation) +
        " |",
      )
    }
  }
  lines.join("\n")
}

///|
fn audit_policy_counts(
  spec : BoundarySpec,
  policy : ValidationPolicy,
  findings : Array[TraceFinding],
) -> Unit {
  audit_minimum(
    policy,
    "capability-count",
    "capability",
    spec.capabilities.length(),
    policy.minimum_capabilities,
    findings,
  )
  audit_minimum(
    policy,
    "requirement-count",
    "requirement",
    spec.requirements.length(),
    policy.minimum_requirements,
    findings,
  )
  audit_minimum(
    policy,
    "evidence-count",
    "evidence",
    spec.evidence.length(),
    policy.minimum_evidence,
    findings,
  )
}

///|
fn audit_policy_summary(
  spec : BoundarySpec,
  policy : ValidationPolicy,
  findings : Array[TraceFinding],
) -> Unit {
  if spec.project.trim().length() < policy.minimum_project_length {
    policy_finding(
      findings,
      policy,
      "project-length",
      High,
      "project",
      "project name is shorter than the policy minimum",
      "Use a stable project name with at least " +
      policy.minimum_project_length.to_string() +
      " characters.",
    )
  }
  if spec.purpose.trim().length() < policy.minimum_purpose_length {
    policy_finding(
      findings,
      policy,
      "purpose-length",
      Medium,
      "purpose",
      "project purpose is too short to be reviewable",
      "Describe the user problem and intended boundary in at least " +
      policy.minimum_purpose_length.to_string() +
      " characters.",
    )
  }
  if policy.require_out_of_scope && spec.out_of_scope.is_empty() {
    policy_finding(
      findings,
      policy,
      "out-of-scope",
      Medium,
      "out-of-scope",
      "policy requires at least one explicit non-goal",
      "Add an out-of-scope record for functionality the package will not provide.",
    )
  }
}

///|
fn audit_policy_capabilities(
  spec : BoundarySpec,
  policy : ValidationPolicy,
  findings : Array[TraceFinding],
) -> Unit {
  for capability in spec.capabilities {
    if policy.require_lowercase_ids && !is_lowercase_identifier(capability.id) {
      policy_finding(
        findings,
        policy,
        "capability-id-style",
        Low,
        capability.id,
        "capability id must use lowercase identifier style",
        "Use lowercase ids such as parse-manifest or report.export.",
      )
    }
    if policy.require_capability_scope &&
      spec.requirements_for_capability(capability.id).is_empty() {
      policy_finding(
        findings,
        policy,
        "capability-requirement",
        Medium,
        capability.id,
        "capability is not linked to an observable requirement",
        "Add a requirement that references this capability.",
      )
    }
    if policy.require_capability_constraints &&
      capability.constraints.is_empty() {
      policy_finding(
        findings,
        policy,
        "capability-constraints",
        Medium,
        capability.id,
        "capability has no documented constraints",
        "Document determinism, offline limits, performance goals, or other boundaries.",
      )
    }
  }
}

///|
fn audit_policy_requirements(
  spec : BoundarySpec,
  policy : ValidationPolicy,
  findings : Array[TraceFinding],
) -> Unit {
  for requirement in spec.requirements {
    if policy.require_lowercase_ids && !is_lowercase_identifier(requirement.id) {
      policy_finding(
        findings,
        policy,
        "requirement-id-style",
        Low,
        requirement.id,
        "requirement id must use lowercase identifier style",
        "Use lowercase ids such as parser.accepts-records or export.json.",
      )
    }
    if policy.require_should_evidence &&
      requirement.level == Should &&
      spec.evidence_for_requirement(requirement.id).is_empty() {
      policy_finding(
        findings,
        policy,
        "should-evidence",
        Medium,
        requirement.id,
        "should requirement has no evidence under this policy",
        "Attach test, example, documentation, CI, or design evidence.",
      )
    }
  }
}

///|
fn audit_policy_evidence(
  spec : BoundarySpec,
  policy : ValidationPolicy,
  findings : Array[TraceFinding],
) -> Unit {
  let seen_targets : Map[String, String] = Map([])
  let mut has_verified_test = false
  let mut has_verified_example = false
  let mut has_verified_ci = false
  let mut has_verified_doc = false
  let mut has_verified_release = false
  for item in spec.evidence {
    if policy.require_lowercase_ids && !is_lowercase_identifier(item.id) {
      policy_finding(
        findings,
        policy,
        "evidence-id-style",
        Low,
        item.id,
        "evidence id must use lowercase identifier style",
        "Use lowercase ids such as test.parser or ci.check.",
      )
    }
    if item.status == Verified {
      match item.kind {
        Test => has_verified_test = true
        Example => has_verified_example = true
        CI => has_verified_ci = true
        Doc => has_verified_doc = true
        Release => has_verified_release = true
        Design => ()
      }
    }
    if policy.reject_pending_evidence && item.status == Pending {
      policy_finding(
        findings,
        policy,
        "pending-evidence",
        Medium,
        item.id,
        "pending evidence is not allowed by this policy",
        "Verify the artifact or remove it from the release manifest.",
      )
    }
    if policy.require_unique_targets && !item.target.trim().is_empty() {
      let target_key = item.target.trim().to_lower().to_owned()
      match seen_targets.get(target_key) {
        Some(first_id) =>
          policy_finding(
            findings,
            policy,
            "duplicate-evidence-target",
            Low,
            item.id,
            "evidence target is already claimed by " + first_id,
            "Use one evidence record per target or document why the records differ.",
          )
        None => seen_targets.set(target_key, item.id)
      }
    }
  }
  audit_required_evidence(
    findings,
    policy,
    "verified-test",
    "test",
    has_verified_test,
    policy.require_verified_test,
  )
  audit_required_evidence(
    findings,
    policy,
    "verified-example",
    "example",
    has_verified_example,
    policy.require_verified_example,
  )
  audit_required_evidence(
    findings,
    policy,
    "verified-ci",
    "ci",
    has_verified_ci,
    policy.require_verified_ci,
  )
  audit_required_evidence(
    findings,
    policy,
    "verified-doc",
    "documentation",
    has_verified_doc,
    policy.require_verified_doc,
  )
  audit_required_evidence(
    findings,
    policy,
    "verified-release",
    "release",
    has_verified_release,
    policy.require_verified_release,
  )
}

///|
fn audit_minimum(
  policy : ValidationPolicy,
  rule : String,
  subject : String,
  actual : Int,
  expected : Int,
  findings : Array[TraceFinding],
) -> Unit {
  if actual < expected {
    policy_finding(
      findings,
      policy,
      rule,
      Medium,
      subject,
      "policy requires at least " +
      expected.to_string() +
      ", found " +
      actual.to_string(),
      "Add independent, reviewable " + subject + " records before release.",
    )
  }
}

///|
fn audit_required_evidence(
  findings : Array[TraceFinding],
  policy : ValidationPolicy,
  rule : String,
  subject : String,
  actual : Bool,
  required : Bool,
) -> Unit {
  if required && !actual {
    policy_finding(
      findings,
      policy,
      rule,
      High,
      subject,
      "policy requires verified " + subject + " evidence",
      "Add a verified " + subject + " evidence record tied to a requirement.",
    )
  }
}

///|
fn policy_finding(
  findings : Array[TraceFinding],
  policy : ValidationPolicy,
  rule : String,
  severity : Severity,
  subject : String,
  message : String,
  remediation : String,
) -> Unit {
  findings.push({
    id: "policy." + policy.name + "." + rule,
    severity,
    subject,
    message,
    remediation,
  })
}

///|
fn is_lowercase_identifier(value : String) -> Bool {
  let trimmed = value.trim().to_owned()
  trimmed == trimmed.to_lower() && looks_like_id(trimmed)
}