///|
pub(all) suberror RegistryError {
  /// A family name is already owned by this registry.
  DuplicateMetric(String)
  /// Retained for source compatibility with the initial skeleton.
  MetricNotFound(String)
  /// A custom collector's descriptor list is structurally invalid.
  InvalidCollector(String)
  /// Two descriptors use the same name but disagree on a stable identity field.
  DescriptorConflict(name~ : String, field~ : String)
} derive(Debug, Eq)

///|
/// Registry owns collector registrations and creates deterministic detached
/// snapshots. Native builds protect its entry table with storage's pthread
/// mutex; portable targets keep the same copy-and-release protocol as no-op.
pub struct Registry {
  entries : Array[CollectorEntry]
  mut next_token : Int
  lock : @storage.Mutex
}

///|
pub fn Registry::new() -> Registry {
  { entries: [], next_token: 1, lock: @storage.Mutex::new() }
}

///|
fn Registry::next_registration(self : Registry) -> Registration {
  let token = self.next_token
  self.next_token += 1
  { token, }
}

///|
fn Registry::validate_descriptors(
  self : Registry,
  descriptors : Array[DescriptorInfo],
) -> Unit raise RegistryError {
  if descriptors.is_empty() {
    raise InvalidCollector(
      "a collector must describe at least one metric family",
    )
  }
  for index, descriptor in descriptors {
    for previous in 0.. CollectorEntry,
  descriptors : Array[DescriptorInfo],
) -> Registration raise RegistryError {
  self.lock.lock()
  defer self.lock.unlock()
  self.validate_descriptors(descriptors)
  let registration = self.next_registration()
  self.entries.push(entry_from_token(registration.token))
  registration
}

///|
/// Register a Counter and return a handle for identity-safe unregistration.
pub fn Registry::register_counter_with_handle(
  self : Registry,
  metric : @metric.Counter,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_counter(metric)
  self.insert(token => CounterEntry(token, metric, descriptor), [descriptor])
}

///|
/// Register a Gauge and return a handle for identity-safe unregistration.
pub fn Registry::register_gauge_with_handle(
  self : Registry,
  metric : @metric.Gauge,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_gauge(metric)
  self.insert(token => GaugeEntry(token, metric, descriptor), [descriptor])
}

///|
/// Register a Histogram and return a handle for identity-safe unregistration.
pub fn Registry::register_histogram_with_handle(
  self : Registry,
  metric : @metric.Histogram,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_histogram(metric)
  self.insert(token => HistogramEntry(token, metric, descriptor), [descriptor])
}

///|
/// Register a CounterVec as one dynamic metric family.
///
/// Children are read at gather time, so adding or deleting a label set changes
/// the next snapshot without requiring another registry registration.
pub fn Registry::register_counter_vec_with_handle(
  self : Registry,
  vector : @vector.CounterVec,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
  self.insert(token => CounterVecEntry(token, vector, descriptor), [descriptor])
}

///|
/// Register a GaugeVec as one dynamic metric family.
pub fn Registry::register_gauge_vec_with_handle(
  self : Registry,
  vector : @vector.GaugeVec,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
  self.insert(token => GaugeVecEntry(token, vector, descriptor), [descriptor])
}

///|
/// Register a HistogramVec as one dynamic metric family.
pub fn Registry::register_histogram_vec_with_handle(
  self : Registry,
  vector : @vector.HistogramVec,
) -> Registration raise RegistryError {
  let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
  self.insert(token => HistogramVecEntry(token, vector, descriptor), [
    descriptor,
  ])
}

///|
/// Register a framework-neutral custom collector adapter.
pub fn Registry::register_collector(
  self : Registry,
  collector : CollectorAdapter,
) -> Registration raise RegistryError {
  let descriptors : Array[DescriptorInfo] = []
  for descriptor in collector.descriptors() {
    descriptors.push(DescriptorInfo::from_descriptor(descriptor))
  }
  self.insert(token => AdapterEntry(token, collector, descriptors), descriptors)
}

///|
/// Backwards-compatible Counter registration.  Prefer
/// `register_counter_with_handle` when lifecycle management is needed.
pub fn Registry::register_counter(
  self : Registry,
  metric : @metric.Counter,
) -> Unit raise RegistryError {
  ignore(self.register_counter_with_handle(metric))
}

///|
/// Backwards-compatible Gauge registration.  Prefer
/// `register_gauge_with_handle` when lifecycle management is needed.
pub fn Registry::register_gauge(
  self : Registry,
  metric : @metric.Gauge,
) -> Unit raise RegistryError {
  ignore(self.register_gauge_with_handle(metric))
}

///|
/// Backwards-compatible Histogram registration.  Prefer
/// `register_histogram_with_handle` when lifecycle management is needed.
pub fn Registry::register_histogram(
  self : Registry,
  metric : @metric.Histogram,
) -> Unit raise RegistryError {
  ignore(self.register_histogram_with_handle(metric))
}

///|
/// Backwards-compatible CounterVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_counter_vec(
  self : Registry,
  vector : @vector.CounterVec,
) -> Unit raise RegistryError {
  ignore(self.register_counter_vec_with_handle(vector))
}

