import { metrics } from "@opentelemetry/api";
import type { Counter, Histogram, UpDownCounter } from "@opentelemetry/api";
import type { IMetrics, MetricAttributeValue } from "../metrics.interface";

/**
 * OTel-backed IMetrics implementation.
 *
 * - counter   → OTel Counter (monotonic, add-only)
 * - histogram → OTel Histogram
 * - gauge     → OTel UpDownCounter (synchronous emit). Known limitation: this
 *               accumulates deltas, not point-in-time values. True "set to
 *               absolute" semantics require ObservableGauge with a callback;
 *               deferred to a v2 interface.
 *
 * Instrument instances are lazily created and cached per name so repeated
 * calls to the same metric name reuse the same OTel instrument.
 */
export class OtelMetrics implements IMetrics {
  private readonly meter = metrics.getMeter("@repo/core-shared", "1.0.0");
  private readonly counters = new Map<string, Counter>();
  private readonly histograms = new Map<string, Histogram>();
  private readonly gauges = new Map<string, UpDownCounter>();

  counter(
    name: string,
    value = 1,
    attributes?: Record<string, MetricAttributeValue>,
  ): void {
    let counter = this.counters.get(name);
    if (!counter) {
      counter = this.meter.createCounter(name);
      this.counters.set(name, counter);
    }
    counter.add(value, attributes);
  }

  histogram(
    name: string,
    value: number,
    attributes?: Record<string, MetricAttributeValue>,
  ): void {
    let histogram = this.histograms.get(name);
    if (!histogram) {
      histogram = this.meter.createHistogram(name);
      this.histograms.set(name, histogram);
    }
    histogram.record(value, attributes);
  }

  /**
   * Records a gauge value via UpDownCounter.
   *
   * Note: UpDownCounter accumulates a running delta — each call adds to the
   * previous value rather than replacing it. This is a synchronous approximation
   * of gauge semantics. For true "set to absolute value" behaviour, use an
   * ObservableGauge with a periodic callback instead (a future v2 addition).
   */
  gauge(
    name: string,
    value: number,
    attributes?: Record<string, MetricAttributeValue>,
  ): void {
    let gauge = this.gauges.get(name);
    if (!gauge) {
      gauge = this.meter.createUpDownCounter(name);
      this.gauges.set(name, gauge);
    }
    gauge.add(value, attributes);
  }
}
