/**
 * Observability Manager
 * Integrations for Prometheus, OpenTelemetry, and Datadog
 * Sealed namespace for immutability
 */
import { ErrorFactory, Logger, generateUuid } from '../../../src/index.js';
// Internal state
let config = null;
// Prometheus state
let promClient = null;
let promRegistry = null;
const promMetrics = new Map();
// OpenTelemetry state
let otelTracer = null;
const activeSpans = new Map();
let spanSweepInterval = null;
const MAX_ACTIVE_SPANS = 1000;
const SPAN_TTL_MS = 5 * 60 * 1000;
const isUnrefableTimer = (value) => {
    if (typeof value !== 'object' || value === null)
        return false;
    return 'unref' in value && typeof value.unref === 'function';
};
const cleanupStaleSpans = () => {
    const now = Date.now();
    for (const [spanId, entry] of activeSpans.entries()) {
        if (now - entry.startedAt > SPAN_TTL_MS) {
            entry.span.end();
            activeSpans.delete(spanId);
        }
    }
};
const evictOldestSpan = () => {
    if (activeSpans.size < MAX_ACTIVE_SPANS)
        return;
    let oldestId = null;
    let oldestTime = Number.POSITIVE_INFINITY;
    for (const [spanId, entry] of activeSpans.entries()) {
        if (entry.startedAt < oldestTime) {
            oldestTime = entry.startedAt;
            oldestId = spanId;
        }
    }
    if (oldestId !== null) {
        const entry = activeSpans.get(oldestId);
        if (entry) {
            entry.span.end();
        }
        activeSpans.delete(oldestId);
    }
};
// Datadog state
let datadogClient = null;
/**
 * Helper: Lazy load Prometheus client
 */
const getPrometheusClient = async () => {
    promClient ??= await import('prom-client');
    return promClient;
};
/**
 * Helper: Lazy load OpenTelemetry API
 */
const getOpenTelemetryApi = async () => {
    return import('@opentelemetry/api');
};
/**
 * Helper: Initialize Prometheus
 */
const initPrometheus = async (promConfig) => {
    if (!promConfig.enabled)
        return;
    try {
        const client = await getPrometheusClient();
        promRegistry = new client.Registry();
        // Set default labels if provided
        if (promConfig.defaultLabels) {
            promRegistry.setDefaultLabels(promConfig.defaultLabels);
        }
        // Enable default metrics (process, Node.js metrics)
        client.collectDefaultMetrics({ register: promRegistry });
        Logger.info('Prometheus metrics initialized', {
            port: promConfig.port,
            path: promConfig.path ?? '/metrics',
        });
    }
    catch (error) {
        Logger.error('Failed to initialize Prometheus', error);
        throw error;
    }
};
/**
 * Helper: Initialize OpenTelemetry
 */
const initOpenTelemetry = async (otelConfig) => {
    if (!otelConfig.enabled)
        return;
    try {
        const api = await getOpenTelemetryApi();
        otelTracer = api.trace.getTracer(otelConfig.serviceName);
        Logger.info('OpenTelemetry tracing initialized', {
            serviceName: otelConfig.serviceName,
            sampleRate: otelConfig.sampleRate ?? 1,
        });
    }
    catch (error) {
        Logger.error('Failed to initialize OpenTelemetry', error);
        // Don't throw - allow app to continue without tracing
    }
};
/**
 * Helper: Initialize Datadog
 */
const initDatadog = async (ddConfig) => {
    if (!ddConfig.enabled)
        return;
    try {
        const module = (await import('hot-shots'));
        const StatsDClass = module.StatsD;
        if (!StatsDClass) {
            Logger.warn('Datadog StatsD client unavailable');
            return;
        }
        datadogClient = new StatsDClass({
            host: ddConfig.host ?? 'localhost',
            port: ddConfig.port ?? 8125,
            prefix: ddConfig.prefix ?? 'worker.',
            globalTags: ddConfig.tags ?? [],
        });
        Logger.info('Datadog StatsD initialized', {
            host: ddConfig.host ?? 'localhost',
            port: ddConfig.port ?? 8125,
        });
    }
    catch (error) {
        Logger.error('Failed to initialize Datadog', error);
        // Don't throw - allow app to continue without Datadog
    }
};
/**
 * Observability Manager - Sealed namespace
 */