///|
/// Backwards-compatible GaugeVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_gauge_vec(
  self : Registry,
  vector : @vector.GaugeVec,
) -> Unit raise RegistryError {
  ignore(self.register_gauge_vec_with_handle(vector))
}

///|
/// Backwards-compatible HistogramVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_histogram_vec(
  self : Registry,
  vector : @vector.HistogramVec,
) -> Unit raise RegistryError {
  ignore(self.register_histogram_vec_with_handle(vector))
}

///|
/// Remove exactly the registration represented by `registration`.
///
/// Returning `false` for a stale or already removed handle is intentional;
/// the method never removes a newer family with the same descriptor name.
pub fn Registry::unregister(
  self : Registry,
  registration : Registration,
) -> Bool {
  self.lock.lock()
  defer self.lock.unlock()
  match self.entries.search_by(entry => entry.token() == registration.token) {
    Some(index) => {
      ignore(self.entries.remove(index))
      true
    }
    None => false
  }
}

///|
pub fn Registry::registered_count(self : Registry) -> Int {
  self.lock.lock()
  defer self.lock.unlock()
  self.entries.length()
}

///|
/// Copy the collection set under the table mutex and immediately release it.
/// Collector callbacks are consequently never invoked while the Registry lock
/// is held, which preserves re-entry and lock-order safety.
fn Registry::entries_snapshot(self : Registry) -> Array[CollectorEntry] {
  self.lock.lock()
  defer self.lock.unlock()
  self.entries.copy()
}

///|
/// Return a copy of currently registered Counter references.  This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::counters(self : Registry) -> Array[@metric.Counter] {
  self.lock.lock()
  defer self.lock.unlock()
  let result : Array[@metric.Counter] = []
  for entry in self.entries {
    match entry {
      CounterEntry(_, counter, _) => result.push(counter)
      _ => ()
    }
  }
  result
}

///|
/// Return a copy of currently registered Gauge references.  This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::gauges(self : Registry) -> Array[@metric.Gauge] {
  self.lock.lock()
  defer self.lock.unlock()
  let result : Array[@metric.Gauge] = []
  for entry in self.entries {
    match entry {
      GaugeEntry(_, gauge, _) => result.push(gauge)
      _ => ()
    }
  }
  result
}

///|
/// Return a copy of currently registered Histogram references.  This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::histograms(self : Registry) -> Array[@metric.Histogram] {
  self.lock.lock()
  defer self.lock.unlock()
  let result : Array[@metric.Histogram] = []
  for entry in self.entries {
    match entry {
      HistogramEntry(_, histogram, _) => result.push(histogram)
      _ => ()
    }
  }
  result
}

///|
/// Collect every registered family into a deeply detached, deterministic
/// snapshot.  Collection is fail-closed: no partial result is returned when a
/// custom collector fails or violates its descriptor contract.
pub fn Registry::gather(self : Registry) -> GatherSnapshot raise GatherError {
  // A shallow copy establishes the collection set before invoking user
  // callbacks. `entries_snapshot` already released the Registry mutex.
  let entries = self.entries_snapshot()
  let families : Array[FamilySnapshot] = []
  for entry in entries {
    let collected = match entry.collect() {
      Ok(value) => value
      Err(Failed(message)) =>
        raise CollectorFailed(token=entry.token(), message~)
    }
    for family in normalize_entry_families(entry, collected) {
      for previous in families {
        if previous.name == family.name {
          raise InvalidFamilySamples(family.name)
        }
      }
      families.push(family)
    }
  }
  families.sort_by((left, right) => left.name.lexical_compare(right.name))
  { families, }
}