///|
fn minimum_double(left : Double, right : Double) -> Double {
  if left < right {
    left
  } else {
    right
  }
}

///|
/// Calculate Cp/Cpk using sample spread plus stated uncertainty.
pub fn capability_indices(
  feature~ : Feature,
  values~ : Array[Double],
  uncertainty~ : Double,
) -> CapabilitySummary {
  let spread = sample_stddev(values) + uncertainty.abs()
  let width = upper_limit(feature) - lower_limit(feature)
  if values.length() == 0 || width <= 0.0 {
    {
      cp: 0.0,
      cpk: 0.0,
      adequate: false,
      sample_count: values.length(),
      uncertainty,
    }
  } else if spread == 0.0 {
    {
      cp: 999.0,
      cpk: 999.0,
      adequate: true,
      sample_count: values.length(),
      uncertainty,
    }
  } else {
    let cp = width / (6.0 * spread)
    let average = mean(values)
    let cpu = (upper_limit(feature) - average) / (3.0 * spread)
    let cpl = (average - lower_limit(feature)) / (3.0 * spread)
    let cpk = minimum_double(cpu, cpl)
    {
      cp,
      cpk,
      adequate: cp >= 1.33 && cpk >= 1.33,
      sample_count: values.length(),
      uncertainty,
    }
  }
}

///|
/// Return whether a capability summary meets the common 1.33 threshold.
pub fn capability_is_acceptable(summary : CapabilitySummary) -> Bool {
  summary.adequate && summary.cp >= 1.33 && summary.cpk >= 1.33
}

///|
/// Return a conservative margin to the nearest capability threshold.
pub fn capability_margin(summary : CapabilitySummary) -> Double {
  let cp_margin = summary.cp - 1.33
  let cpk_margin = summary.cpk - 1.33
  minimum_double(cp_margin, cpk_margin)
}