/**
 * OpenTelemetry Observability
 *
 * Provides metrics, tracing, and logging for Vaspera operations.
 * Disabled by default, enabled via VASPERA_OTEL_ENABLED=true.
 *
 * @module observability/otel
 */
// In-memory metrics storage (for when OTEL is disabled)
const inMemoryMetrics = {
    findings: new Map(),
    scanDurations: [],
    agentRuns: new Map(),
};
/**
 * Check if OpenTelemetry is enabled
 */
export function isOtelEnabled() {
    return process.env.VASPERA_OTEL_ENABLED === "true";
}
/**
 * Get the OTLP endpoint
 */
export function getOtlpEndpoint() {
    return process.env.VASPERA_OTEL_ENDPOINT || "http://localhost:4318";
}
/**
 * No-op span implementation when OTEL is disabled
 */
class NoOpSpan {
    context;
    constructor(name) {
        this.context = {
            traceId: generateId(32),
            spanId: generateId(16),
        };
    }
    setAttribute(_key, _value) {
        // No-op
    }
    recordException(_error) {
        // No-op
    }
    end() {
        // No-op
    }
    getContext() {
        return this.context;
    }
}
/**
 * No-op tracer implementation when OTEL is disabled
 */
class NoOpTracer {
    startSpan(name, _attributes) {
        return new NoOpSpan(name);
    }
    async withSpan(name, fn) {
        const span = this.startSpan(name);
        try {
            return await fn(span);
        }
        finally {
            span.end();
        }
    }
}
/**
 * Generate a random hex ID
 */
function generateId(length) {
    const bytes = new Uint8Array(length / 2);
    crypto.getRandomValues(bytes);
    return Array.from(bytes)
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");
}
/**
 * Create a no-op metrics implementation
 */
function createNoOpMetrics() {
    return {
        findingsTotal: (severity, scanner) => {
            const key = `${scanner}:${severity}`;
            inMemoryMetrics.findings.set(key, (inMemoryMetrics.findings.get(key) || 0) + 1);
        },
        scanDuration: (scanner, durationMs) => {
            inMemoryMetrics.scanDurations.push({ scanner, durationMs, timestamp: new Date() });
            // Keep only last 1000 entries
            if (inMemoryMetrics.scanDurations.length > 1000) {
                inMemoryMetrics.scanDurations.shift();
            }
        },
        certificationDuration: (_durationMs) => {
            // No-op for now
        },
        agentRuns: (agentType, status) => {
            const current = inMemoryMetrics.agentRuns.get(agentType) || { success: 0, failure: 0 };
            current[status]++;
            inMemoryMetrics.agentRuns.set(agentType, current);
        },
        circuitBreakerState: (_name, _state) => {
            // No-op
        },
        retryAttempts: (_operation, _attempt) => {
            // No-op
        },
    };
}
// Singleton instances
let metricsInstance = null;
let tracerInstance = null;
/**
 * Initialize OpenTelemetry (lazy initialization)
 *
 * When VASPERA_OTEL_ENABLED=true, this will:
 * 1. Initialize the OTEL SDK
 * 2. Set up OTLP exporter
 * 3. Register metrics and tracing providers
 *
 * When disabled (default), returns no-op implementations.
 */
