///|
/// An opaque identity returned by a successful registry registration.
///
/// A registration is scoped to one `Registry`.  It is intentionally not a
/// metric name: an old handle must never unregister a newer metric family that
/// reused the same name.
pub struct Registration {
  token : Int
} derive(Debug, Eq)

///|
/// Return the registry-local token for diagnostics and test assertions.
///
/// The token alone is not accepted by `unregister`; callers must retain the
/// `Registration` value returned by this registry.
pub fn Registration::token(self : Registration) -> Int {
  self.token
}

///|
priv struct DescriptorInfo {
  name : String
  help : String
  unit : String?
  kind : @metric.MetricKind
  const_labels : Array[@metric.Label]
  variable_label_names : Array[String]
}

///|
fn canonical_labels(labels : Array[@metric.Label]) -> Array[@metric.Label] {
  let result = labels.copy()
  result.sort_by((left, right) => {
    let name_order = left.name.lexical_compare(right.name)
    if name_order != 0 {
      name_order
    } else {
      left.value.lexical_compare(right.value)
    }
  })
  result
}

///|
fn labels_equal(
  left : Array[@metric.Label],
  right : Array[@metric.Label],
) -> Bool {
  if left.length() != right.length() {
    return false
  }
  for index in 0.. Bool {
  if left.length() != right.length() {
    return false
  }
  for index in 0.. DescriptorInfo {
  {
    name: descriptor.name(),
    help: descriptor.help(),
    unit: descriptor.unit(),
    kind: descriptor.kind(),
    const_labels: canonical_labels(descriptor.const_labels()),
    // Label values supplied to a future vector are positional, so this order
    // is part of descriptor identity and must not be canonicalized.
    variable_label_names: descriptor.variable_label_names(),
  }
}

///|
fn DescriptorInfo::from_counter(counter : @metric.Counter) -> DescriptorInfo {
  DescriptorInfo::from_descriptor(counter.descriptor())
}

///|
fn DescriptorInfo::from_gauge(gauge : @metric.Gauge) -> DescriptorInfo {
  DescriptorInfo::from_descriptor(gauge.descriptor())
}

///|
fn DescriptorInfo::from_histogram(
  histogram : @metric.Histogram,
) -> DescriptorInfo {
  DescriptorInfo::from_descriptor(histogram.descriptor())
}

///|
fn descriptor_infos_equal(
  left : DescriptorInfo,
  right : DescriptorInfo,
) -> Bool {
  left.name == right.name &&
  left.help == right.help &&
  left.unit == right.unit &&
  left.kind == right.kind &&
  labels_equal(left.const_labels, right.const_labels) &&
  strings_equal(left.variable_label_names, right.variable_label_names)
}

///|
fn descriptor_mismatch_field(
  left : DescriptorInfo,
  right : DescriptorInfo,
) -> String {
  if left.kind != right.kind {
    "type"
  } else if left.help != right.help {
    "help"
  } else if left.unit != right.unit {
    "unit"
  } else if !labels_equal(left.const_labels, right.const_labels) {
    "const_labels"
  } else if !strings_equal(
      left.variable_label_names,
      right.variable_label_names,
    ) {
    "variable_label_names"
  } else {
    "name"
  }
}

///|
fn DescriptorInfo::family_type(self : DescriptorInfo) -> FamilyType {
  match self.kind {
    CounterKind => CounterFamily
    GaugeKind => GaugeFamily
    HistogramKind => HistogramFamily
  }
}

///|
fn DescriptorInfo::expected_label_names(self : DescriptorInfo) -> Array[String] {
  let result = self.variable_label_names.copy()
  for label in self.const_labels {
    result.push(label.name)
  }
  result.sort_by((left, right) => left.lexical_compare(right))
  result
}