/**
 * Metrics Collector
 *
 * Centralized metrics collection system for Code Buddy.
 * Supports counters, gauges, histograms, and various exporters.
 *
 * Features:
 * - Counters: Track cumulative values (requests, errors, tokens)
 * - Gauges: Track current values (memory, connections)
 * - Histograms: Track distributions (latency, execution time)
 * - Multiple exporters: Console, File, OpenTelemetry
 */
import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { logger } from '../utils/logger.js';
// ============================================================================
// Metric Classes
// ============================================================================
/**
 * Counter metric - monotonically increasing value
 */
export class Counter {
    name;
    help;
    unit;
    values = new Map();
    constructor(name, help, unit) {
        this.name = name;
        this.help = help;
        this.unit = unit;
    }
    /**
     * Increment counter
     */
    inc(labels = {}, value = 1) {
        const key = this.labelsToKey(labels);
        const current = this.values.get(key) || 0;
        this.values.set(key, current + value);
    }
    /**
     * Get all values
     */
    getValues() {
        return Array.from(this.values.entries()).map(([key, value]) => ({
            value,
            labels: this.keyToLabels(key),
        }));
    }
    /**
     * Reset counter
     */
    reset() {
        this.values.clear();
    }
    labelsToKey(labels) {
        return Object.entries(labels)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([k, v]) => `${k}="${v}"`)
            .join(',');
    }
    keyToLabels(key) {
        if (!key)
            return {};
        const labels = {};
        key.split(',').forEach((pair) => {
            const match = pair.match(/^(.+)="(.+)"$/);
            if (match) {
                labels[match[1]] = match[2];
            }
        });
        return labels;
    }
}
/**
 * Gauge metric - value that can go up and down
 */
export class Gauge {
    name;
    help;
    unit;
    values = new Map();
    timestamps = new Map();
    constructor(name, help, unit) {
        this.name = name;
        this.help = help;
        this.unit = unit;
    }
    /**
     * Set gauge value
     */
    set(value, labels = {}) {
        const key = this.labelsToKey(labels);
        this.values.set(key, value);
        this.timestamps.set(key, Date.now());
    }
    /**
     * Increment gauge
     */
    inc(labels = {}, value = 1) {
        const key = this.labelsToKey(labels);
        const current = this.values.get(key) || 0;
        this.values.set(key, current + value);
        this.timestamps.set(key, Date.now());
    }
    /**
     * Decrement gauge
     */
    dec(labels = {}, value = 1) {
        const key = this.labelsToKey(labels);
        const current = this.values.get(key) || 0;
        this.values.set(key, current - value);
        this.timestamps.set(key, Date.now());
    }
    /**
     * Get all values
     */
    getValues() {
        return Array.from(this.values.entries()).map(([key, value]) => ({
            value,
            labels: this.keyToLabels(key),
            timestamp: this.timestamps.get(key) || Date.now(),
        }));
    }
    /**
     * Reset gauge
     */
    reset() {
        this.values.clear();
        this.timestamps.clear();
    }
    labelsToKey(labels) {
        return Object.entries(labels)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([k, v]) => `${k}="${v}"`)
            .join(',');
    }
    keyToLabels(key) {
        if (!key)
            return {};
        const labels = {};
        key.split(',').forEach((pair) => {
            const match = pair.match(/^(.+)="(.+)"$/);
            if (match) {
                labels[match[1]] = match[2];
            }
        });
        return labels;
    }
}
/**
 * Histogram metric - distribution of values
 */
