///|
/// Immutable observation snapshot consumed by exporters.
pub(all) struct HistogramSnapshot {
  boundaries : Array[Double]
  cumulative_counts : Array[Int64]
  count : Int64
  sum : Double
} derive(Debug, Eq)

///|
/// A histogram with fixed, strictly increasing bucket boundaries.
pub struct Histogram {
  descriptor : Descriptor
  boundaries : Array[Double]
  value : @storage.HistogramCell
}

///|
pub fn validate_histogram_boundaries(
  boundaries : Array[Double],
) -> Unit raise MetricError {
  for boundary in boundaries {
    if boundary.is_nan() || boundary.is_inf() {
      raise InvalidBucketBoundary(boundary)
    }
  }
  for index in 1.. Histogram raise MetricError {
  let descriptor = make_metric_descriptor(
    name,
    HistogramKind,
    help,
    unit,
    const_labels,
  )
  validate_histogram_boundaries(boundaries)
  {
    descriptor,
    boundaries: boundaries.copy(),
    value: @storage.HistogramCell::new(boundaries),
  }
}

///|
pub fn Histogram::name(self : Histogram) -> String {
  self.descriptor.name()
}

///|
pub fn Histogram::help(self : Histogram) -> String {
  self.descriptor.help()
}

///|
pub fn Histogram::unit(self : Histogram) -> String? {
  self.descriptor.unit()
}

///|
pub fn Histogram::labels(self : Histogram) -> Array[Label] {
  self.descriptor.const_labels()
}

///|
pub fn Histogram::descriptor(self : Histogram) -> Descriptor {
  self.descriptor
}

///|
pub fn Histogram::boundaries(self : Histogram) -> Array[Double] {
  self.boundaries.copy()
}

///|
pub fn Histogram::observe(
  self : Histogram,
  value : Double,
) -> Unit raise MetricError {
  if value.is_nan() || value.is_inf() {
    raise NonFiniteObservation
  }
  self.value.observe(value)
}

///|
pub fn Histogram::snapshot(self : Histogram) -> HistogramSnapshot {
  let value = self.value.snapshot()
  {
    boundaries: self.boundaries.copy(),
    cumulative_counts: value.cumulative_counts,
    count: value.count,
    sum: value.sum,
  }
}