/**
 * Exporter Registry
 * Manages multiple observability exporters with circuit breaker protection
 */
import { logger } from "../utils/logger.js";
import { AlwaysSampler } from "./sampling/samplers.js";
/** Default timeout for exporter API calls (30 seconds) */
const DEFAULT_EXPORT_TIMEOUT_MS = 30_000;
/**
 * Wrap a promise with a timeout. Rejects with a descriptive error if the
 * promise does not settle within `timeoutMs` milliseconds.
 */
function withExportTimeout(promise, timeoutMs, label) {
    return new Promise((resolve, reject) => {
        const timer = setTimeout(() => {
            reject(new Error(`Export to '${label}' timed out after ${timeoutMs}ms`));
        }, timeoutMs);
        promise.then((v) => {
            clearTimeout(timer);
            resolve(v);
        }, (e) => {
            clearTimeout(timer);
            reject(e);
        });
    });
}
/**
 * Registry for managing multiple observability exporters
 * Includes circuit breaker protection to prevent cascading failures
 */
export class ExporterRegistry {
    exporters = new Map();
    defaultExporter = null;
    sampler = new AlwaysSampler();
    circuitBreakers = new Map();
    circuitBreakerConfig = {
        failureThreshold: 5,
        resetTimeout: 30000, // 30 seconds
    };
    /**
     * Register an exporter
     */
    register(exporter) {
        this.exporters.set(exporter.getName(), exporter);
    }
    /**
     * Unregister an exporter
     */
    unregister(name) {
        return this.exporters.delete(name);
    }
    /**
     * Get an exporter by name
     */
    get(name) {
        return this.exporters.get(name);
    }
    /**
     * Get all registered exporter names
     */
    getNames() {
        return Array.from(this.exporters.keys());
    }
    /**
     * Get total exporter count
     */
    getCount() {
        return this.exporters.size;
    }
    /**
     * Set the default exporter
     */
    setDefault(name) {
        if (!this.exporters.has(name)) {
            throw new Error(`Exporter '${name}' not registered`);
        }
        this.defaultExporter = name;
    }
    /**
     * Get the default exporter
     */
    getDefault() {
        if (!this.defaultExporter) {
            return undefined;
        }
        return this.exporters.get(this.defaultExporter);
    }
    /**
     * Set the sampler for the registry
     */
    setSampler(sampler) {
        this.sampler = sampler;
    }
    /**
     * Get the current sampler
     */
    getSampler() {
        return this.sampler;
    }
    /**
     * Configure the circuit breaker settings
     * @param config - Partial circuit breaker configuration
     */
    configureCircuitBreaker(config) {
        Object.assign(this.circuitBreakerConfig, config);
    }
    /**
     * Check if circuit is open for an exporter
     * @param exporterName - Name of the exporter
     * @returns true if circuit is open (exporter should be skipped)
     */
    isCircuitOpen(exporterName) {
        const breaker = this.circuitBreakers.get(exporterName);
        if (!breaker) {
            return false;
        }
        if (breaker.state === "open") {
            // Check if we should try half-open
            if (Date.now() - breaker.lastFailure >
                this.circuitBreakerConfig.resetTimeout) {
                breaker.state = "half-open";
                logger.info(`[ExporterRegistry] Circuit half-open for ${exporterName}, attempting recovery`);
                return false;
            }
            return true;
        }
        return false;
    }
    /**
     * Record a failure for an exporter's circuit breaker
     * @param exporterName - Name of the exporter
     */
    recordFailure(exporterName) {
        let breaker = this.circuitBreakers.get(exporterName);
        if (!breaker) {
            breaker = { failures: 0, lastFailure: 0, state: "closed" };
            this.circuitBreakers.set(exporterName, breaker);
        }
        breaker.failures++;
        breaker.lastFailure = Date.now();
        if (breaker.failures >= this.circuitBreakerConfig.failureThreshold) {
            breaker.state = "open";
            logger.warn(`[ExporterRegistry] Circuit opened for ${exporterName} after ${breaker.failures} failures`);
        }
    }
    /**
     * Record a success for an exporter's circuit breaker
     * Resets the circuit to closed state
     * @param exporterName - Name of the exporter
     */
    recordSuccess(exporterName) {
        const breaker = this.circuitBreakers.get(exporterName);
        if (breaker) {
            if (breaker.state === "half-open") {
                logger.info(`[ExporterRegistry] Circuit closed for ${exporterName} after successful recovery`);
            }
            breaker.failures = 0;
            breaker.state = "closed";
        }
    }
    /**
     * Get circuit breaker status for an exporter
     * @param exporterName - Name of the exporter
     * @returns Circuit breaker state or undefined if not tracked
     */
    getCircuitBreakerStatus(exporterName) {
        return this.circuitBreakers.get(exporterName);
    }
    /**
     * Reset circuit breaker for an exporter
     * @param exporterName - Name of the exporter
     */
    resetCircuitBreaker(exporterName) {
        this.circuitBreakers.delete(exporterName);
    }
    /**
     * Export span to all registered exporters
     * Applies sampling and circuit breaker protection before export
     */
    async exportToAll(span) {
        const results = new Map();
        // Apply sampling
        if (!this.sampler.shouldSample(span)) {
            // Return empty results if not sampled
            return results;
        }
        const exportPromises = Array.from(this.exporters.entries()).map(async ([name, exporter]) => {
            // Check circuit breaker before attempting export
            if (this.isCircuitOpen(name)) {
                results.set(name, {
                    success: false,
                    exportedCount: 0,
                    failedCount: 1,
                    errors: [
                        {
                            spanId: span.spanId,
                            error: "Circuit breaker open - exporter temporarily disabled",
                            retryable: true,
                        },
                    ],
                    durationMs: 0,
                });
                return;
            }
            if (exporter.isInitialized()) {
                try {
                    const result = await withExportTimeout(exporter.exportSpan(span), DEFAULT_EXPORT_TIMEOUT_MS, name);
                    results.set(name, result);
                    // Record success or failure based on result
                    if (result.success) {
                        this.recordSuccess(name);
                    }
                    else {
                        this.recordFailure(name);
                    }
                }
                catch (error) {
                    this.recordFailure(name);
                    results.set(name, {
                        success: false,
                        exportedCount: 0,
                        failedCount: 1,
                        errors: [
                            {
                                spanId: span.spanId,
                                error: error instanceof Error ? error.message : String(error),
                                retryable: true,
                            },
                        ],
                        durationMs: 0,
                    });
                }
            }
            else {
                logger.debug(`[ExporterRegistry] Skipping uninitialized exporter '${name}' for span ${span.spanId}`);
            }
        });
        await Promise.all(exportPromises);
        return results;
    }
    /**
     * Export span to a specific exporter
     * Applies sampling and circuit breaker protection
     */
    async exportTo(name, span) {
        const exporter = this.exporters.get(name);
        if (!exporter) {
            return null;
        }
        if (!exporter.isInitialized()) {
            logger.debug(`[ExporterRegistry] Skipping uninitialized exporter '${name}' for span ${span.spanId}`);
            return null;
        }
        // Check circuit breaker before attempting export
        if (this.isCircuitOpen(name)) {
            return {
                success: false,
                exportedCount: 0,
                failedCount: 1,
                errors: [
                    {
                        spanId: span.spanId,
                        error: "Circuit breaker open - exporter temporarily disabled",
                        retryable: true,
                    },
                ],
                durationMs: 0,
            };
        }
        // Apply sampling
        if (!this.sampler.shouldSample(span)) {
            return {
                success: true,
                exportedCount: 0,
                failedCount: 0,
                durationMs: 0,
            };
        }
        try {
            const result = await withExportTimeout(exporter.exportSpan(span), DEFAULT_EXPORT_TIMEOUT_MS, name);
            if (result.success) {
                this.recordSuccess(name);
            }
            else {
                this.recordFailure(name);
            }
            return result;
        }
        catch (error) {
            this.recordFailure(name);
            return {
                success: false,
                exportedCount: 0,
                failedCount: 1,
                errors: [
                    {
                        spanId: span.spanId,
                        error: error instanceof Error ? error.message : String(error),
                        retryable: true,
                    },
                ],
                durationMs: 0,
            };
        }
    }
    /**
     * Initialize all exporters
     */
    async initializeAll() {
        const results = await Promise.allSettled(Array.from(this.exporters.entries()).map(([name, e]) => e.initialize().catch((err) => {
            logger.error(`[ExporterRegistry] Failed to initialize exporter '${name}':`, err);
            throw err;
        })));
        for (const result of results) {
            if (result.status === "rejected") {
                logger.warn(`[ExporterRegistry] One or more exporters failed to initialize`);
                break;
            }
        }
    }
    /**
     * Shutdown all exporters
     */
    async shutdownAll() {
        const results = await Promise.allSettled(Array.from(this.exporters.entries()).map(([name, e]) => e.shutdown().catch((err) => {
            logger.error(`[ExporterRegistry] Failed to shutdown exporter '${name}':`, err);
            throw err;
        })));
        for (const result of results) {
            if (result.status === "rejected") {
                logger.warn(`[ExporterRegistry] One or more exporters failed to shutdown`);
                break;
            }
        }
    }
    /**
     * Flush all exporters
     */
    async flushAll() {
        const results = await Promise.allSettled(Array.from(this.exporters.entries()).map(([name, e]) => e.flush().catch((err) => {
            logger.error(`[ExporterRegistry] Failed to flush exporter '${name}':`, err);
            throw err;
        })));
        for (const result of results) {
            if (result.status === "rejected") {
                logger.warn(`[ExporterRegistry] One or more exporters failed to flush`);
                break;
            }
        }
    }
    /**
     * Get health status of all exporters
     */
    async healthCheckAll() {
        const results = new Map();
        const healthPromises = Array.from(this.exporters.entries()).map(async ([name, exporter]) => {
            const status = await exporter.healthCheck();
            results.set(name, status);
        });
        await Promise.all(healthPromises);
        return results;
    }
    /**
     * Check if all exporters are healthy
     */
    async isHealthy() {
        const statuses = await this.healthCheckAll();
        return Array.from(statuses.values()).every((s) => s.healthy);
    }
    /**
     * Get total pending spans across all exporters
     */
    getTotalPendingSpans() {
        let total = 0;
        const exporterArray = Array.from(this.exporters.values());
        for (const exporter of exporterArray) {
            total += exporter.getPendingCount();
        }
        return total;
    }
    /**
     * Clear all registered exporters and reset state
     * (For testing and cleanup)
     */
    clear() {
        this.exporters.clear();
        this.defaultExporter = null;
        this.circuitBreakers.clear();
        this.sampler = new AlwaysSampler();
    }
}
/**
 * Global exporter registry instance (singleton pattern from main)
 */
let globalRegistry = null;
/**
 * Get the global exporter registry instance
 */
export function getExporterRegistry() {
    if (!globalRegistry) {
        globalRegistry = new ExporterRegistry();
    }
    return globalRegistry;
}
/**
 * Reset the global exporter registry (for testing)
 */
export function resetExporterRegistry() {
    if (globalRegistry) {
        globalRegistry.clear();
    }
    globalRegistry = null;
}
//# sourceMappingURL=exporterRegistry.js.map