///|
/// Control limits for a stable sequence of observed values.
pub(all) struct ControlLimits {
  center : Double
  lower : Double
  upper : Double
  sensitivity : Double
}

///|
/// Observation analysis combining specification and process views.
pub(all) struct ObservationAnalysis {
  feature_id : String
  count : Int
  summary : BatchSummary
  within_control_limit_count : Int
  outlier_count : Int
  mean_residual : Double
  trend : String
  repeat_consistent : Bool
}

///|
/// Select observations belonging to a feature.
pub fn observations_for(
  feature~ : Feature,
  observations~ : Array[Observation],
) -> Array[Observation] {
  let result = []
  for observation in observations {
    if observation.feature_id == feature.id {
      result.push(observation)
    }
  }
  result
}

///|
/// Return values belonging to a feature in observation order.
pub fn observation_values_for(
  feature~ : Feature,
  observations~ : Array[Observation],
) -> Array[Double] {
  let result = []
  for observation in observations_for(feature~, observations~) {
    result.push(observation.value)
  }
  result
}

///|
/// Return signed residuals from nominal values.
pub fn residuals(feature~ : Feature, values~ : Array[Double]) -> Array[Double] {
  let result = []
  for value in values {
    result.push(signed_deviation(feature, value))
  }
  result
}

///|
/// Build symmetric control limits around the observed mean.
pub fn control_limits(
  values~ : Array[Double],
  sensitivity~ : Double,
) -> ControlLimits {
  let center = mean(values)
  let spread = population_stddev(values)
  let width = spread * sensitivity.abs()
  { center, lower: center - width, upper: center + width, sensitivity }
}

///|
/// Return flags for values outside control limits.
pub fn outlier_flags(
  values~ : Array[Double],
  limits~ : ControlLimits,
) -> Array[Bool] {
  let result = []
  for value in values {
    result.push(value < limits.lower || value > limits.upper)
  }
  result
}

///|
/// Count values inside control limits.
pub fn within_control_limit_count(
  values~ : Array[Double],
  limits~ : ControlLimits,
) -> Int {
  let mut result = 0
  for value in values {
    if value >= limits.lower && value <= limits.upper {
      result += 1
    }
  }
  result
}

///|
/// Check repeatability against the full bilateral tolerance width.
pub fn repeated_measurement_consistent(
  feature~ : Feature,
  values : Array[Double],
) -> Bool {
  values.length() <= 1 || range(values) <= (feature.upper - feature.lower).abs()
}

///|
/// Analyze feature observations with process and specification metrics.
pub fn analyze_observations(
  feature~ : Feature,
  observations~ : Array[Observation],
  uncertainty~ : Double,
) -> ObservationAnalysis {
  let selected = observations_for(feature~, observations~)
  let values = observation_values_for(feature~, observations~)
  let summary = summarize_batch(feature~, observations=selected)
  let limits = control_limits(values~, sensitivity=3.0)
  let flags = outlier_flags(values~, limits~)
  let mut outlier_count = 0
  for flag in flags {
    if flag {
      outlier_count += 1
    }
  }
  let mut residual_total = 0.0
  for residual in residuals(feature~, values~) {
    residual_total += residual
  }
  let mean_residual = if values.length() == 0 {
    0.0
  } else {
    residual_total / values.length().to_double()
  }
  let updated_capability = capability_indices(feature~, values~, uncertainty~)
  let adjusted_summary = BatchSummary::{
    feature_id: summary.feature_id,
    count: summary.count,
    pass_count: summary.pass_count,
    fail_count: summary.fail_count,
    unmeasured_count: summary.unmeasured_count,
    mean: summary.mean,
    minimum: summary.minimum,
    maximum: summary.maximum,
    range: summary.range,
    standard_deviation: summary.standard_deviation,
    defect_rate: summary.defect_rate,
    capability: updated_capability,
  }
  {
    feature_id: feature.id,
    count: values.length(),
    summary: adjusted_summary,
    within_control_limit_count: within_control_limit_count(values~, limits~),
    outlier_count,
    mean_residual,
    trend: batch_trend(values),
    repeat_consistent: repeated_measurement_consistent(feature~, values),
  }
}

///|
/// Return a compact risk label for an analysis.
pub fn analysis_risk(analysis : ObservationAnalysis) -> RiskLevel {
  if analysis.outlier_count > 0 || analysis.summary.fail_count > 0 {
    RiskLevel::High
  } else if !analysis.repeat_consistent || !analysis.summary.capability.adequate {
    RiskLevel::Medium
  } else {
    RiskLevel::Low
  }
}