///|
pub fn default_matrix_policy() -> MatrixPolicy {
  MatrixPolicy::{
    required_targets: [Native, Js, Wasm],
    max_timeout_ms: 120000,
    require_evidence: true,
    require_output_for_success: true,
    allow_failure_scenarios: true,
  }
}

///|
pub fn strict_matrix_policy() -> MatrixPolicy {
  MatrixPolicy::{
    required_targets: [Native, Js, Wasm, WasmGc],
    max_timeout_ms: 60000,
    require_evidence: true,
    require_output_for_success: true,
    allow_failure_scenarios: false,
  }
}

///|
pub fn policy_issues(
  manifest : ScenarioManifest,
  policy : MatrixPolicy,
) -> Array[ScenarioIssue] {
  let issues : Array[ScenarioIssue] = []
  let mut spec_index = 0
  while spec_index < manifest.specs.length() {
    let spec = manifest.specs[spec_index]
    if spec.timeout_ms > policy.max_timeout_ms {
      issues.push(
        scenario_issue(
          "policy-timeout-too-large",
          Error,
          spec.id,
          "",
          "scenario timeout exceeds the selected policy",
          "Use a bounded timeout or choose a policy with an explicit larger limit.",
        ),
      )
    }
    if !policy.allow_failure_scenarios && spec.expected == Failure {
      issues.push(
        scenario_issue(
          "policy-failure-scenario-disabled",
          Error,
          spec.id,
          "",
          "selected policy does not allow expected-failure scenarios",
          "Use expected=success in the strict smoke matrix.",
        ),
      )
    }
    if policy.require_output_for_success &&
      spec.expected == Success &&
      spec.expected_output.length() == 0 {
      issues.push(
        scenario_issue(
          "policy-output-missing",
          Error,
          spec.id,
          "",
          "success scenario has no stable output fragment",
          "Declare output=... so a passing run has observable evidence.",
        ),
      )
    }
    spec_index += 1
  }
  let mut target_index = 0
  while target_index < policy.required_targets.length() {
    let target = policy.required_targets[target_index]
    if !manifest_has_required_target(manifest.specs, target) {
      issues.push(
        scenario_issue(
          "policy-target-not-covered",
          Error,
          "",
          target.name(),
          "no required scenario covers a policy target",
          "Add at least one required scenario for every selected target.",
        ),
      )
    }
    target_index += 1
  }
  if manifest.specs.length() == 0 {
    issues.push(
      scenario_issue(
        "policy-no-scenarios",
        Error,
        "",
        "",
        "policy cannot be evaluated against an empty manifest",
        "Declare at least one scenario.",
      ),
    )
  }
  issues
}

///|
pub fn apply_policy(
  matrix : ScenarioMatrix,
  policy : MatrixPolicy,
) -> ScenarioMatrix {
  let result = matrix
  let policy_result = policy_issues(
    ScenarioManifest::{
      name: matrix.name,
      version: matrix.version,
      specs: matrix.specs,
      issues: [],
    },
    policy,
  )
  let mut index = 0
  while index < policy_result.length() {
    result.issues.push(policy_result[index])
    index += 1
  }
  if policy.require_evidence {
    index = 0
    while index < result.observations.length() {
      let observation = result.observations[index]
      if observation.evidence_digest.length() == 0 {
        result.issues.push(
          scenario_issue(
            "policy-evidence-missing",
            Error,
            observation.id,
            observation.target.name(),
            "policy requires a digest for every observation",
            "Record scenario_evidence_digest(stdout, stderr) with the observation.",
          ),
        )
      }
      index += 1
    }
  }
  result
}

///|
fn manifest_has_required_target(
  specs : Array[ScenarioSpec],
  target : ScenarioTarget,
) -> Bool {
  let mut index = 0
  while index < specs.length() {
    if specs[index].required && specs[index].has_target(target) {
      return true
    }
    index += 1
  }
  false
}

///|
pub fn policy_summary(policy : MatrixPolicy) -> String {
  "targets=" +
  join_target_names(policy.required_targets) +
  ", max-timeout-ms=" +
  policy.max_timeout_ms.to_string() +
  ", require-evidence=" +
  bool_text(policy.require_evidence) +
  ", require-output=" +
  bool_text(policy.require_output_for_success) +
  ", allow-failure=" +
  bool_text(policy.allow_failure_scenarios)
}

///|
pub fn policy_markdown(policy : MatrixPolicy) -> String {
  "| Rule | Value |\n| --- | --- |\n" +
  "| Required targets | `" +
  join_target_names(policy.required_targets) +
  "` |\n" +
  "| Maximum timeout | " +
  policy.max_timeout_ms.to_string() +
  " ms |\n" +
  "| Evidence required | " +
  bool_text(policy.require_evidence) +
  " |\n" +
  "| Output required | " +
  bool_text(policy.require_output_for_success) +
  " |\n" +
  "| Failure scenarios allowed | " +
  bool_text(policy.allow_failure_scenarios) +
  " |\n"
}