///|
/// Errors raised when a registry cannot produce one internally consistent
/// snapshot.  Gather is fail-closed: it never returns a partial exposition.
pub(all) suberror GatherError {
  CollectorFailed(token~ : Int, message~ : String)
  UndeclaredFamily(String)
  FamilyMetadataMismatch(name~ : String, field~ : String)
  InvalidFamilySamples(String)
  InvalidLabels(String)
  DuplicateSample(String)
  InvalidHistogram(String)
} derive(Debug, Eq)

///|
fn labels_for_snapshot(labels : Array[@metric.Label]) -> Array[@metric.Label] {
  canonical_labels(labels)
}

///|
fn compare_labels(
  left : Array[@metric.Label],
  right : Array[@metric.Label],
) -> Int {
  let shared_length = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  for index in 0.. Unit raise GatherError {
  let canonical = canonical_labels(labels)
  let expected_names = descriptor.expected_label_names()
  if canonical.length() != expected_names.length() {
    raise InvalidLabels(descriptor.name)
  }
  for index in 0.. 0 && canonical[index - 1].name == canonical[index].name {
      raise InvalidLabels(descriptor.name)
    }
  }
  for constant in descriptor.const_labels {
    let matched = canonical.search_by(label => label.name == constant.name)
    match matched {
      Some(index) =>
        if canonical[index].value != constant.value {
          raise InvalidLabels(descriptor.name)
        }
      None => raise InvalidLabels(descriptor.name)
    }
  }
}

///|
fn validate_histogram(
  sample : @metric.HistogramSnapshot,
  family_name : String,
) -> Unit raise GatherError {
  if sample.boundaries.length() != sample.cumulative_counts.length() {
    raise InvalidHistogram(family_name)
  }
  for index in 0.. 0 && sample.boundaries[index] <= sample.boundaries[index - 1] {
      raise InvalidHistogram(family_name)
    }
    if sample.cumulative_counts[index] < 0L {
      raise InvalidHistogram(family_name)
    }
    if index > 0 &&
      sample.cumulative_counts[index] < sample.cumulative_counts[index - 1] {
      raise InvalidHistogram(family_name)
    }
  }
  if sample.count < 0L ||
    (
      sample.cumulative_counts.length() > 0 &&
      sample.cumulative_counts[sample.cumulative_counts.length() - 1] >
      sample.count
    ) {
    raise InvalidHistogram(family_name)
  }
}

///|
fn normalize_scalar_samples(
  samples : Array[ScalarSampleSnapshot],
  descriptor : DescriptorInfo,
) -> Array[ScalarSampleSnapshot] raise GatherError {
  let result : Array[ScalarSampleSnapshot] = []
  for sample in samples {
    validate_labels(sample.labels, descriptor)
    result.push(scalar_sample(labels_for_snapshot(sample.labels), sample.value))
  }
  result.sort_by((left, right) => compare_labels(left.labels, right.labels))
  for index in 1.. Array[HistogramSampleSnapshot] raise GatherError {
  let result : Array[HistogramSampleSnapshot] = []
  for sample in samples {
    validate_labels(sample.labels, descriptor)
    validate_histogram(sample.value, descriptor.name)
    result.push(
      histogram_sample(labels_for_snapshot(sample.labels), sample.value),
    )
  }
  result.sort_by((left, right) => compare_labels(left.labels, right.labels))
  for index in 1.. DescriptorInfo? {
  for descriptor in descriptors {
    if descriptor.name == name {
      return Some(descriptor)
    }
  }
  None
}

///|
fn normalize_family(
  family : FamilySnapshot,
  descriptors : Array[DescriptorInfo],
) -> FamilySnapshot raise GatherError {
  let descriptor = match descriptor_for_family(descriptors, family.name) {
    Some(descriptor) => descriptor
    None => raise UndeclaredFamily(family.name)
  }
  if family.help != descriptor.help {
    raise FamilyMetadataMismatch(name=family.name, field="help")
  }
  if family.unit != descriptor.unit {
    raise FamilyMetadataMismatch(name=family.name, field="unit")
  }
  if family.family_type != descriptor.family_type() {
    raise FamilyMetadataMismatch(name=family.name, field="type")
  }
  match family.family_type {
    CounterFamily | GaugeFamily => {
      if !family.histogram_samples.is_empty() {
        raise InvalidFamilySamples(family.name)
      }
      FamilySnapshot::scalar(
        family.name,
        family.help,
        family.family_type,
        normalize_scalar_samples(family.scalar_samples, descriptor),
        family.unit,
      )
    }
    HistogramFamily => {
      if !family.scalar_samples.is_empty() {
        raise InvalidFamilySamples(family.name)
      }
      FamilySnapshot::histogram(
        family.name,
        family.help,
        normalize_histogram_samples(family.histogram_samples, descriptor),
        family.unit,
      )
    }
  }
}

///|
fn normalize_entry_families(
  entry : CollectorEntry,
  families : Array[FamilySnapshot],
) -> Array[FamilySnapshot] raise GatherError {
  let descriptors = entry.descriptors()
  let normalized : Array[FamilySnapshot] = []
  for family in families {
    for previous in normalized {
      if previous.name == family.name {
        raise InvalidFamilySamples(family.name)
      }
    }
    normalized.push(normalize_family(family, descriptors))
  }
  normalized
}