"use strict";
// Minimal OTLP/HTTP-JSON trace exporter.
//
// Speaks the OpenTelemetry Protocol over HTTP using the JSON encoding
// (protobuf is the official default but JSON is documented as a valid
// alternative). Avoids the full @opentelemetry/* dependency tree so the
// harness keeps its "no new runtime deps" posture intact.
//
// Behaviour:
//   * Drains a queue at most every `flushIntervalMs`, or eagerly when the
//     queue exceeds `flushThreshold`.
//   * Drops to a circular buffer if the endpoint fails — never blocks the
//     RuntimeTracer producer.
//   * Survives a missing endpoint quietly: when no `endpoint` is set the
//     exporter is a no-op.
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOtlpExporter = createOtlpExporter;
exports.attachOtlpExporter = attachOtlpExporter;
const openinference_1 = require("./openinference");
const DEFAULT_FLUSH_INTERVAL_MS = 5_000;
const DEFAULT_FLUSH_THRESHOLD = 32;
const DEFAULT_QUEUE_LIMIT = 1024;
function createOtlpExporter(options = {}) {
    const endpoint = options.endpoint?.replace(/\/$/, '');
    const flushIntervalMs = options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
    const flushThreshold = options.flushThreshold ?? DEFAULT_FLUSH_THRESHOLD;
    const fetchImpl = options.fetchImpl ?? globalThis.fetch;
    const logger = options.logger;
    const traceIdHex = options.traceIdHex ?? '';
    const spanQueue = [];
    const eventQueue = [];
    let lastError;
    let lastSuccessAt;
    let timer = null;
    let stopped = false;
    function trim() {
        while (spanQueue.length > DEFAULT_QUEUE_LIMIT)
            spanQueue.shift();
        while (eventQueue.length > DEFAULT_QUEUE_LIMIT)
            eventQueue.shift();
    }
    function enqueueSpan(span) {
        if (!endpoint || stopped)
            return;
        spanQueue.push(span);
        trim();
        maybeFlushEarly();
    }
    function enqueueEvent(event) {
        if (!endpoint || stopped)
            return;
        eventQueue.push(event);
        trim();
        maybeFlushEarly();
    }
    function maybeFlushEarly() {
        if (spanQueue.length + eventQueue.length >= flushThreshold) {
            flush().catch(() => { });
        }
    }
    async function flush() {
        if (!endpoint)
            return { exported: 0, ok: true };
        if (!fetchImpl) {
            lastError = 'fetch is not available';
            return { exported: 0, ok: false, error: lastError };
        }
        if (spanQueue.length === 0 && eventQueue.length === 0) {
            return { exported: 0, ok: true };
        }
        const spans = spanQueue.splice(0, spanQueue.length);
        const events = eventQueue.splice(0, eventQueue.length);
        const payload = (0, openinference_1.buildOtlpPayload)({
            spans,
            events,
            traceIdHex: traceIdHex || mintFallbackTraceId(),
            serviceName: options.serviceName,
            serviceVersion: options.serviceVersion,
            resourceAttributes: options.resourceAttributes,
        });
        try {
            const response = await fetchImpl(`${endpoint}/v1/traces`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    ...(options.authorization ? { Authorization: options.authorization } : {}),
                },
                body: JSON.stringify(payload),
            });
            if (!response.ok) {
                lastError = `OTLP exporter HTTP ${response.status}`;
                // Re-queue so the next flush retries.
                spanQueue.unshift(...spans);
                eventQueue.unshift(...events);
                trim();
                logger?.warn?.(lastError);
                return { exported: 0, ok: false, error: lastError };
            }
            lastSuccessAt = new Date().toISOString();
            return { exported: spans.length + events.length, ok: true };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            lastError = `OTLP exporter failed: ${message}`;
            // Re-queue and rely on the timer to retry.
            spanQueue.unshift(...spans);
            eventQueue.unshift(...events);
            trim();
            logger?.warn?.(lastError);
            return { exported: 0, ok: false, error: lastError };
        }
    }
    async function shutdown() {
        stopped = true;
        if (timer) {
            clearInterval(timer);
            timer = null;
        }
        await flush().catch(() => { });
    }
    if (endpoint && flushIntervalMs > 0) {
        timer = setInterval(() => {
            flush().catch(() => { });
        }, flushIntervalMs);
        if (typeof timer.unref === 'function')
            timer.unref();
    }
    return {
        enqueueSpan,
        enqueueEvent,
        flush,
        shutdown,
        status: () => ({
            queued: spanQueue.length + eventQueue.length,
            lastError,
            lastSuccessAt,
        }),
    };
}
function mintFallbackTraceId() {
    const seed = `${process.pid}-${Date.now()}`;
    let hex = '';
    for (let i = 0; i < seed.length; i++) {
        hex += seed.charCodeAt(i).toString(16);
    }
    return hex.padEnd(32, '0').slice(0, 32);
}
function attachOtlpExporter(tracer, options = {}) {
    const exporter = options.exporter ?? createOtlpExporter(options);
    const originalStartSpan = tracer.startSpan.bind(tracer);
    const originalRecordEvent = tracer.recordEvent.bind(tracer);
    tracer.startSpan = (name, attributes) => {
        const span = originalStartSpan(name, attributes);
        const wrappedEnd = span.end;
        const wrappedFail = span.fail;
        span.end = (status, attributes) => {
            wrappedEnd(status, attributes);
            const recent = tracer.snapshot().spans.find((record) => record.id === span.id);
            if (recent)
                exporter.enqueueSpan(recent);
        };
        span.fail = (error, attributes) => {
            wrappedFail(error, attributes);
            const recent = tracer.snapshot().spans.find((record) => record.id === span.id);
            if (recent)
                exporter.enqueueSpan(recent);
        };
        return span;
    };
    tracer.recordEvent = (name, attributes) => {
        originalRecordEvent(name, attributes);
        const recent = tracer.snapshot().events.find((event) => event.name === name);
        if (recent)
            exporter.enqueueEvent(recent);
    };
    return {
        exporter,
        detach: async () => {
            tracer.startSpan = originalStartSpan;
            tracer.recordEvent = originalRecordEvent;
            await exporter.shutdown();
        },
    };
}
//# sourceMappingURL=otlpExporter.js.map