///|
/// Storage type of a metric emitted by a Modbus service.
pub(all) enum MetricKind {
  CounterMetric
  GaugeMetric
  DurationMetric
  RatioMetric
  EventMetric
} derive(Debug, Eq)

///|
/// A metric sample with a small, allocation-friendly label set.
pub(all) struct ServiceMetric {
  name : String
  kind : MetricKind
  value : Int
  timestamp : Int
  labels : Array[(String, String)]
}

///|
pub fn ServiceMetric::new(
  name : String,
  kind : MetricKind,
  value : Int,
  timestamp : Int,
) -> Result[ServiceMetric, ModbusError] {
  if name == "" || timestamp < 0 {
    Err(InvalidData)
  } else {
    Ok({ name, kind, value, timestamp, labels: [] })
  }
}

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

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

///|
pub fn ServiceMetric::value(self : ServiceMetric) -> Int {
  self.value
}

///|
pub fn ServiceMetric::timestamp(self : ServiceMetric) -> Int {
  self.timestamp
}

///|
pub fn ServiceMetric::label_count(self : ServiceMetric) -> Int {
  self.labels.length()
}

///|
pub fn ServiceMetric::label(self : ServiceMetric, key : String) -> String? {
  for item in self.labels {
    if item.0 == key {
      return Some(item.1)
    }
  }
  None
}

///|
pub fn ServiceMetric::with_label(
  self : ServiceMetric,
  key : String,
  value : String,
) -> Result[Unit, ModbusError] {
  if key == "" {
    return Err(InvalidData)
  }
  for i in 0..= 12 {
    return Err(CapacityExceeded)
  }
  self.labels.push((key, value))
  Ok(())
}

///|
pub fn metric_kind_name(kind : MetricKind) -> String {
  match kind {
    CounterMetric => "counter"
    GaugeMetric => "gauge"
    DurationMetric => "duration"
    RatioMetric => "ratio"
    EventMetric => "event"
  }
}

///|
/// Bounded history for one named metric.
pub(all) struct MetricSeries {
  name : String
  kind : MetricKind
  capacity : Int
  samples : Array[ServiceMetric]
  mut dropped : Int
}

///|
pub fn MetricSeries::new(
  name : String,
  kind : MetricKind,
  capacity? : Int = 1024,
) -> Result[MetricSeries, ModbusError] {
  if name == "" || capacity < 1 {
    Err(InvalidData)
  } else {
    Ok({ name, kind, capacity, samples: [], dropped: 0 })
  }
}

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

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

///|
pub fn MetricSeries::length(self : MetricSeries) -> Int {
  self.samples.length()
}

///|
pub fn MetricSeries::dropped(self : MetricSeries) -> Int {
  self.dropped
}

///|
pub fn MetricSeries::append(
  self : MetricSeries,
  metric : ServiceMetric,
) -> Result[Unit, ModbusError] {
  if metric.name() != self.name || metric.kind() != self.kind {
    return Err(UnitMismatch)
  }
  if self.samples.length() > 0 &&
    metric.timestamp() < self.samples[self.samples.length() - 1].timestamp() {
    return Err(InvalidData)
  }
  if self.samples.length() >= self.capacity {
    ignore(self.samples.remove(0))
    self.dropped += 1
  }
  self.samples.push(metric)
  Ok(())
}

///|
pub fn MetricSeries::latest(self : MetricSeries) -> ServiceMetric? {
  if self.samples.length() == 0 {
    None
  } else {
    Some(self.samples[self.samples.length() - 1])
  }
}

///|
pub fn MetricSeries::sum(self : MetricSeries) -> Int {
  let mut sum = 0
  for metric in self.samples {
    sum += metric.value()
  }
  sum
}

///|
pub fn MetricSeries::minimum(self : MetricSeries) -> Int? {
  if self.samples.length() == 0 {
    return None
  }
  let mut value = self.samples[0].value()
  for metric in self.samples {
    if metric.value() < value {
      value = metric.value()
    }
  }
  Some(value)
}

///|
pub fn MetricSeries::maximum(self : MetricSeries) -> Int? {
  if self.samples.length() == 0 {
    return None
  }
  let mut value = self.samples[0].value()
  for metric in self.samples {
    if metric.value() > value {
      value = metric.value()
    }
  }
  Some(value)
}

///|
pub fn MetricSeries::average(self : MetricSeries) -> Int? {
  if self.samples.length() == 0 {
    None
  } else {
    Some(self.sum() / self.samples.length())
  }
}

///|
pub fn MetricSeries::between(
  self : MetricSeries,
  start : Int,
  end : Int,
) -> Result[Array[ServiceMetric], ModbusError] {
  if start < 0 || end < start {
    return Err(InvalidData)
  }
  let result : Array[ServiceMetric] = []
  for metric in self.samples {
    if metric.timestamp() >= start && metric.timestamp() <= end {
      result.push(metric)
    }
  }
  Ok(result)
}

