export class Metrics {
  private static counters = new Map<string, number>();
  private static gauges = new Map<string, number>();
  private static labeledCounters = new Map<string, Map<string, number>>();
  private static labeledGauges = new Map<string, Map<string, number>>();

  static enabled(): boolean {
    if (typeof process !== 'undefined' && process.env) {
      if (process.env.METRICS_ENABLED != null) {
        const v = process.env.METRICS_ENABLED.toLowerCase();
        return v === 'true' || v === '1' || v === 'yes';
      }
      return process.env.NODE_ENV !== 'test';
    }
    return true;
  }

  private static serializeLabels(labels: Record<string, string> | undefined | null): string | null {
    if (!labels) return null;
    const keys = Object.keys(labels).sort();
    const parts = keys.map(k => {
      const val = String(labels[k] ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
      return `${k}="${val}"`;
    });
    return parts.join(',');
  }

  private static ensureLabeledMap(map: Map<string, Map<string, number>>, name: string): Map<string, number> {
    let inner = map.get(name);
    if (!inner) {
      inner = new Map<string, number>();
      map.set(name, inner);
    }
    return inner;
  }

  static inc(name: string, value: number = 1, labels?: Record<string, string>): void {
    if (!this.enabled()) return;
    if (labels && Object.keys(labels).length > 0) {
      const key = this.serializeLabels(labels);
      if (!key) return;
      const inner = this.ensureLabeledMap(this.labeledCounters, name);
      const v = inner.get(key) || 0;
      inner.set(key, v + value);
      return;
    }
    const v = this.counters.get(name) || 0;
    this.counters.set(name, v + value);
  }

  static set(name: string, value: number, labels?: Record<string, string>): void {
    if (!this.enabled()) return;
    if (labels && Object.keys(labels).length > 0) {
      const key = this.serializeLabels(labels);
      if (!key) return;
      const inner = this.ensureLabeledMap(this.labeledGauges, name);
      inner.set(key, value);
      return;
    }
    this.gauges.set(name, value);
  }

  static get(name: string): number | undefined {
    if (this.gauges.has(name)) return this.gauges.get(name);
    if (this.counters.has(name)) return this.counters.get(name);
    return undefined;
  }

  static render(format: 'prom' | 'json' = 'prom'): string {
    if (format === 'json') {
      const json = {
        counters: Object.fromEntries(this.counters.entries()),
        gauges: Object.fromEntries(this.gauges.entries()),
        labeledCounters: Object.fromEntries(
          Array.from(this.labeledCounters.entries()).map(([name, inner]) => [name, Object.fromEntries(inner.entries())])
        ),
        labeledGauges: Object.fromEntries(
          Array.from(this.labeledGauges.entries()).map(([name, inner]) => [name, Object.fromEntries(inner.entries())])
        )
      };
      return JSON.stringify(json);
    }
    const lines: string[] = [];
    for (const [k, v] of this.counters.entries()) {
      lines.push(`# TYPE ${k} counter`);
      lines.push(`${k} ${v}`);
    }
    for (const [name, inner] of this.labeledCounters.entries()) {
      lines.push(`# TYPE ${name} counter`);
      for (const [labelKey, v] of inner.entries()) {
        lines.push(`${name}{${labelKey}} ${v}`);
      }
    }
    for (const [k, v] of this.gauges.entries()) {
      lines.push(`# TYPE ${k} gauge`);
      lines.push(`${k} ${v}`);
    }
    for (const [name, inner] of this.labeledGauges.entries()) {
      lines.push(`# TYPE ${name} gauge`);
      for (const [labelKey, v] of inner.entries()) {
        lines.push(`${name}{${labelKey}} ${v}`);
      }
    }
    return lines.join('\n') + '\n';
  }

  static reset(): void {
    this.counters.clear();
    this.gauges.clear();
    this.labeledCounters.clear();
    this.labeledGauges.clear();
  }
}
