///|
/// Metric family kinds supported by the P0 data model.
pub(all) enum MetricKind {
  CounterKind
  GaugeKind
  HistogramKind
} derive(Debug, Eq, Hash)

///|
/// Canonical identity used to compare descriptor registration intent.
///
/// Const-label order does not affect this value. Variable-label order does:
/// values supplied to a vector are positional in that declared order.
pub(all) struct DescriptorIdentity {
  name : String
  kind : MetricKind
  const_labels : Array[Label]
  variable_label_names : Array[String]
} derive(Debug, Eq)

///|
/// Immutable identity and exposition metadata for one metric family.
pub struct Descriptor {
  name : String
  help : String
  unit : String?
  kind : MetricKind
  const_labels : Array[Label]
  variable_label_names : Array[String]
}

///|
pub fn Descriptor::new(
  name : String,
  kind : MetricKind,
  help? : String = "",
  unit? : String,
  const_labels? : Array[Label] = [],
  variable_label_names? : Array[String] = [],
) -> Descriptor raise MetricError {
  validate_metric_name(name)
  validate_help(help)
  match unit {
    Some(value) => validate_unit(value)
    None => ()
  }
  let canonical_labels = canonical_const_labels(const_labels)
  validate_variable_label_names(variable_label_names)
  for label_name in variable_label_names {
    for label in const_labels {
      if label.name == label_name {
        raise DuplicateLabelName(label_name)
      }
    }
  }
  {
    name,
    help,
    unit,
    kind,
    const_labels: canonical_labels,
    variable_label_names: variable_label_names.copy(),
  }
}

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

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

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

///|
pub fn Descriptor::kind(self : Descriptor) -> MetricKind {
  self.kind
}

///|
pub fn Descriptor::const_labels(self : Descriptor) -> Array[Label] {
  self.const_labels.copy()
}

///|
pub fn Descriptor::variable_label_names(self : Descriptor) -> Array[String] {
  self.variable_label_names.copy()
}

///|
pub fn Descriptor::identity(self : Descriptor) -> DescriptorIdentity {
  {
    name: self.name,
    kind: self.kind,
    const_labels: self.const_labels.copy(),
    variable_label_names: self.variable_label_names.copy(),
  }
}

///|
/// Build a descriptor for a concrete metric without variable labels.
///
/// MoonBit optional arguments accept their contained type at call sites, so
/// this helper is the single place that unwraps a public `String?` unit.
fn make_metric_descriptor(
  name : String,
  kind : MetricKind,
  help : String,
  unit : String?,
  const_labels : Array[Label],
) -> Descriptor raise MetricError {
  match unit {
    Some(value) => Descriptor::new(name, kind, help~, unit=value, const_labels~)
    None => Descriptor::new(name, kind, help~, const_labels~)
  }
}