/**
 * @fileoverview OpenTelemetry SDK initialization and lifecycle management.
 * Provides runtime-aware initialization with graceful degradation for Worker/Edge environments.
 * Supports both Node.js (full NodeSDK) and serverless runtimes (lightweight telemetry).
 * @module src/utils/telemetry/instrumentation
 */
import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
import { config } from '../../config/index.js';
import { runtimeCaps } from '../../utils/internal/runtime.js';
/**
 * The active OpenTelemetry `NodeSDK` instance, or `null` when telemetry is disabled,
 * the runtime is not Node/Bun, or the SDK has been shut down.
 * Node-specific SDK modules are lazy-loaded inside `initializeOpenTelemetry` to prevent
 * crashes in Worker/Edge environments where those modules are unavailable.
 */
export let sdk = null;
// Initialization state management
let isOtelInitialized = false;
let initializationPromise = null;
/**
 * Determines if the NodeSDK can be used in the current runtime.
 * Returns false in Worker/Edge environments — Cloudflare Workers with
 * `nodejs_compat` populate `process.versions.node`, so we must explicitly
 * check `isWorkerLike` to avoid the SDK loading `node:perf_hooks` and
 * `node:worker_threads` that the polyfill does not provide.
 * Bun is allowed — auto-instrumentations that rely on Node http hooks silently
 * no-op, but manual spans, custom metrics, and OTLP export all work correctly.
 */
function canUseNodeSDK() {
    return (runtimeCaps.isNode &&
        !runtimeCaps.isWorkerLike &&
        typeof process?.versions?.node === 'string' &&
        typeof process.env === 'object');
}
/**
 * Detects cloud platform and provider for resource attributes.
 * Enriches telemetry with deployment environment metadata.
 *
 * @returns Record of cloud-related resource attributes
 */
function detectCloudResource() {
    // cloud.* attrs remain in @opentelemetry/semantic-conventions/incubating, so
    // string literals are kept here. Stable attrs (service.*, deployment.*) are
    // applied at the resource construction site using their typed constants.
    const CLOUD_PROVIDER = 'cloud.provider';
    const CLOUD_PLATFORM = 'cloud.platform';
    const CLOUD_REGION = 'cloud.region';
    const attrs = {};
    // Cloudflare Workers
    if (runtimeCaps.isWorkerLike) {
        attrs[CLOUD_PROVIDER] = 'cloudflare';
        attrs[CLOUD_PLATFORM] = 'cloudflare_workers';
    }
    // AWS Lambda
    if (typeof process !== 'undefined' && process.env?.AWS_LAMBDA_FUNCTION_NAME) {
        attrs[CLOUD_PROVIDER] = 'aws';
        attrs[CLOUD_PLATFORM] = 'aws_lambda';
        if (process.env.AWS_REGION) {
            attrs[CLOUD_REGION] = process.env.AWS_REGION;
        }
    }
    // GCP Cloud Functions/Cloud Run
    if (typeof process !== 'undefined' && (process.env?.FUNCTION_TARGET || process.env?.K_SERVICE)) {
        attrs[CLOUD_PROVIDER] = 'gcp';
        attrs[CLOUD_PLATFORM] = process.env.FUNCTION_TARGET ? 'gcp_cloud_functions' : 'gcp_cloud_run';
        if (process.env.GCP_REGION) {
            attrs[CLOUD_REGION] = process.env.GCP_REGION;
        }
    }
    return attrs;
}
/**
 * Initializes the OpenTelemetry SDK with runtime-appropriate configuration.
 * Idempotent — safe to call multiple times; subsequent calls return the existing promise or resolve immediately.
 * No-ops silently when telemetry is disabled (`OTEL_ENABLED=false`) or when running in a
 * Worker/Edge environment where `NodeSDK` is unavailable.
 *
 * In Node/Bun environments, all SDK modules (`@opentelemetry/sdk-node`, exporters,
 * auto-instrumentations) are **lazy-loaded** via `Promise.all(import(...))` to prevent
 * Worker bundle failures.
 *
 * Configures:
 * - OTLP trace exporter + `BatchSpanProcessor` (when `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` is set)
 * - OTLP metrics exporter + `PeriodicExportingMetricReader` at 15 s intervals (when `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` is set)
 * - `TraceIdRatioBasedSampler` using `config.openTelemetry.samplingRatio`
 * - Node auto-instrumentations (HTTP enabled, FS disabled)
 * - Pino instrumentation that injects `trace_id`/`span_id` into log records
 * - Cloud resource attributes via `detectCloudResource()`
 *
 * @returns Promise that resolves when initialization is complete (or was already complete)
 * @throws Error if `NodeSDK.start()` or any lazy import fails; re-thrown after resetting `sdk` to `null`
 *
 * @example
 * ```typescript
 * // In application entry point (src/index.ts)
 * await initializeOpenTelemetry();
 * ```
 */