export class Histogram {
    name;
    help;
    buckets;
    unit;
    values = new Map();
    constructor(name, help, buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], unit) {
        this.name = name;
        this.help = help;
        this.buckets = buckets;
        this.unit = unit;
        // Sort buckets
        this.buckets = [...buckets].sort((a, b) => a - b);
    }
    /**
     * Observe a value
     */
    observe(value, labels = {}) {
        const key = this.labelsToKey(labels);
        let data = this.values.get(key);
        if (!data) {
            data = {
                count: 0,
                sum: 0,
                min: Infinity,
                max: -Infinity,
                buckets: new Map(this.buckets.map((b) => [b, 0])),
            };
            this.values.set(key, data);
        }
        data.count++;
        data.sum += value;
        data.min = Math.min(data.min, value);
        data.max = Math.max(data.max, value);
        // Update buckets
        for (const bucket of this.buckets) {
            if (value <= bucket) {
                data.buckets.set(bucket, (data.buckets.get(bucket) || 0) + 1);
            }
        }
    }
    /**
     * Get all values
     */
    getValues() {
        return Array.from(this.values.entries()).map(([key, data]) => ({
            count: data.count,
            sum: data.sum,
            min: data.min === Infinity ? 0 : data.min,
            max: data.max === -Infinity ? 0 : data.max,
            buckets: new Map(data.buckets),
            labels: this.keyToLabels(key),
        }));
    }
    /**
     * Start a timer
     */
    startTimer(labels = {}) {
        const start = process.hrtime.bigint();
        return () => {
            const elapsed = Number(process.hrtime.bigint() - start) / 1e9; // Convert to seconds
            this.observe(elapsed, labels);
            return elapsed;
        };
    }
    /**
     * Reset histogram
     */
    reset() {
        this.values.clear();
    }
    labelsToKey(labels) {
        return Object.entries(labels)
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([k, v]) => `${k}="${v}"`)
            .join(',');
    }
    keyToLabels(key) {
        if (!key)
            return {};
        const labels = {};
        key.split(',').forEach((pair) => {
            const match = pair.match(/^(.+)="(.+)"$/);
            if (match) {
                labels[match[1]] = match[2];
            }
        });
        return labels;
    }
}
// ============================================================================
// Metrics Collector
// ============================================================================
/**
 * Main metrics collector class
 */
