///|
/// Empirical diagnostics, not estimates of a universal conditional guarantee.
pub(all) struct IntervalMetrics {
  count : Int32
  covered : Int32
  empty : Int32
  unbounded : Int32
  mean_width : Double
  coverage : Double
}

///|
/// NaN and infinities with the wrong orientation are not valid prediction sets.
fn validate_interval(interval : Interval) -> Unit raise ConformalError {
  if interval.lower.is_nan() || interval.upper.is_nan() {
    raise Invalid("NaN interval endpoint")
  }
  if interval.lower.is_pos_inf() || interval.upper == @double.neg_infinity {
    raise Invalid("invalid infinite endpoint orientation")
  }
}

///|
/// Count infinities separately; an unbounded set makes mean_width infinite.
pub fn evaluate_intervals(
  truth : Array[Double],
  intervals : Array[Interval],
) -> IntervalMetrics raise ConformalError {
  validate_values(truth, "evaluation truth")
  same_length(truth.length(), intervals.length())
  let mut covered = 0
  let mut empty = 0
  let mut unbounded = 0
  let mut mean = 0.0
  for i in 0.. Double raise ConformalError {
  validate_alpha(alpha)
  validate_interval(interval)
  if !finite(truth) {
    raise Invalid("interval score truth must be finite")
  }
  if interval.is_empty() {
    raise Invalid("interval score undefined for an empty set")
  }
  if interval.lower.is_inf() || interval.upper.is_inf() {
    return @double.infinity
  }
  let miss = if truth < interval.lower {
    interval.lower - truth
  } else if truth > interval.upper {
    truth - interval.upper
  } else {
    0.0
  }
  interval.width() + 2.0 * miss / alpha
}

///|
pub(all) struct SetMetrics {
  count : Int32
  covered : Int32
  empty : Int32
  singletons : Int32
  mean_size : Double
  coverage : Double
  singleton_accuracy : Double?
}

///|
/// Duplicated labels are invalid rather than artificially increasing set size.
fn validate_label_set(
  labels : Array[Int32],
  classes : Int32,
) -> Unit raise ConformalError {
  if labels.length() > classes {
    raise Invalid("prediction set exceeds class count")
  }
  let seen = Array::make(classes, false)
  for label in labels {
    if label < 0 || label >= classes {
      raise Invalid("prediction set label out of range")
    }
    if seen[label] {
      raise Invalid("duplicate prediction set label")
    }
    seen[label] = true
  }
}

///|
pub fn evaluate_sets(
  truth : Array[Int32],
  sets : Array[Array[Int32]],
  classes : Int32,
) -> SetMetrics raise ConformalError {
  if truth.is_empty() || truth.length() > 100000 {
    raise Invalid("evaluation size must be in 1..100000")
  }
  if classes < 2 || classes > 1024 {
    raise Invalid("invalid evaluation class count")
  }
  if truth.length() > 2000000 / classes {
    raise ResourceLimit("set evaluation exceeds two million class checks")
  }
  same_length(truth.length(), sets.length())
  let mut covered = 0
  let mut empty = 0
  let mut singletons = 0
  let mut singleton_hits = 0
  let mut total_size = 0
  for i in 0..= classes {
      raise Invalid("evaluation label out of range")
    }
    validate_label_set(sets[i], classes)
    let hit = sets[i].contains(truth[i])
    if hit {
      covered = covered + 1
    }
    if sets[i].is_empty() {
      empty = empty + 1
    }
    if sets[i].length() == 1 {
      singletons = singletons + 1
      if hit {
        singleton_hits = singleton_hits + 1
      }
    }
    total_size = total_size + sets[i].length()
  }
  {
    count: truth.length(),
    covered,
    empty,
    singletons,
    mean_size: total_size.to_double() / truth.length().to_double(),
    coverage: covered.to_double() / truth.length().to_double(),
    singleton_accuracy: if singletons == 0 {
      None
    } else {
      Some(singleton_hits.to_double() / singletons.to_double())
    },
  }
}