///|
/// One empirical report per fixed group; group order is deterministic.
pub(all) struct GroupIntervalMetrics {
  group : String
  metrics : IntervalMetrics
}

///|
pub fn evaluate_grouped_intervals(
  truth : Array[Double],
  intervals : Array[Interval],
  groups : Array[String],
) -> Array[GroupIntervalMetrics] raise ConformalError {
  validate_values(truth, "group evaluation truth")
  same_length(truth.length(), intervals.length())
  same_length(truth.length(), groups.length())
  let indices : Map[String, Array[Int32]] = Map([])
  for i in 0.. 256 {
      raise Invalid("invalid evaluation group name")
    }
    match indices.get(groups[i]) {
      Some(rows) => rows.push(i)
      None => indices.set(groups[i], [i])
    }
    if indices.length() > 10000 {
      raise ResourceLimit("too many evaluation groups")
    }
  }
  let names = []
  for name, _ in indices {
    names.push(name)
  }
  names.sort()
  let result = []
  for name in names {
    match indices.get(name) {
      None => raise UnknownGroup(name)
      Some(rows) => {
        let y = rows.map(i => truth[i])
        let sets = rows.map(i => intervals[i])
        result.push({ group: name, metrics: evaluate_intervals(y, sets), })
      }
    }
  }
  result
}

///|
/// Simultaneous coverage: a row is covered only when every coordinate is covered.
pub(all) struct VectorMetrics {
  count : Int32
  dimensions : Int32
  covered : Int32
  coverage : Double
  per_dimension : Array[IntervalMetrics]
}

///|
pub fn evaluate_vectors(
  truth : Array[Array[Double]],
  rectangles : Array[Array[Interval]],
) -> VectorMetrics raise ConformalError {
  if truth.is_empty() {
    raise Invalid("empty vector evaluation")
  }
  let dimensions = truth[0].length()
  validate_matrix(truth, truth.length(), dimensions, "vector evaluation truth")
  same_length(truth.length(), rectangles.length())
  let mut covered = 0
  for i in 0.. row[j]),
        rectangles.map(row => row[j]),
      ),
    )
  }
  {
    count: truth.length(),
    dimensions,
    covered,
    coverage: covered.to_double() / truth.length().to_double(),
    per_dimension,
  }
}