///|
/// A structured failure returned by a framework-neutral custom collector.
///
/// `CollectorAdapter` deliberately uses an explicit `Result` rather than a
/// trait object.  MoonBit packages can therefore register heterogeneous
/// collectors on every supported target without relying on runtime trait
/// object dispatch.
pub(all) suberror CollectorError {
  Failed(String)
} derive(Debug, Eq)

///|
/// A custom collector represented by immutable descriptors and a collection
/// callback.  The callback may produce zero or more samples for each
/// descriptor, but may not introduce an undeclared family.
pub struct CollectorAdapter {
  descriptors : Array[@metric.Descriptor]
  collect : () -> Result[Array[FamilySnapshot], CollectorError]
}

///|
/// Construct a framework-neutral custom collector adapter.
///
/// Descriptor uniqueness and all cross-registry conflict checks happen in
/// `Registry::register_collector`, where they can be checked atomically.
pub fn CollectorAdapter::new(
  descriptors : Array[@metric.Descriptor],
  collect : () -> Result[Array[FamilySnapshot], CollectorError],
) -> CollectorAdapter {
  { descriptors: descriptors.copy(), collect }
}

///|
pub fn CollectorAdapter::descriptors(
  self : CollectorAdapter,
) -> Array[@metric.Descriptor] {
  self.descriptors.copy()
}

///|
fn CollectorAdapter::collect_snapshots(
  self : CollectorAdapter,
) -> Result[Array[FamilySnapshot], CollectorError] {
  (self.collect)()
}

///|
enum CollectorEntry {
  CounterEntry(Int, @metric.Counter, DescriptorInfo)
  GaugeEntry(Int, @metric.Gauge, DescriptorInfo)
  HistogramEntry(Int, @metric.Histogram, DescriptorInfo)
  CounterVecEntry(Int, @vector.CounterVec, DescriptorInfo)
  GaugeVecEntry(Int, @vector.GaugeVec, DescriptorInfo)
  HistogramVecEntry(Int, @vector.HistogramVec, DescriptorInfo)
  AdapterEntry(Int, CollectorAdapter, Array[DescriptorInfo])
}

///|
fn CollectorEntry::token(self : CollectorEntry) -> Int {
  match self {
    CounterEntry(token, _, _) => token
    GaugeEntry(token, _, _) => token
    HistogramEntry(token, _, _) => token
    CounterVecEntry(token, _, _) => token
    GaugeVecEntry(token, _, _) => token
    HistogramVecEntry(token, _, _) => token
    AdapterEntry(token, _, _) => token
  }
}

///|
fn CollectorEntry::descriptors(self : CollectorEntry) -> Array[DescriptorInfo] {
  match self {
    CounterEntry(_, _, descriptor) => [descriptor]
    GaugeEntry(_, _, descriptor) => [descriptor]
    HistogramEntry(_, _, descriptor) => [descriptor]
    CounterVecEntry(_, _, descriptor) => [descriptor]
    GaugeVecEntry(_, _, descriptor) => [descriptor]
    HistogramVecEntry(_, _, descriptor) => [descriptor]
    AdapterEntry(_, _, descriptors) => descriptors.copy()
  }
}

///|
fn collect_counter_vec_samples(
  vector : @vector.CounterVec,
) -> Array[ScalarSampleSnapshot] {
  let samples : Array[ScalarSampleSnapshot] = []
  for child in vector.children() {
    samples.push(scalar_sample(child.labels(), child.snapshot().value))
  }
  samples
}

///|
fn collect_gauge_vec_samples(
  vector : @vector.GaugeVec,
) -> Array[ScalarSampleSnapshot] {
  let samples : Array[ScalarSampleSnapshot] = []
  for child in vector.children() {
    samples.push(scalar_sample(child.labels(), child.snapshot().value))
  }
  samples
}

///|
fn collect_histogram_vec_samples(
  vector : @vector.HistogramVec,
) -> Array[HistogramSampleSnapshot] {
  let samples : Array[HistogramSampleSnapshot] = []
  for child in vector.children() {
    samples.push(histogram_sample(child.labels(), child.snapshot()))
  }
  samples
}

///|
fn CollectorEntry::collect(
  self : CollectorEntry,
) -> Result[Array[FamilySnapshot], CollectorError] {
  match self {
    CounterEntry(_, counter, descriptor) =>
      Ok([
        FamilySnapshot::scalar(
          descriptor.name,
          descriptor.help,
          descriptor.family_type(),
          [scalar_sample(descriptor.const_labels, counter.snapshot().value)],
          descriptor.unit,
        ),
      ])
    GaugeEntry(_, gauge, descriptor) =>
      Ok([
        FamilySnapshot::scalar(
          descriptor.name,
          descriptor.help,
          descriptor.family_type(),
          [scalar_sample(descriptor.const_labels, gauge.snapshot().value)],
          descriptor.unit,
        ),
      ])
    HistogramEntry(_, histogram, descriptor) =>
      Ok([
        FamilySnapshot::histogram(
          descriptor.name,
          descriptor.help,
          [histogram_sample(descriptor.const_labels, histogram.snapshot())],
          descriptor.unit,
        ),
      ])
    CounterVecEntry(_, vector, descriptor) =>
      Ok([
        FamilySnapshot::scalar(
          descriptor.name,
          descriptor.help,
          descriptor.family_type(),
          collect_counter_vec_samples(vector),
          descriptor.unit,
        ),
      ])
    GaugeVecEntry(_, vector, descriptor) =>
      Ok([
        FamilySnapshot::scalar(
          descriptor.name,
          descriptor.help,
          descriptor.family_type(),
          collect_gauge_vec_samples(vector),
          descriptor.unit,
        ),
      ])
    HistogramVecEntry(_, vector, descriptor) =>
      Ok([
        FamilySnapshot::histogram(
          descriptor.name,
          descriptor.help,
          collect_histogram_vec_samples(vector),
          descriptor.unit,
        ),
      ])
    AdapterEntry(_, adapter, _) => adapter.collect_snapshots()
  }
}