///|
/// One independently reviewable release criterion.
pub(all) struct ReleaseCheck {
  name : String
  passed : Bool
  detail : String
}

///|
/// Evidence-backed readiness state for a measured part.
pub(all) struct ReleaseGate {
  part_id : String
  checks : Array[ReleaseCheck]
  ready : Bool
  risk : String
}

///|
fn release_check(name : String, passed : Bool, detail : String) -> ReleaseCheck {
  { name, passed, detail }
}

///|
fn feature_has_observation(
  feature : Feature,
  observations : Array[Observation],
) -> Bool {
  for observation in observations {
    if observation.feature_id == feature.id {
      return true
    }
  }
  false
}

///|
fn release_observation_coverage(
  features : Array[Feature],
  observations : Array[Observation],
) -> Double {
  if features.length() == 0 {
    1.0
  } else {
    let mut measured = 0
    for feature in features {
      if feature_has_observation(feature, observations) {
        measured += 1
      }
    }
    measured.to_double() / features.length().to_double()
  }
}

///|
/// Evaluate the practical acceptance criteria for one plan and evidence set.
pub fn evaluate_release_gate(
  plan : MeasurementPlan,
  features : Array[Feature],
  observations : Array[Observation],
) -> ReleaseGate {
  let trace = build_audit_trace_with_features(plan~, features~, observations~)
  let counts = audit_verdict_counts(trace)
  let observation_coverage = release_observation_coverage(
    features, observations,
  )
  let checks = [
    release_check(
      "plan coverage",
      plan.summary.coverage >= 1.0,
      "coverage=" + plan.summary.coverage.to_string(),
    ),
    release_check(
      "device capability",
      plan.summary.capability,
      if plan.summary.capability {
        "all devices are capable"
      } else {
        "capability warning present"
      },
    ),
    release_check(
      "observation coverage",
      observation_coverage >= 1.0,
      "coverage=" + observation_coverage.to_string(),
    ),
    release_check(
      "verdict completeness",
      counts.2 == 0,
      "unmeasured=" + counts.2.to_string(),
    ),
    release_check(
      "passing evidence",
      counts.0 > 0 && counts.1 == 0,
      "pass=" + counts.0.to_string() + " fail=" + counts.1.to_string(),
    ),
    release_check(
      "warning review",
      plan.summary.warnings.length() == 0,
      "warnings=" + plan.summary.warnings.length().to_string(),
    ),
  ]
  let mut ready = true
  for check in checks {
    if !check.passed {
      ready = false
    }
  }
  let failures = failed_release_check_count_from_checks(checks)
  {
    part_id: plan.part_id,
    checks,
    ready,
    risk: if failures == 0 {
      "low"
    } else if failures < 3 {
      "medium"
    } else {
      "high"
    },
  }
}

///|
fn failed_release_check_count_from_checks(checks : Array[ReleaseCheck]) -> Int {
  let mut result = 0
  for check in checks {
    if !check.passed {
      result += 1
    }
  }
  result
}

///|
/// Count failed release criteria.
pub fn failed_release_check_count(gate : ReleaseGate) -> Int {
  failed_release_check_count_from_checks(gate.checks)
}

///|
/// Return a stable gate status for CLI and CI integration.
pub fn release_gate_status(gate : ReleaseGate) -> String {
  if gate.ready {
    "ready"
  } else if gate.risk == "high" {
    "blocked"
  } else {
    "review"
  }
}

///|
/// Render every criterion and its evidence detail.
pub fn release_gate_text(gate : ReleaseGate) -> String {
  let mut result = "part=" +
    gate.part_id +
    " status=" +
    release_gate_status(gate) +
    "\n"
  for check in gate.checks {
    let state = if check.passed { "pass" } else { "fail" }
    result = result + state + " " + check.name + " " + check.detail + "\n"
  }
  result
}

///|
/// Return the names of criteria that need reviewer attention.
pub fn failed_release_check_names(gate : ReleaseGate) -> Array[String] {
  let result = []
  for check in gate.checks {
    if !check.passed {
      result.push(check.name)
    }
  }
  result
}

///|
/// Return the number of passing criteria.
pub fn passed_release_check_count(gate : ReleaseGate) -> Int {
  gate.checks.length() - failed_release_check_count(gate)
}

///|
/// Return whether every criterion has a non-empty evidence detail.
pub fn release_evidence_is_explained(gate : ReleaseGate) -> Bool {
  let mut result = true
  for check in gate.checks {
    if check.detail.trim() == "" {
      result = false
    }
  }
  result
}