export class MetricsCollector extends EventEmitter {
    config;
    counters = new Map();
    gauges = new Map();
    histograms = new Map();
    exportInterval = null;
    startTime = Date.now();
    history = [];
    initialized = false;
    // Pre-defined metrics
    requestsTotal;
    requestErrors;
    tokensUsed;
    toolExecutions;
    apiLatency;
    toolDuration;
    memoryUsage;
    activeConnections;
    activeSessions;
    constructor(config = {}) {
        super();
        this.config = {
            consoleExport: config.consoleExport ?? false,
            fileExport: config.fileExport ?? false,
            filePath: config.filePath ?? path.join(os.homedir(), '.codebuddy', 'metrics'),
            exportInterval: config.exportInterval ?? 60000, // 1 minute
            otelExport: config.otelExport ?? false,
            defaultBuckets: config.defaultBuckets ?? [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
            maxHistorySize: config.maxHistorySize ?? 1000,
        };
        // Initialize pre-defined metrics
        this.requestsTotal = this.createCounter('codebuddy_requests_total', 'Total number of requests processed');
        this.requestErrors = this.createCounter('codebuddy_request_errors_total', 'Total number of request errors');
        this.tokensUsed = this.createCounter('codebuddy_tokens_total', 'Total tokens used', 'tokens');
        this.toolExecutions = this.createCounter('codebuddy_tool_executions_total', 'Total tool executions');
        this.apiLatency = this.createHistogram('codebuddy_api_latency_seconds', 'API request latency in seconds', [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30], 'seconds');
        this.toolDuration = this.createHistogram('codebuddy_tool_duration_seconds', 'Tool execution duration in seconds', [0.01, 0.05, 0.1, 0.5, 1, 5, 10, 30, 60], 'seconds');
        this.memoryUsage = this.createGauge('codebuddy_memory_bytes', 'Memory usage in bytes', 'bytes');
        this.activeConnections = this.createGauge('codebuddy_active_connections', 'Number of active connections');
        this.activeSessions = this.createGauge('codebuddy_active_sessions', 'Number of active sessions');
    }
    /**
     * Initialize metrics collector
     */
    async init() {
        if (this.initialized)
            return;
        // Ensure metrics directory exists
        if (this.config.fileExport) {
            await fs.promises.mkdir(this.config.filePath, { recursive: true });
        }
        // Start export interval
        this.exportInterval = setInterval(() => {
            this.export().catch((err) => {
                logger.debug('Failed to export metrics', { error: err instanceof Error ? err.message : String(err) });
            });
        }, this.config.exportInterval);
        // Collect system metrics periodically
        setInterval(() => {
            this.collectSystemMetrics();
        }, 10000); // Every 10 seconds
        this.initialized = true;
        this.emit('initialized');
        logger.info('Metrics collector initialized');
    }
    /**
     * Create a counter metric
     */
    createCounter(name, help, unit) {
        const counter = new Counter(name, help, unit);
        this.counters.set(name, counter);
        return counter;
    }
    /**
     * Create a gauge metric
     */
    createGauge(name, help, unit) {
        const gauge = new Gauge(name, help, unit);
        this.gauges.set(name, gauge);
        return gauge;
    }
    /**
     * Create a histogram metric
     */
    createHistogram(name, help, buckets, unit) {
        const histogram = new Histogram(name, help, buckets || this.config.defaultBuckets, unit);
        this.histograms.set(name, histogram);
        return histogram;
    }
    /**
     * Get a counter by name
     */
    getCounter(name) {
        return this.counters.get(name);
    }
    /**
     * Get a gauge by name
     */
    getGauge(name) {
        return this.gauges.get(name);
    }
    /**
     * Get a histogram by name
     */
    getHistogram(name) {
        return this.histograms.get(name);
    }
    /**
     * Collect system metrics
     */
    collectSystemMetrics() {
        const mem = process.memoryUsage();
        this.memoryUsage.set(mem.heapUsed, { type: 'heap_used' });
        this.memoryUsage.set(mem.heapTotal, { type: 'heap_total' });
        this.memoryUsage.set(mem.external, { type: 'external' });
        this.memoryUsage.set(mem.rss, { type: 'rss' });
        this.memoryUsage.set(mem.arrayBuffers || 0, { type: 'array_buffers' });
    }
    /**
     * Get system metrics
     */
    getSystemMetrics() {
        const mem = process.memoryUsage();
        const cpu = process.cpuUsage();
        return {
            memory: {
                heapUsed: mem.heapUsed,
                heapTotal: mem.heapTotal,
                external: mem.external,
                rss: mem.rss,
                arrayBuffers: mem.arrayBuffers || 0,
            },
            cpu: {
                user: cpu.user,
                system: cpu.system,
            },
            uptime: Math.floor((Date.now() - this.startTime) / 1000),
            activeHandles: process._getActiveHandles?.()?.length || 0,
            activeRequests: process._getActiveRequests?.()?.length || 0,
        };
    }
    /**
     * Get current metrics snapshot
     */
    getSnapshot() {
        const counters = {};
        const gauges = {};
        const histograms = {};
        Array.from(this.counters.entries()).forEach(([name, counter]) => {
            counters[name] = counter.getValues();
        });
        Array.from(this.gauges.entries()).forEach(([name, gauge]) => {
            gauges[name] = gauge.getValues();
        });
        Array.from(this.histograms.entries()).forEach(([name, histogram]) => {
            histograms[name] = histogram.getValues();
        });
        return {
            timestamp: Date.now(),
            counters,
            gauges,
            histograms,
            system: this.getSystemMetrics(),
        };
    }
    /**
     * Export metrics in Prometheus format
     */
    toPrometheus() {
        const lines = [];
        // Export counters
        Array.from(this.counters.entries()).forEach(([name, counter]) => {
            lines.push(`# HELP ${name} ${counter.help}`);
            lines.push(`# TYPE ${name} counter`);
            counter.getValues().forEach((value) => {
                const labels = this.formatLabels(value.labels);
                lines.push(`${name}${labels} ${value.value}`);
            });
            lines.push('');
        });
        // Export gauges
        Array.from(this.gauges.entries()).forEach(([name, gauge]) => {
            lines.push(`# HELP ${name} ${gauge.help}`);
            lines.push(`# TYPE ${name} gauge`);
            gauge.getValues().forEach((value) => {
                const labels = this.formatLabels(value.labels);
                lines.push(`${name}${labels} ${value.value}`);
            });
            lines.push('');
        });
        // Export histograms
        Array.from(this.histograms.entries()).forEach(([name, histogram]) => {
            lines.push(`# HELP ${name} ${histogram.help}`);
            lines.push(`# TYPE ${name} histogram`);
            histogram.getValues().forEach((value) => {
                const labels = this.formatLabels(value.labels);
                // Export buckets
                Array.from(value.buckets.entries()).forEach(([bucket, count]) => {
                    const bucketLabels = { ...value.labels, le: String(bucket) };
                    lines.push(`${name}_bucket${this.formatLabels(bucketLabels)} ${count}`);
                });
                // +Inf bucket
                const infLabels = { ...value.labels, le: '+Inf' };
                lines.push(`${name}_bucket${this.formatLabels(infLabels)} ${value.count}`);
                // Sum and count
                lines.push(`${name}_sum${labels} ${value.sum}`);
                lines.push(`${name}_count${labels} ${value.count}`);
            });
            lines.push('');
        });
        // System metrics
        const system = this.getSystemMetrics();
        lines.push('# HELP codebuddy_process_uptime_seconds Process uptime in seconds');
        lines.push('# TYPE codebuddy_process_uptime_seconds gauge');
        lines.push(`codebuddy_process_uptime_seconds ${system.uptime}`);
        lines.push('');
        lines.push('# HELP codebuddy_process_cpu_user_seconds_total CPU user time');
        lines.push('# TYPE codebuddy_process_cpu_user_seconds_total counter');
        lines.push(`codebuddy_process_cpu_user_seconds_total ${system.cpu.user / 1e6}`);
        lines.push('');
        lines.push('# HELP codebuddy_process_cpu_system_seconds_total CPU system time');
        lines.push('# TYPE codebuddy_process_cpu_system_seconds_total counter');
        lines.push(`codebuddy_process_cpu_system_seconds_total ${system.cpu.system / 1e6}`);
        lines.push('');
        return lines.join('\n');
    }
    /**
     * Export metrics in JSON format
     */
    toJSON() {
        return this.getSnapshot();
    }
    /**
     * Format labels for Prometheus
     */
    formatLabels(labels) {
        const entries = Object.entries(labels);
        if (entries.length === 0)
            return '';
        const formatted = entries
            .map(([k, v]) => `${k}="${v.replace(/"/g, '\\"')}"`)
            .join(',');
        return `{${formatted}}`;
    }
    /**
     * Export metrics to configured destinations
     */
    async export() {
        const snapshot = this.getSnapshot();
        // Add to history
        this.history.push(snapshot);
        if (this.history.length > this.config.maxHistorySize) {
            this.history.shift();
        }
        // Console export
        if (this.config.consoleExport) {
            logger.debug('[METRICS]', {
                counters: Object.keys(snapshot.counters).length,
                gauges: Object.keys(snapshot.gauges).length,
                histograms: Object.keys(snapshot.histograms).length,
                uptime: snapshot.system.uptime,
            });
        }
        // File export
        if (this.config.fileExport) {
            await this.exportToFile(snapshot);
        }
        this.emit('export', snapshot);
    }
    /**
     * Export metrics to file
     */
    async exportToFile(snapshot) {
        const filename = `metrics-${new Date().toISOString().split('T')[0]}.jsonl`;
        const filepath = path.join(this.config.filePath, filename);
        try {
            await fs.promises.appendFile(filepath, JSON.stringify(snapshot) + '\n', 'utf-8');
        }
        catch (err) {
            logger.debug('Failed to write metrics to file', {
                error: err instanceof Error ? err.message : String(err),
            });
        }
    }
    /**
     * Get metrics history
     */
    getHistory(limit) {
        if (limit) {
            return this.history.slice(-limit);
        }
        return [...this.history];
    }
    /**
     * Reset all metrics
     */
    reset() {
        Array.from(this.counters.values()).forEach((counter) => {
            counter.reset();
        });
        Array.from(this.gauges.values()).forEach((gauge) => {
            gauge.reset();
        });
        Array.from(this.histograms.values()).forEach((histogram) => {
            histogram.reset();
        });
        this.history = [];
        this.emit('reset');
    }
    /**
     * Shutdown metrics collector
     */
    async shutdown() {
        if (this.exportInterval) {
            clearInterval(this.exportInterval);
            this.exportInterval = null;
        }
        // Final export
        await this.export();
        this.initialized = false;
        this.emit('shutdown');
    }
}
// ============================================================================
// Singleton Instance
// ============================================================================
let metricsInstance = null;
/**
 * Initialize metrics collector
 */
export function initMetrics(config) {
    if (!metricsInstance) {
        metricsInstance = new MetricsCollector(config);
        metricsInstance.init().catch((err) => {
            logger.warn('Failed to initialize metrics collector', {
                error: err instanceof Error ? err.message : String(err),
            });
        });
    }
    return metricsInstance;
}
/**
 * Get metrics collector instance
 */
export function getMetrics() {
    return metricsInstance;
}
/**
 * Helper: Measure function execution time
 */
export async function measureTime(histogramName, labels, fn) {
    if (!metricsInstance)
        return fn();
    const histogram = metricsInstance.getHistogram(histogramName);
    if (!histogram)
        return fn();
    const end = histogram.startTimer(labels);
    try {
        return await fn();
    }
    finally {
        end();
    }
}
/**
 * Helper: Increment counter
 */
export function incCounter(name, labels = {}, value = 1) {
    if (!metricsInstance)
        return;
    const counter = metricsInstance.getCounter(name);
    if (counter) {
        counter.inc(labels, value);
    }
}
/**
 * Helper: Set gauge value
 */
export function setGauge(name, value, labels = {}) {
    if (!metricsInstance)
        return;
    const gauge = metricsInstance.getGauge(name);
    if (gauge) {
        gauge.set(value, labels);
    }
}
export default MetricsCollector;
//# sourceMappingURL=metrics-collector.js.map