///|
/// Program-wide quality and coverage summary.
pub(all) struct ProgramSummary {
  feature_count : Int
  observed_feature_count : Int
  pass_count : Int
  fail_count : Int
  unmeasured_count : Int
  coverage : Double
  pass_rate : Double
  defect_rate : Double
  mean_cpk : Double
  minimum_cpk : Double
  high_risk_count : Int
  warning_count : Int
}

///|
fn program_feature_observed(
  feature : Feature,
  observations : Array[Observation],
) -> Bool {
  observations_for(feature~, observations~).length() > 0
}

///|
/// Aggregate all planned features and their observations.
pub fn summarize_program(
  features~ : Array[Feature],
  observations~ : Array[Observation],
  plan~ : MeasurementPlan,
) -> ProgramSummary {
  let mut observed_feature_count = 0
  let mut pass_count = 0
  let mut fail_count = 0
  let mut unmeasured_count = 0
  let mut high_risk_count = 0
  let mut cpk_total = 0.0
  let mut minimum_cpk = 0.0
  let mut first_cpk = true
  for feature in features {
    if program_feature_observed(feature, observations) {
      observed_feature_count += 1
    }
    let analysis = analyze_observations(
      feature~,
      observations~,
      uncertainty=0.0,
    )
    pass_count += analysis.summary.pass_count
    fail_count += analysis.summary.fail_count
    if analysis.count == 0 {
      unmeasured_count += 1
    }
    if analysis_risk(analysis) == RiskLevel::High {
      high_risk_count += 1
    }
    let cpk = analysis.summary.capability.cpk
    cpk_total += cpk
    if first_cpk {
      minimum_cpk = cpk
      first_cpk = false
    } else if cpk < minimum_cpk {
      minimum_cpk = cpk
    }
  }
  let feature_count = features.length()
  let pass_rate = if observed_feature_count == 0 {
    0.0
  } else {
    pass_count.to_double() / (pass_count + fail_count).to_double()
  }
  let defect_rate = if pass_count + fail_count == 0 {
    0.0
  } else {
    fail_count.to_double() / (pass_count + fail_count).to_double()
  }
  let mean_cpk = if feature_count == 0 {
    0.0
  } else {
    cpk_total / feature_count.to_double()
  }
  {
    feature_count,
    observed_feature_count,
    pass_count,
    fail_count,
    unmeasured_count,
    coverage: plan.summary.coverage,
    pass_rate,
    defect_rate,
    mean_cpk,
    minimum_cpk,
    high_risk_count,
    warning_count: plan.summary.warnings.length(),
  }
}

///|
/// Return whether a program is acceptable for offline release.
pub fn program_is_acceptable(summary : ProgramSummary) -> Bool {
  summary.feature_count > 0 &&
  summary.fail_count == 0 &&
  summary.unmeasured_count == 0 &&
  summary.coverage >= 1.0 &&
  summary.warning_count == 0
}

///|
/// Return the pass rate for a program summary.
pub fn program_pass_rate(summary : ProgramSummary) -> Double {
  summary.pass_rate
}

///|
/// Return the defect rate for a program summary.
pub fn program_defect_rate(summary : ProgramSummary) -> Double {
  summary.defect_rate
}

///|
/// Return the count of all classified observations.
pub fn program_observation_count(summary : ProgramSummary) -> Int {
  summary.pass_count + summary.fail_count
}

///|
/// Return whether a program has any unmeasured features.
pub fn program_has_unmeasured(summary : ProgramSummary) -> Bool {
  summary.unmeasured_count > 0
}

///|
/// Return the Cpk gap to the common 1.33 target.
pub fn program_cpk_margin(summary : ProgramSummary) -> Double {
  summary.minimum_cpk - 1.33
}

///|
/// Return the number of features with high operational risk.
pub fn program_high_risk_count(summary : ProgramSummary) -> Int {
  summary.high_risk_count
}

///|
/// Return a compact program status label.
pub fn program_status(summary : ProgramSummary) -> String {
  if program_is_acceptable(summary) {
    "acceptable"
  } else if summary.fail_count > 0 {
    "failed"
  } else if summary.unmeasured_count > 0 {
    "incomplete"
  } else {
    "review"
  }
}