///|
/// Explains finite-sample resolution before selecting a model or confidence level.
pub(all) struct CalibrationResolution {
  count : Int32
  smallest_p_value : Double
  rank : Int32
  finite_threshold : Bool
  threshold : Double
  tied_at_threshold : Int32
}

///|
pub fn Scores::resolution(
  self : Scores,
  alpha : Double,
) -> CalibrationResolution raise ConformalError {
  let rank = conformal_rank(self.count(), alpha)
  let threshold = self.threshold(alpha)
  let mut ties = 0
  if finite(threshold) {
    for value in self.sorted {
      if value == threshold {
        ties = ties + 1
      }
    }
  }
  {
    count: self.count(),
    smallest_p_value: 1.0 / (self.count() + 1).to_double(),
    rank,
    finite_threshold: finite(threshold),
    threshold,
    tied_at_threshold: ties,
  }
}

///|
/// Smallest n within the supported budget admitting a finite corrected threshold.
pub fn minimum_calibration_count(alpha : Double) -> Int32 raise ConformalError {
  validate_alpha(alpha)
  if conformal_rank(100000, alpha) > 100000 {
    raise ResourceLimit(
      "requested alpha requires more than 100000 calibration rows",
    )
  }
  let mut low = 1
  let mut high = 100000
  while low < high {
    let mid = low + (high - low) / 2
    if conformal_rank(mid, alpha) <= mid {
      high = mid
    } else {
      low = mid + 1
    }
  }
  low
}

///|
/// Nonempty alpha grid in caller order; no sorting or deduplication changes semantics.
pub fn interval_path(
  scores : Scores,
  prediction : Double,
  levels : Array[Double],
) -> Array[Interval] raise ConformalError {
  if levels.is_empty() || levels.length() > 1000 {
    raise ResourceLimit("confidence path requires 1..1000 levels")
  }
  if !finite(prediction) {
    raise Invalid("path prediction must be finite")
  }
  if scores.sorted[0] < 0.0 {
    raise Invalid("interval path requires nonnegative residual scores")
  }
  levels.map(alpha => checked_interval(prediction, scores.threshold(alpha)))
}