export const Observability = Object.freeze({
    /**
     * Initialize observability with configuration
     */
    async initialize(observabilityConfig) {
        if (config) {
            Logger.warn('Observability already initialized');
            return;
        }
        config = observabilityConfig;
        // Initialize all enabled platforms
        await Promise.all([
            initPrometheus(config.prometheus),
            initOpenTelemetry(config.openTelemetry),
            initDatadog(config.datadog),
        ]);
        if (config.openTelemetry.enabled === true && spanSweepInterval === null) {
            spanSweepInterval = setInterval(() => {
                cleanupStaleSpans();
            }, SPAN_TTL_MS);
            if (isUnrefableTimer(spanSweepInterval)) {
                spanSweepInterval.unref();
            }
        }
        Logger.info('Observability initialized', {
            prometheus: config.prometheus.enabled,
            openTelemetry: config.openTelemetry.enabled,
            datadog: config.datadog.enabled,
        });
    },
    /**
     * Register a metric
     */
    async registerMetric(definition) {
        if (config?.prometheus.enabled !== true || !promRegistry) {
            return;
        }
        if (promMetrics.has(definition.name)) {
            Logger.debug(`Metric already registered: ${definition.name}`);
            return;
        }
        const client = await getPrometheusClient();
        let metric;
        switch (definition.type) {
            case 'counter':
                metric = new client.Counter({
                    name: definition.name,
                    help: definition.help,
                    labelNames: definition.labels ?? [],
                    registers: [promRegistry],
                });
                break;
            case 'gauge':
                metric = new client.Gauge({
                    name: definition.name,
                    help: definition.help,
                    labelNames: definition.labels ?? [],
                    registers: [promRegistry],
                });
                break;
            case 'histogram':
                metric = new client.Histogram({
                    name: definition.name,
                    help: definition.help,
                    labelNames: definition.labels ?? [],
                    registers: [promRegistry],
                });
                break;
            case 'summary':
                metric = new client.Summary({
                    name: definition.name,
                    help: definition.help,
                    labelNames: definition.labels ?? [],
                    registers: [promRegistry],
                });
                break;
        }
        promMetrics.set(definition.name, metric);
        Logger.debug(`Metric registered: ${definition.name} (${definition.type})`);
    },
    /**
     * Increment a counter
     */
    incrementCounter(name, value = 1, labels) {
        // Prometheus
        if (config?.prometheus.enabled === true && promMetrics.has(name)) {
            const metric = promMetrics.get(name);
            if (labels) {
                metric.inc(labels, value);
            }
            else {
                metric.inc(value);
            }
        }
        // Datadog
        if (config?.datadog.enabled === true && datadogClient !== null) {
            const tags = labels ? Object.entries(labels).map(([k, v]) => `${k}:${v}`) : [];
            datadogClient.increment(name, value, tags);
        }
    },
    /**
     * Set a gauge value
     */
    setGauge(name, value, labels) {
        // Prometheus
        if (config?.prometheus.enabled === true && promMetrics.has(name)) {
            const metric = promMetrics.get(name);
            if (labels) {
                metric.set(labels, value);
            }
            else {
                metric.set(value);
            }
        }
        // Datadog
        if (config?.datadog.enabled === true && datadogClient !== null) {
            const tags = labels ? Object.entries(labels).map(([k, v]) => `${k}:${v}`) : [];
            datadogClient.gauge(name, value, tags);
        }
    },
    /**
     * Record a histogram observation
     */
    recordHistogram(name, value, labels) {
        // Prometheus
        if (config?.prometheus.enabled === true && promMetrics.has(name)) {
            const metric = promMetrics.get(name);
            if (labels) {
                metric.observe(labels, value);
            }
            else {
                metric.observe(value);
            }
        }
        // Datadog
        if (config?.datadog.enabled === true && datadogClient !== null) {
            const tags = labels ? Object.entries(labels).map(([k, v]) => `${k}:${v}`) : [];
            datadogClient.histogram(name, value, tags);
        }
    },
    /**
     * Record timing (histogram for duration)
     */
    recordTiming(name, durationMs, labels) {
        // Prometheus (convert to seconds)
        if (config?.prometheus.enabled === true && promMetrics.has(name)) {
            const metric = promMetrics.get(name);
            if (labels) {
                metric.observe(labels, durationMs / 1000);
            }
            else {
                metric.observe(durationMs / 1000);
            }
        }
        // Datadog (milliseconds)
        if (config?.datadog.enabled === true && datadogClient !== null) {
            const tags = labels ? Object.entries(labels).map(([k, v]) => `${k}:${v}`) : [];
            datadogClient.timing(name, durationMs, tags);
        }
    },
    /**
     * Start a span (OpenTelemetry)
     */
    startSpan(name, options) {
        if (config?.openTelemetry.enabled !== true || otelTracer === null) {
            return null;
        }
        try {
            const spanOptions = {
                attributes: options?.attributes ?? {},
            };
            const span = otelTracer.startSpan(name, spanOptions);
            const spanId = `${name}-${Date.now()}-${generateUuid()}`;
            evictOldestSpan();
            activeSpans.set(spanId, { span, startedAt: Date.now() });
            Logger.debug(`Span started: ${name}`, { spanId });
            return spanId;
        }
        catch (error) {
            Logger.error('Failed to start span', error);
            return null;
        }
    },
    /**
     * End a span
     */
    endSpan(spanId, attributes) {
        if (config?.openTelemetry.enabled !== true) {
            return;
        }
        try {
            const entry = activeSpans.get(spanId);
            if (!entry)
                return;
            if (attributes) {
                entry.span.setAttributes(attributes);
            }
            entry.span.end();
            activeSpans.delete(spanId);
            Logger.debug(`Span ended: ${spanId}`);
        }
        catch (error) {
            Logger.error('Failed to end span', error);
        }
    },
    /**
     * Record an error on a span
     */
    recordSpanError(spanId, error) {
        if (config?.openTelemetry.enabled !== true) {
            return;
        }
        try {
            const entry = activeSpans.get(spanId);
            if (!entry)
                return;
            entry.span.recordException(error);
            entry.span.setStatus({ code: 2, message: error.message }); // ERROR status
            Logger.debug(`Span error recorded: ${spanId}`, { error: error.message });
        }
        catch (err) {
            Logger.error('Failed to record span error', err);
        }
    },
    /**
     * Add event to span
     */
    addSpanEvent(spanId, name, attributes) {
        if (config?.openTelemetry.enabled !== true) {
            return;
        }
        try {
            const entry = activeSpans.get(spanId);
            if (!entry)
                return;
            entry.span.addEvent(name, attributes);
            Logger.debug(`Span event added: ${spanId}/${name}`);
        }
        catch (error) {
            Logger.error('Failed to add span event', error);
        }
    },
    /**
     * Get Prometheus metrics (for HTTP endpoint)
     */
    async getPrometheusMetrics() {
        if (config?.prometheus.enabled !== true || !promRegistry) {
            throw ErrorFactory.createGeneralError('Prometheus metrics not enabled');
        }
        return promRegistry.metrics();
    },
    /**
     * Get Prometheus registry (for advanced usage)
     */
    getPrometheusRegistry() {
        return promRegistry;
    },
    /**
     * Get Datadog client (for advanced usage)
     */
    getDatadogClient() {
        return datadogClient;
    },
    /**
     * Get OpenTelemetry tracer (for advanced usage)
     */
    getTracer() {
        return otelTracer;
    },
    /**
     * Record worker job metrics
     */
    recordJobMetrics(workerName, jobName, metrics) {
        const labels = { worker: workerName, job: jobName };
        if (metrics.processed !== undefined) {
            Observability.incrementCounter('worker_jobs_processed_total', metrics.processed, labels);
        }
        if (metrics.failed !== undefined) {
            Observability.incrementCounter('worker_jobs_failed_total', metrics.failed, labels);
        }
        if (metrics.durationMs !== undefined) {
            Observability.recordTiming('worker_job_duration_seconds', metrics.durationMs, labels);
        }
        if (metrics.queueSize !== undefined) {
            Observability.setGauge('worker_queue_size', metrics.queueSize, labels);
        }
    },
    /**
     * Record worker resource metrics
     */
    recordResourceMetrics(workerName, resources) {
        const labels = { worker: workerName };
        if (resources.cpuUsage !== undefined) {
            Observability.setGauge('worker_cpu_usage_percent', resources.cpuUsage, labels);
        }
        if (resources.memoryUsage !== undefined) {
            Observability.setGauge('worker_memory_usage_bytes', resources.memoryUsage, labels);
        }
        if (resources.activeJobs !== undefined) {
            Observability.setGauge('worker_active_jobs', resources.activeJobs, labels);
        }
    },
    /**
     * Create a traced function wrapper
     */
    traced(name, fn, options) {
        return (async (...args) => {
            const spanId = Observability.startSpan(name, options);
            try {
                const result = await fn(...args);
                if (spanId !== null) {
                    Observability.endSpan(spanId, { success: true });
                }
                return result;
            }
            catch (error) {
                if (spanId !== null) {
                    Observability.recordSpanError(spanId, error);
                    Observability.endSpan(spanId, { success: false });
                }
                throw error;
            }
        });
    },
    /**
     * Get configuration
     */
    getConfig() {
        return config ? { ...config } : null;
    },
    /**
     * Check if observability is enabled
     */
    isEnabled() {
        return (config !== null &&
            (config.prometheus.enabled || config.openTelemetry.enabled || config.datadog.enabled));
    },
    /**
     * Clear metrics for a specific worker
     */
    async clearWorkerMetrics(workerName) {
        if (config?.prometheus.enabled !== true) {
            return;
        }
        const metrics = Array.from(promMetrics.values());
        await Promise.all(metrics.map(async (metric) => {
            try {
                // Access internal values to find matching labels
                // This relies on prom-client get() method returning values with labels
                const item = await metric.get();
                const values = item.values ?? [];
                for (const val of values) {
                    const labels = val.labels;
                    if (labels?.['worker'] === workerName) {
                        metric.remove(labels);
                    }
                }
            }
            catch (err) {
                Logger.debug('Failed to clear worker metric labels', err);
            }
        }));
        Logger.debug(`Cleared metrics for worker: ${workerName}`);
    },
    /**
     * Shutdown
     */
    shutdown() {
        Logger.info('Observability shutting down...');
        // Close Datadog client
        if (datadogClient !== null) {
            datadogClient.close(() => {
                Logger.debug('Datadog client closed');
            });
            datadogClient = null;
        }
        // End all active spans
        for (const [spanId, entry] of activeSpans.entries()) {
            entry.span.end();
            Logger.debug(`Span force-ended: ${spanId}`);
        }
        activeSpans.clear();
        if (spanSweepInterval) {
            clearInterval(spanSweepInterval);
            spanSweepInterval = null;
        }
        // Clear metrics
        promMetrics.clear();
        promRegistry = null;
        promClient = null;
        otelTracer = null;
        config = null;
        Logger.info('Observability shutdown complete');
    },
});
// Graceful shutdown handled by WorkerShutdown