export async function initializeOtel() {
    if (!isOtelEnabled()) {
        return;
    }
    // Dynamic import to avoid loading OTEL dependencies when disabled
    // Note: OpenTelemetry packages are optional. Install them for full observability:
    // npm install @opentelemetry/sdk-node @opentelemetry/api @opentelemetry/exporter-metrics-otlp-http @opentelemetry/exporter-trace-otlp-http @opentelemetry/sdk-metrics
    try {
        // Use Function constructor to hide imports from TypeScript's static analysis
        // This allows the code to compile even when @opentelemetry packages aren't installed
        const dynamicImport = new Function("specifier", "return import(specifier)");
        const otelApi = await dynamicImport("@opentelemetry/api");
        const { NodeSDK } = await dynamicImport("@opentelemetry/sdk-node");
        const { OTLPMetricExporter } = await dynamicImport("@opentelemetry/exporter-metrics-otlp-http");
        const { OTLPTraceExporter } = await dynamicImport("@opentelemetry/exporter-trace-otlp-http");
        const { PeriodicExportingMetricReader } = await dynamicImport("@opentelemetry/sdk-metrics");
        const endpoint = getOtlpEndpoint();
        const sdk = new NodeSDK({
            serviceName: "vaspera-hardening",
            metricReader: new PeriodicExportingMetricReader({
                exporter: new OTLPMetricExporter({ url: `${endpoint}/v1/metrics` }),
                exportIntervalMillis: 10000,
            }),
            traceExporter: new OTLPTraceExporter({ url: `${endpoint}/v1/traces` }),
        });
        sdk.start();
        // Create real metrics
        const meter = otelApi.metrics.getMeter("vaspera-hardening");
        const findingsCounter = meter.createCounter("vaspera_findings_total", {
            description: "Total number of findings by severity and scanner",
        });
        const scanDurationHistogram = meter.createHistogram("vaspera_scan_duration_ms", {
            description: "Scanner execution duration in milliseconds",
        });
        const certDurationHistogram = meter.createHistogram("vaspera_certification_duration_ms", {
            description: "Certification total duration in milliseconds",
        });
        const agentRunsCounter = meter.createCounter("vaspera_agent_runs_total", {
            description: "Total number of agent runs by type and status",
        });
        meter.createObservableGauge("vaspera_circuit_breaker_state", {
            description: "Circuit breaker state (0=closed, 1=half-open, 2=open)",
        });
        const retryCounter = meter.createCounter("vaspera_retry_attempts_total", {
            description: "Total number of retry attempts",
        });
        metricsInstance = {
            findingsTotal: (severity, scanner) => {
                findingsCounter.add(1, { severity, scanner });
            },
            scanDuration: (scanner, durationMs) => {
                scanDurationHistogram.record(durationMs, { scanner });
            },
            certificationDuration: (durationMs) => {
                certDurationHistogram.record(durationMs);
            },
            agentRuns: (agentType, status) => {
                agentRunsCounter.add(1, { agent_type: agentType, status });
            },
            circuitBreakerState: (_name, _state) => {
                // Observable gauge callback handles this
            },
            retryAttempts: (operation, attempt) => {
                retryCounter.add(1, { operation, attempt: attempt.toString() });
            },
        };
        // Create real tracer
        const tracer = otelApi.trace.getTracer("vaspera-hardening");
        tracerInstance = {
            startSpan: (name, attributes) => {
                const span = tracer.startSpan(name);
                if (attributes) {
                    Object.entries(attributes).forEach(([key, value]) => {
                        span.setAttribute(key, value);
                    });
                }
                return {
                    setAttribute: (key, value) => span.setAttribute(key, value),
                    recordException: (error) => span.recordException(error),
                    end: () => span.end(),
                    getContext: () => {
                        const ctx = span.spanContext();
                        return {
                            traceId: ctx.traceId,
                            spanId: ctx.spanId,
                        };
                    },
                };
            },
            withSpan: async (name, fn) => {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                return tracer.startActiveSpan(name, async (otelSpan) => {
                    const span = {
                        setAttribute: (key, value) => otelSpan.setAttribute(key, value),
                        recordException: (error) => otelSpan.recordException(error),
                        end: () => otelSpan.end(),
                        getContext: () => ({
                            traceId: otelSpan.spanContext().traceId,
                            spanId: otelSpan.spanContext().spanId,
                        }),
                    };
                    try {
                        return await fn(span);
                    }
                    finally {
                        otelSpan.end();
                    }
                });
            },
        };
        console.log(`[otel] Initialized with endpoint: ${endpoint}`);
    }
    catch (error) {
        // OpenTelemetry packages not installed or initialization failed
        // This is expected - OTEL is optional
        if (process.env.VASPERA_OTEL_DEBUG === "true") {
            console.warn("[otel] OpenTelemetry not available. Install @opentelemetry/* packages for observability.", error.message);
        }
        // Fall back to no-op
        metricsInstance = null;
        tracerInstance = null;
    }
}
/**
 * Get the metrics instance
 */
export function getMetrics() {
    if (!metricsInstance) {
        metricsInstance = createNoOpMetrics();
    }
    return metricsInstance;
}
/**
 * Get the tracer instance
 */
export function getTracer() {
    if (!tracerInstance) {
        tracerInstance = new NoOpTracer();
    }
    return tracerInstance;
}
/**
 * Get in-memory metrics stats (when OTEL is disabled)
 */
export function getInMemoryStats() {
    return {
        findings: Object.fromEntries(inMemoryMetrics.findings),
        recentScans: inMemoryMetrics.scanDurations.slice(-100),
        agentRuns: Object.fromEntries(inMemoryMetrics.agentRuns),
    };
}
/**
 * Reset in-memory metrics (for testing)
 */
export function resetInMemoryStats() {
    inMemoryMetrics.findings.clear();
    inMemoryMetrics.scanDurations.length = 0;
    inMemoryMetrics.agentRuns.clear();
}
//# sourceMappingURL=otel.js.map