///|
/// Return a stable strategy label.
pub fn strategy_name(strategy : Strategy) -> String {
  match strategy {
    SinglePoint => "single"
    RepeatedPoint(_) => "repeated"
    Scan(_) => "scan"
  }
}

///|
/// Return the sample count represented by a strategy.
pub fn strategy_sample_count(strategy : Strategy) -> Int {
  match strategy {
    SinglePoint => 1
    RepeatedPoint(count~) => count
    Scan(count~) => count
  }
}

///|
/// Return whether a strategy uses more than one sample.
pub fn strategy_is_repeated(strategy : Strategy) -> Bool {
  strategy_sample_count(strategy) > 1
}

///|
/// Return whether a strategy is a scan.
pub fn strategy_is_scan(strategy : Strategy) -> Bool {
  match strategy {
    Scan(_) => true
    _ => false
  }
}

///|
/// Return a simple time cost for a strategy.
pub fn strategy_cost(strategy : Strategy) -> Double {
  match strategy {
    SinglePoint => 1.0
    RepeatedPoint(count~) => count.to_double() * 1.5
    Scan(count~) => count.to_double() * 2.0
  }
}

///|
/// Return a conservative risk for a strategy.
pub fn strategy_risk(strategy : Strategy) -> RiskLevel {
  match strategy {
    SinglePoint => RiskLevel::High
    RepeatedPoint(_) => RiskLevel::Medium
    Scan(_) => RiskLevel::Low
  }
}

///|
/// Return a stable verdict code.
pub fn verdict_code(verdict : Verdict) -> String {
  match verdict {
    Pass => "PASS"
    Fail(_) => "FAIL"
    Unmeasured => "UNMEASURED"
  }
}

///|
/// Return whether a verdict is a hard failure.
pub fn verdict_is_failure(verdict : Verdict) -> Bool {
  verdict is Verdict::Fail(_)
}

///|
/// Return whether a verdict passed.
pub fn verdict_is_pass(verdict : Verdict) -> Bool {
  verdict is Verdict::Pass
}

///|
/// Return a stable verdict explanation.
pub fn verdict_reason(verdict : Verdict) -> String {
  match verdict {
    Pass => "within tolerance"
    Fail(reason~) => reason
    Unmeasured => "no observation"
  }
}

///|
/// Return whether a value is inside inclusive limits.
pub fn within_tolerance(feature : Feature, value : Double) -> Bool {
  value >= lower_limit(feature) && value <= upper_limit(feature)
}

///|
/// Return whether tolerance limits are ordered and non-zero in width.
pub fn tolerance_is_valid(feature : Feature) -> Bool {
  feature.lower <= feature.upper && tolerance_half_width(feature) > 0.0
}

///|
/// Return which side of a feature's limits contains a value.
pub fn limit_position(feature : Feature, value : Double) -> String {
  if value < lower_limit(feature) {
    "below"
  } else if value > upper_limit(feature) {
    "above"
  } else if value == feature.nominal {
    "center"
  } else if value < feature.nominal {
    "lower-half"
  } else {
    "upper-half"
  }
}

///|
/// Return the normalized uncertainty ratio.
pub fn uncertainty_ratio(feature : Feature, uncertainty : Double) -> Double {
  let tolerance = tolerance_half_width(feature)
  if tolerance == 0.0 {
    1.0
  } else {
    uncertainty.abs() / tolerance
  }
}

///|
/// Recommend sample count using existing conservative planning rules.
pub fn recommended_samples(feature : Feature, accuracy : Double) -> Int {
  repeat_count(feature, Device::{
    id: "temporary",
    accuracy,
    reach: feature.nominal.abs() + 1.0,
    supported_kinds: [feature.kind],
  })
}

///|
/// Return a human-readable confidence band.
pub fn confidence_label(feature : Feature, uncertainty : Double) -> String {
  let ratio = uncertainty_ratio(feature, uncertainty)
  if ratio <= 0.25 {
    "high"
  } else if ratio <= 0.5 {
    "medium"
  } else {
    "low"
  }
}

///|
/// Resolve a strategy from a feature kind and requested sample count.
pub fn resolve_strategy(kind : FeatureKind, count : Int) -> Strategy {
  if kind_requires_scan(kind) {
    Scan(count=if count < 3 { 3 } else { count })
  } else if count <= 1 {
    SinglePoint
  } else {
    RepeatedPoint(count~)
  }
}

///|
/// Return whether a strategy can support a feature's tolerance.
pub fn strategy_can_resolve(
  feature : Feature,
  strategy : Strategy,
  device : Device,
) -> Bool {
  capability_ok(feature, estimate_uncertainty(strategy, device))
}

///|
/// Return the better of two verdicts using fail > unmeasured > pass priority.
pub fn worse_verdict(left : Verdict, right : Verdict) -> Verdict {
  if verdict_is_failure(left) {
    left
  } else if verdict_is_failure(right) {
    right
  } else if left is Verdict::Unmeasured {
    left
  } else if right is Verdict::Unmeasured {
    right
  } else {
    Pass
  }
}

///|
/// Reduce a collection of verdicts to its worst outcome.
pub fn worst_verdict(verdicts : Array[Verdict]) -> Verdict {
  let mut result = Verdict::Pass
  for verdict in verdicts {
    result = worse_verdict(result, verdict)
  }
  result
}