///|
/// A registry used by transports, schedulers, and HTTP adapters.
pub(all) struct MetricRegistry {
  series : Array[MetricSeries]
  max_series : Int
  max_samples : Int
}

///|
pub fn MetricRegistry::new(
  max_series? : Int = 512,
  max_samples? : Int = 1024,
) -> Result[MetricRegistry, ModbusError] {
  if max_series < 1 || max_samples < 1 {
    Err(InvalidData)
  } else {
    Ok({ series: [], max_series, max_samples })
  }
}

///|
pub fn MetricRegistry::series_count(self : MetricRegistry) -> Int {
  self.series.length()
}

///|
pub fn MetricRegistry::register(
  self : MetricRegistry,
  name : String,
  kind : MetricKind,
) -> Result[Unit, ModbusError] {
  if self.series.length() >= self.max_series {
    return Err(CapacityExceeded)
  }
  for existing in self.series {
    if existing.name() == name {
      return Err(InvalidData)
    }
  }
  let series = match MetricSeries::new(name, kind, capacity=self.max_samples) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  self.series.push(series)
  Ok(())
}

///|
pub fn MetricRegistry::has(self : MetricRegistry, name : String) -> Bool {
  for series in self.series {
    if series.name() == name {
      return true
    }
  }
  false
}

///|
pub fn MetricRegistry::record(
  self : MetricRegistry,
  metric : ServiceMetric,
) -> Result[Unit, ModbusError] {
  for series in self.series {
    if series.name() == metric.name() {
      return series.append(metric)
    }
  }
  if self.series.length() >= self.max_series {
    return Err(CapacityExceeded)
  }
  let series = match
    MetricSeries::new(metric.name(), metric.kind(), capacity=self.max_samples) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  self.series.push(series)
  self.series[self.series.length() - 1].append(metric)
}

///|
pub fn MetricRegistry::latest(
  self : MetricRegistry,
  name : String,
) -> ServiceMetric? {
  for series in self.series {
    if series.name() == name {
      return series.latest()
    }
  }
  None
}

///|
pub fn MetricRegistry::series(
  self : MetricRegistry,
  name : String,
) -> MetricSeries? {
  for series in self.series {
    if series.name() == name {
      return Some(series)
    }
  }
  None
}

///|
pub fn MetricRegistry::names(self : MetricRegistry) -> Array[String] {
  let result : Array[String] = []
  for series in self.series {
    result.push(series.name())
  }
  result
}

///|
/// Export a compact Prometheus-like line representation without external dependencies.
pub fn MetricRegistry::export_lines(self : MetricRegistry) -> Array[String] {
  let lines : Array[String] = []
  for series in self.series {
    match series.latest() {
      Some(metric) =>
        lines.push(
          metric.name() +
          "{kind=\"" +
          metric_kind_name(metric.kind()) +
          "\"} " +
          metric.value().to_string(),
        )
      None => ()
    }
  }
  lines
}

///|
/// Collect a time range from every registered series.
pub fn MetricRegistry::range(
  self : MetricRegistry,
  start : Int,
  end : Int,
) -> Result[Array[ServiceMetric], ModbusError] {
  if start < 0 || end < start {
    return Err(InvalidData)
  }
  let result : Array[ServiceMetric] = []
  for series in self.series {
    let values = match series.between(start, end) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    for value in values {
      result.push(value)
    }
  }
  Ok(result)
}

///|
/// Snapshot counts for a service status endpoint.
pub(all) struct ObservabilityDigest {
  metric_series : Int
  samples : Int
  counters : Int
  gauges : Int
  latest_timestamp : Int
}

///|
pub fn MetricRegistry::digest(self : MetricRegistry) -> ObservabilityDigest {
  let mut samples = 0
  let mut counters = 0
  let mut gauges = 0
  let mut latest = 0
  for series in self.series {
    samples += series.length()
    match series.kind() {
      CounterMetric => counters += 1
      GaugeMetric => gauges += 1
      _ => ()
    }
    match series.latest() {
      Some(metric) =>
        if metric.timestamp() > latest {
          latest = metric.timestamp()
        }
      None => ()
    }
  }
  {
    metric_series: self.series.length(),
    samples,
    counters,
    gauges,
    latest_timestamp: latest,
  }
}

///|
pub fn ObservabilityDigest::metric_series(self : ObservabilityDigest) -> Int {
  self.metric_series
}

///|
pub fn ObservabilityDigest::samples(self : ObservabilityDigest) -> Int {
  self.samples
}

///|
pub fn ObservabilityDigest::counters(self : ObservabilityDigest) -> Int {
  self.counters
}

///|
pub fn ObservabilityDigest::gauges(self : ObservabilityDigest) -> Int {
  self.gauges
}

///|
pub fn ObservabilityDigest::latest_timestamp(self : ObservabilityDigest) -> Int {
  self.latest_timestamp
}