export async function initializeOpenTelemetry() {
    // Return existing promise if initialization in progress
    if (initializationPromise) {
        return await initializationPromise;
    }
    // Already initialized
    if (isOtelInitialized) {
        return;
    }
    initializationPromise = (async () => {
        if (!config.openTelemetry.enabled) {
            diag.info('OpenTelemetry disabled via configuration.');
            isOtelInitialized = true;
            return;
        }
        if (!canUseNodeSDK()) {
            diag.info('NodeSDK unavailable in this runtime. Using lightweight telemetry mode.');
            isOtelInitialized = true;
            return;
        }
        try {
            // Lazy-load Node-specific modules
            const [{ HttpInstrumentation }, { OTLPMetricExporter }, { OTLPTraceExporter }, { PinoInstrumentation }, { resourceFromAttributes }, { PeriodicExportingMetricReader }, { NodeSDK }, { BatchSpanProcessor, TraceIdRatioBasedSampler }, { ATTR_DEPLOYMENT_ENVIRONMENT_NAME, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION },] = await Promise.all([
                import('@opentelemetry/instrumentation-http'),
                import('@opentelemetry/exporter-metrics-otlp-http'),
                import('@opentelemetry/exporter-trace-otlp-http'),
                import('@opentelemetry/instrumentation-pino'),
                import('@opentelemetry/resources'),
                import('@opentelemetry/sdk-metrics'),
                import('@opentelemetry/sdk-node'),
                import('@opentelemetry/sdk-trace-node'),
                import('@opentelemetry/semantic-conventions'),
            ]);
            const otelLogLevelString = config.openTelemetry.logLevel.toUpperCase();
            const otelLogLevel = DiagLogLevel[otelLogLevelString] ?? DiagLogLevel.INFO;
            diag.setLogger(new DiagConsoleLogger(), otelLogLevel);
            const tracesEndpoint = config.openTelemetry.tracesEndpoint;
            const metricsEndpoint = config.openTelemetry.metricsEndpoint;
            if (!tracesEndpoint && !metricsEndpoint) {
                diag.warn('OTEL_ENABLED is true, but no OTLP endpoint for traces or metrics is configured. OpenTelemetry will not export any telemetry.');
            }
            const resource = resourceFromAttributes({
                [ATTR_SERVICE_NAME]: config.openTelemetry.serviceName,
                [ATTR_SERVICE_VERSION]: config.openTelemetry.serviceVersion,
                [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: config.environment,
                ...detectCloudResource(),
            });
            const spanProcessors = [];
            if (tracesEndpoint) {
                diag.info(`Using OTLP exporter for traces, endpoint: ${tracesEndpoint}`);
                const traceExporter = new OTLPTraceExporter({ url: tracesEndpoint });
                spanProcessors.push(new BatchSpanProcessor(traceExporter));
            }
            else {
                diag.info('No OTLP traces endpoint configured. Traces will not be exported.');
            }
            const metricReader = metricsEndpoint
                ? new PeriodicExportingMetricReader({
                    exporter: new OTLPMetricExporter({ url: metricsEndpoint }),
                    exportIntervalMillis: 15000,
                })
                : undefined;
            sdk = new NodeSDK({
                resource,
                spanProcessors,
                ...(metricReader && { metricReaders: [metricReader] }),
                sampler: new TraceIdRatioBasedSampler(config.openTelemetry.samplingRatio),
                instrumentations: [
                    new HttpInstrumentation({
                        ignoreIncomingRequestHook: (req) => req.url === '/healthz',
                    }),
                    new PinoInstrumentation({
                        logHook: (_span, record) => {
                            record.trace_id = _span.spanContext().traceId;
                            record.span_id = _span.spanContext().spanId;
                        },
                    }),
                ],
            });
            sdk.start();
            isOtelInitialized = true;
            diag.info(`OpenTelemetry NodeSDK initialized for ${config.openTelemetry.serviceName} v${config.openTelemetry.serviceVersion}`);
        }
        catch (error) {
            diag.error('Error initializing OpenTelemetry', error);
            sdk = null;
            isOtelInitialized = false;
            initializationPromise = null;
            throw error;
        }
    })();
    return initializationPromise;
}
/**
 * Gracefully shuts down the OpenTelemetry SDK with timeout protection.
 * This function is called during the application's shutdown sequence.
 * Prevents hung processes by racing shutdown against a timeout.
 *
 * @param timeoutMs - Maximum time to wait for shutdown in milliseconds (default: 5000)
 * @throws Error if shutdown times out or fails critically
 *
 * @example
 * ```typescript
 * // During application shutdown
 * try {
 *   await shutdownOpenTelemetry();
 * } catch (error) {
 *   console.error('Failed to shutdown telemetry:', error);
 * }
 * ```
 */
export async function shutdownOpenTelemetry(timeoutMs = 5000) {
    if (!sdk) {
        return;
    }
    try {
        const shutdownPromise = sdk.shutdown();
        // Attach a no-op catch so the promise doesn't become an unhandled rejection
        // if the timeout wins the race and sdk.shutdown() later rejects.
        shutdownPromise.catch(() => { });
        const { promise: timeoutPromise, reject } = Promise.withResolvers();
        const timer = setTimeout(() => reject(new Error('OpenTelemetry SDK shutdown timeout')), timeoutMs);
        await Promise.race([shutdownPromise, timeoutPromise]);
        clearTimeout(timer);
        diag.info('OpenTelemetry SDK terminated successfully.');
    }
    catch (error) {
        diag.error('Error terminating OpenTelemetry SDK', error);
        throw error; // Propagate for caller handling
    }
    finally {
        sdk = null;
        isOtelInitialized = false;
        initializationPromise = null;
    }
}
//# sourceMappingURL=instrumentation.js.map