import { resourceFromAttributes } from '@opentelemetry/resources';
import { W3CTraceContextPropagator } from '@opentelemetry/core';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import * as SemanticAttributes from '@opentelemetry/semantic-conventions';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { SessionType, SessionRecorderSdk, SessionRecorderIdGenerator, SessionRecorderBrowserTraceExporter, SessionRecorderTraceIdRatioBasedSampler, ATTR_MULTIPLAYER_SESSION_ID, MULTIPLAYER_TRACE_CLIENT_ID_LENGTH, } from '@multiplayer-app/session-recorder-common';
import { getExporterEndpoint } from './helpers';
import { getInstrumentations } from './instrumentations';
import { CrashBufferSpanProcessor } from './CrashBufferSpanProcessor';
const clientIdGenerator = SessionRecorderSdk.getIdGenerator(MULTIPLAYER_TRACE_CLIENT_ID_LENGTH);
export class TracerBrowserSDK {
    constructor() {
        this.clientId = '';
        this.sessionId = '';
        this.globalErrorListenersRegistered = false;
    }
    _setSessionId(sessionId, sessionType = SessionType.MANUAL) {
        this.sessionId = sessionId;
        if (!this.idGenerator) {
            throw new Error('Id generator not initialized');
        }
        this.idGenerator.setSessionId(sessionId, sessionType, this.clientId);
    }
    init(options) {
        this.config = options;
        this.clientId = clientIdGenerator();
        const { application, version, environment } = this.config;
        this.idGenerator = new SessionRecorderIdGenerator();
        this._setSessionId('', SessionType.SESSION_CACHE);
        this.exporter = new SessionRecorderBrowserTraceExporter({
            apiKey: options.apiKey,
            url: getExporterEndpoint(options.exporterEndpoint),
            usePostMessageFallback: options.usePostMessageFallback,
        });
        const resourceAttributes = resourceFromAttributes({
            [SemanticAttributes.SEMRESATTRS_SERVICE_NAME]: application,
            [SemanticAttributes.SEMRESATTRS_SERVICE_VERSION]: version,
            [SemanticAttributes.SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: environment,
        });
        SessionRecorderSdk.setResourceAttributes(resourceAttributes.attributes);
        this.tracerProvider = new WebTracerProvider({
            resource: resourceAttributes,
            idGenerator: this.idGenerator,
            sampler: new SessionRecorderTraceIdRatioBasedSampler(this.config.sampleTraceRatio),
            spanProcessors: [
                this._getSpanSessionIdProcessor(),
                new BatchSpanProcessor(this.exporter),
                new CrashBufferSpanProcessor(this.crashBuffer, this.exporter.serializeSpan),
            ],
        });
        this.tracerProvider.register({
            propagator: new W3CTraceContextPropagator(),
        });
        registerInstrumentations({
            tracerProvider: this.tracerProvider,
            instrumentations: getInstrumentations(this.config),
        });
        this._registerGlobalErrorListeners();
    }
    setCrashBuffer(crashBuffer) {
        this.crashBuffer = crashBuffer;
    }
    start(sessionId, sessionType) {
        if (!this.tracerProvider) {
            throw new Error('Configuration not initialized. Call init() before start().');
        }
        this._setSessionId(sessionId, sessionType);
    }
    stop() {
        if (!this.tracerProvider) {
            throw new Error('Configuration not initialized. Call init() before start().');
        }
        this._setSessionId('', SessionType.SESSION_CACHE);
    }
    setApiKey(apiKey) {
        if (!this.exporter) {
            throw new Error('Configuration not initialized. Call init() before setApiKey().');
        }
        this.exporter.setApiKey(apiKey);
    }
    async exportTraces(spans) {
        if (!this.exporter) {
            throw new Error('Trace exporter not initialized');
        }
        if (!spans || spans.length === 0) {
            return Promise.resolve();
        }
        const readableSpans = spans.map((s) => TracerBrowserSDK._toReadableSpanLike(s));
        return new Promise((resolve) => {
            var _a;
            (_a = this.exporter) === null || _a === void 0 ? void 0 : _a.exportBuffer(readableSpans, (result) => {
                resolve(result);
            });
        });
    }
    /**
     * Capture an exception as an error span/event.
     * If there is an active span, the exception will be recorded on it.
     * Otherwise, a short-lived span will be created to hold the exception event.
     */
    captureException(error, errorInfo) {
        SessionRecorderSdk.captureException(error, errorInfo);
    }
    _getSpanSessionIdProcessor() {
        return {
            onStart: (span) => {
                if (this.sessionId) {
                    span.setAttribute(ATTR_MULTIPLAYER_SESSION_ID, this.sessionId);
                }
            },
            onEnd: () => { },
            shutdown: () => Promise.resolve(),
            forceFlush: () => Promise.resolve(),
        };
    }
    _registerGlobalErrorListeners() {
        if (this.globalErrorListenersRegistered)
            return;
        if (typeof window === 'undefined')
            return;
        const errorHandler = (event) => {
            const err = (event === null || event === void 0 ? void 0 : event.error) instanceof Error ? event.error : new Error((event === null || event === void 0 ? void 0 : event.message) || 'Script error');
            this.captureException(err);
        };
        const rejectionHandler = (event) => {
            const reason = event && 'reason' in event ? event.reason : undefined;
            const err = reason instanceof Error
                ? reason
                : new Error(typeof reason === 'string' ? reason : 'Unhandled promise rejection');
            this.captureException(err);
        };
        window.addEventListener('error', errorHandler);
        window.addEventListener('unhandledrejection', rejectionHandler);
        this.globalErrorListenersRegistered = true;
    }
    static _toReadableSpanLike(span) {
        var _a;
        if (span && typeof span.spanContext === 'function' && span.instrumentationScope) {
            return span;
        }
        const spanContext = typeof (span === null || span === void 0 ? void 0 : span.spanContext) === 'function' ? span.spanContext() : span === null || span === void 0 ? void 0 : span._spanContext;
        const normalizedCtx = spanContext ||
            {
                traceId: span === null || span === void 0 ? void 0 : span.traceId,
                spanId: span === null || span === void 0 ? void 0 : span.spanId,
                traceFlags: span === null || span === void 0 ? void 0 : span.traceFlags,
                traceState: span === null || span === void 0 ? void 0 : span.traceState,
            };
        const instrumentationScope = (span === null || span === void 0 ? void 0 : span.instrumentationScope) ||
            (span === null || span === void 0 ? void 0 : span.instrumentationLibrary) ||
            { name: 'multiplayer-buffer', version: undefined, schemaUrl: undefined };
        const normalizedScope = {
            name: (instrumentationScope === null || instrumentationScope === void 0 ? void 0 : instrumentationScope.name) || 'multiplayer-buffer',
            version: instrumentationScope === null || instrumentationScope === void 0 ? void 0 : instrumentationScope.version,
            schemaUrl: instrumentationScope === null || instrumentationScope === void 0 ? void 0 : instrumentationScope.schemaUrl,
        };
        const resource = (span === null || span === void 0 ? void 0 : span.resource) || { attributes: {}, asyncAttributesPending: false };
        const parentSpanId = span === null || span === void 0 ? void 0 : span.parentSpanId;
        return {
            name: (span === null || span === void 0 ? void 0 : span.name) || '',
            kind: span === null || span === void 0 ? void 0 : span.kind,
            spanContext: () => normalizedCtx,
            parentSpanContext: parentSpanId
                ? {
                    traceId: normalizedCtx === null || normalizedCtx === void 0 ? void 0 : normalizedCtx.traceId,
                    spanId: parentSpanId,
                    traceFlags: normalizedCtx === null || normalizedCtx === void 0 ? void 0 : normalizedCtx.traceFlags,
                    traceState: normalizedCtx === null || normalizedCtx === void 0 ? void 0 : normalizedCtx.traceState,
                }
                : undefined,
            startTime: span === null || span === void 0 ? void 0 : span.startTime,
            endTime: (_a = span === null || span === void 0 ? void 0 : span.endTime) !== null && _a !== void 0 ? _a : span === null || span === void 0 ? void 0 : span.startTime,
            duration: span === null || span === void 0 ? void 0 : span.duration,
            status: span === null || span === void 0 ? void 0 : span.status,
            attributes: (span === null || span === void 0 ? void 0 : span.attributes) || {},
            links: (span === null || span === void 0 ? void 0 : span.links) || [],
            events: (span === null || span === void 0 ? void 0 : span.events) || [],
            ended: typeof (span === null || span === void 0 ? void 0 : span.ended) === 'boolean' ? span.ended : true,
            droppedAttributesCount: (span === null || span === void 0 ? void 0 : span.droppedAttributesCount) || 0,
            droppedEventsCount: (span === null || span === void 0 ? void 0 : span.droppedEventsCount) || 0,
            droppedLinksCount: (span === null || span === void 0 ? void 0 : span.droppedLinksCount) || 0,
            resource,
            instrumentationScope: normalizedScope,
        };
    }
}
//# sourceMappingURL=index.js.map