///|
/// Quality decision produced by a configurable acceptance policy.
pub(all) enum QualityDecision {
  Pass
  Warning(reason~ : String)
  Fail(reason~ : String)
} derive(Eq, Debug)

///|
/// Thresholds used to turn measurements into operational decisions.
pub(all) struct AcceptancePolicy {
  warning_uncertainty_ratio : Double
  fail_uncertainty_ratio : Double
  minimum_cpk : Double
  minimum_samples : Int
  allow_unmeasured : Bool
}

///|
/// Result of evaluating one value against a policy.
pub(all) struct ValueQualityResult {
  feature_id : String
  value : Double
  normalized_deviation : Double
  uncertainty : Double
  decision : QualityDecision
}

///|
/// Result of evaluating a batch summary against a policy.
pub(all) struct BatchQualityResult {
  feature_id : String
  observed_count : Int
  defect_rate : Double
  cpk : Double
  decision : QualityDecision
}

///|
/// Return a conservative default policy for offline acceptance screening.
pub fn default_acceptance_policy() -> AcceptancePolicy {
  {
    warning_uncertainty_ratio: 0.5,
    fail_uncertainty_ratio: 1.0,
    minimum_cpk: 1.0,
    minimum_samples: 1,
    allow_unmeasured: false,
  }
}

///|
/// Create a policy with explicit thresholds.
pub fn acceptance_policy(
  warning_uncertainty_ratio~ : Double,
  fail_uncertainty_ratio~ : Double,
  minimum_cpk~ : Double,
  minimum_samples~ : Int,
  allow_unmeasured~ : Bool,
) -> AcceptancePolicy {
  {
    warning_uncertainty_ratio,
    fail_uncertainty_ratio,
    minimum_cpk,
    minimum_samples,
    allow_unmeasured,
  }
}

///|
fn decision_for_uncertainty(
  policy : AcceptancePolicy,
  ratio : Double,
) -> QualityDecision? {
  if ratio > policy.fail_uncertainty_ratio {
    Some(QualityDecision::Fail(reason="uncertainty exceeds policy limit"))
  } else if ratio > policy.warning_uncertainty_ratio {
    Some(QualityDecision::Warning(reason="uncertainty requires review"))
  } else {
    None
  }
}

///|
/// Evaluate one observed value and its stated uncertainty.
pub fn evaluate_value(
  policy~ : AcceptancePolicy,
  feature~ : Feature,
  value~ : Double,
  uncertainty~ : Double,
) -> ValueQualityResult {
  let normalized_deviation = relative_error(feature, value)
  let tolerance = tolerance_half_width(feature)
  let ratio = if tolerance == 0.0 {
    if uncertainty == 0.0 {
      0.0
    } else {
      policy.fail_uncertainty_ratio + 1.0
    }
  } else {
    uncertainty.abs() / tolerance
  }
  let decision = if value < lower_limit(feature) || value > upper_limit(feature) {
    QualityDecision::Fail(reason="value outside tolerance")
  } else {
    match decision_for_uncertainty(policy, ratio) {
      Some(decision) => decision
      None => QualityDecision::Pass
    }
  }
  { feature_id: feature.id, value, normalized_deviation, uncertainty, decision }
}

///|
/// Evaluate a complete batch summary.
pub fn evaluate_batch(
  policy : AcceptancePolicy,
  summary : BatchSummary,
) -> BatchQualityResult {
  let decision = if summary.fail_count > 0 {
    QualityDecision::Fail(reason="batch contains failed observations")
  } else if summary.count < policy.minimum_samples && !policy.allow_unmeasured {
    QualityDecision::Warning(reason="batch has too few observations")
  } else if summary.capability.cpk < policy.minimum_cpk {
    QualityDecision::Warning(
      reason="batch capability is below policy threshold",
    )
  } else {
    QualityDecision::Pass
  }
  {
    feature_id: summary.feature_id,
    observed_count: summary.count,
    defect_rate: summary.defect_rate,
    cpk: summary.capability.cpk,
    decision,
  }
}

///|
/// Return whether a policy result is acceptable for release.
pub fn policy_accepts(result : BatchQualityResult) -> Bool {
  result.decision is QualityDecision::Pass
}

///|
/// Return whether a value result is a hard failure.
pub fn value_is_failure(result : ValueQualityResult) -> Bool {
  result.decision is QualityDecision::Fail(_)
}

///|
/// Return whether a value result needs human review.
pub fn value_needs_review(result : ValueQualityResult) -> Bool {
  result.decision is QualityDecision::Warning(_)
}

///|
/// Render a stable decision label.
pub fn quality_decision_text(decision : QualityDecision) -> String {
  match decision {
    Pass => "pass"
    Warning(_) => "warning"
    Fail(_) => "fail"
  }
}

///|
/// Return a human-readable decision reason.
pub fn quality_decision_reason(decision : QualityDecision) -> String {
  match decision {
    Pass => "within policy"
    Warning(reason~) => reason
    Fail(reason~) => reason
  }
}

///|
/// Return whether a policy is structurally usable.
pub fn policy_is_valid(policy : AcceptancePolicy) -> Bool {
  policy.warning_uncertainty_ratio >= 0.0 &&
  policy.fail_uncertainty_ratio >= policy.warning_uncertainty_ratio &&
  policy.minimum_cpk >= 0.0 &&
  policy.minimum_samples >= 0
}

///|
/// Count failures in a set of value results.
pub fn value_failure_count(results : Array[ValueQualityResult]) -> Int {
  let mut count = 0
  for result in results {
    if value_is_failure(result) {
      count += 1
    }
  }
  count
}

///|
/// Count warnings in a set of value results.
pub fn value_warning_count(results : Array[ValueQualityResult]) -> Int {
  let mut count = 0
  for result in results {
    if value_needs_review(result) {
      count += 1
    }
  }
  count
}