import { TracerBrowserSDK } from './otel';
import { RecorderBrowserSDK } from './rrweb';
import { getStoredItem, setStoredItem, getNavigatorInfo, getFormattedDate, getTimeDifferenceInSeconds, isSessionActive } from './utils';
import { SessionState, } from './types';
import { BASE_CONFIG, SESSION_RESPONSE, SESSION_PROP_NAME, SESSION_ID_PROP_NAME, SESSION_TYPE_PROP_NAME, SESSION_STATE_PROP_NAME, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig, SESSION_AUTO_CREATED, SESSION_STOPPED_EVENT, SESSION_STARTED_EVENT } from './config';
import { setShouldRecordHttpData, setMaxCapturingHttpPayloadSize, } from './patch';
import { recorderEventBus } from './eventBus';
import { SessionWidget } from './sessionWidget';
import messagingService from './services/messaging.service';
import { ApiService } from './services/api.service';
import './index.scss';
import { SessionType } from '@multiplayer-app/session-recorder-common';
import { ContinuousRecordingSaveButtonState } from './sessionWidget/buttonStateConfigs';
import { Observable } from 'lib0/observable';
import { NavigationRecorder } from './navigation';
export class SessionRecorder extends Observable {
    get navigation() {
        return this._navigationRecorder.api;
    }
    get isInitialized() {
        return this._isInitialized;
    }
    get sessionId() {
        return this._sessionId;
    }
    set sessionId(sessionId) {
        this._sessionId = sessionId;
        setStoredItem(SESSION_ID_PROP_NAME, sessionId);
    }
    get sessionType() {
        return this._sessionType;
    }
    set sessionType(sessionType) {
        this._sessionType = sessionType;
        const continuousRecording = sessionType === SessionType.CONTINUOUS;
        this._sessionWidget.updateContinuousRecordingState(continuousRecording);
        messagingService.sendMessage('continuous-debugging', continuousRecording);
        setStoredItem(SESSION_TYPE_PROP_NAME, sessionType);
    }
    get continuousRecording() {
        return this.sessionType === SessionType.CONTINUOUS;
    }
    get sessionState() {
        return this._sessionState || SessionState.stopped;
    }
    set sessionState(state) {
        this._sessionState = state;
        this._sessionWidget.updateState(this._sessionState, this.continuousRecording);
        messagingService.sendMessage('state-change', this._sessionState);
        setStoredItem(SESSION_STATE_PROP_NAME, state);
        // Emit observable event to support React wrapper
        this.emit('state-change', [this._sessionState || SessionState.stopped, this.sessionType]);
    }
    get session() {
        return this._session;
    }
    set session(session) {
        this._session = session;
        setStoredItem(SESSION_PROP_NAME, this._session);
    }
    get sessionAttributes() {
        return this._sessionAttributes || {};
    }
    set sessionAttributes(attributes) {
        this._sessionAttributes = attributes;
    }
    get error() {
        return this._error;
    }
    set error(v) {
        this._error = v;
        this._sessionWidget.error = v;
        this.emit('error', [v]);
    }
    /**
     * Returns the HTML button element for the session widget's recorder button.
     *
     * This element is used to control the start/stop recording functionality in the session widget UI.
     *
     * @returns {HTMLButtonElement | null} The recorder button element from the session widget.
     */
    get sessionWidgetButtonElement() {
        return this._sessionWidget.recorderButton;
    }
    /**
     * Initialize debugger with default or custom configurations
     */
    constructor() {
        var _a;
        super();
        this._apiService = new ApiService();
        this._tracer = new TracerBrowserSDK();
        this._recorder = new RecorderBrowserSDK();
        this._sessionWidget = new SessionWidget();
        this._navigationRecorder = new NavigationRecorder();
        this._startRequestController = null;
        this._isInitialized = false;
        // Session ID and state are stored in localStorage
        this._sessionId = null;
        this._sessionType = SessionType.MANUAL;
        this._sessionState = null;
        this._session = null;
        this._sessionAttributes = null;
        /**
         * Error message getter and setter
         */
        this._error = '';
        // Safety: avoid accessing storage in SSR/non-browser environments
        const isBrowser = typeof window !== 'undefined';
        const sessionLocal = isBrowser ? getStoredItem(SESSION_PROP_NAME, true) : null;
        const sessionIdLocal = isBrowser ? getStoredItem(SESSION_ID_PROP_NAME) : null;
        const sessionStateLocal = isBrowser ? getStoredItem(SESSION_STATE_PROP_NAME) : null;
        const sessionTypeLocal = isBrowser ? getStoredItem(SESSION_TYPE_PROP_NAME) : null;
        if (isSessionActive(sessionLocal, sessionTypeLocal)) {
            this.session = sessionLocal;
            this.sessionId = sessionIdLocal;
            this.sessionType = sessionTypeLocal;
            this.sessionState = sessionStateLocal;
        }
        else {
            this.session = null;
            this.sessionId = null;
            this.sessionState = null;
            this.sessionType = SessionType.MANUAL;
        }
        this._configs = {
            ...BASE_CONFIG,
            apiKey: ((_a = this.session) === null || _a === void 0 ? void 0 : _a.tempApiKey) || '',
        };
    }
    /**
     * Initialize the session debugger
     * @param configs - custom configurations for session debugger
     */
    init(configs) {
        if (typeof window === 'undefined') {
            return;
        }
        this._configs = getSessionRecorderConfig({ ...this._configs, ...configs });
        this._isInitialized = true;
        this._checkOperation('init');
        setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize || DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE);
        setShouldRecordHttpData(this._configs.captureBody, this._configs.captureHeaders);
        this._tracer.init(this._configs);
        this._apiService.init(this._configs);
        this._sessionWidget.init(this._configs);
        this._navigationRecorder.init({
            enabled: this._configs.recordNavigation,
            application: this._configs.application,
            environment: this._configs.environment,
            version: this._configs.version,
        });
        if (this._configs.apiKey) {
            this._recorder.init(this._configs);
        }
        if (this.sessionId && (this.sessionState === SessionState.started || this.sessionState === SessionState.paused)) {
            this._start();
        }
        this._registerWidgetEvents();
        this._registerSessionLimitReach();
        this._registerSessionAutoCreation();
        messagingService.sendMessage('state-change', this.sessionState);
        // Emit init observable event
        this.emit('init', [this]);
    }
    /**
     * Save the continuous recording session
     */
    async save() {
        try {
            this._checkOperation('save');
            if (!this.continuousRecording || !this._configs.showContinuousRecording) {
                return;
            }
            this._sessionWidget.updateSaveContinuousDebugSessionState(ContinuousRecordingSaveButtonState.SAVING);
            const res = await this._apiService.saveContinuousDebugSession(this.sessionId, {
                sessionAttributes: this.sessionAttributes,
                resourceAttributes: getNavigatorInfo(),
                stoppedAt: this._recorder.stoppedAt,
                name: this.sessionAttributes.userName
                    ? `${this.sessionAttributes.userName}'s session on ${getFormattedDate(Date.now(), { month: 'short', day: 'numeric' })}`
                    : `Session on ${getFormattedDate(Date.now())}`,
            });
            this._sessionWidget.updateSaveContinuousDebugSessionState(ContinuousRecordingSaveButtonState.SAVED);
            const sessionUrl = res === null || res === void 0 ? void 0 : res.url;
            this._sessionWidget.showToast({
                type: 'success',
                message: 'Your session was saved',
                button: {
                    text: 'Open session', url: sessionUrl,
                },
            }, 5000);
            return res;
        }
        catch (error) {
            this.error = error.message;
            this._sessionWidget.updateSaveContinuousDebugSessionState(ContinuousRecordingSaveButtonState.ERROR);
        }
        finally {
            setTimeout(() => {
                this._sessionWidget.updateSaveContinuousDebugSessionState(ContinuousRecordingSaveButtonState.IDLE);
            }, 3000);
        }
    }
    /**
     * Start a new session
     * @param type - the type of session to start
     * @param session - the session to start
     */
    start(type = SessionType.MANUAL, session) {
        this._checkOperation('start');
        // If continuous recording is disabled, force plain mode
        if (type === SessionType.CONTINUOUS && !this._configs.showContinuousRecording) {
            type = SessionType.MANUAL;
        }
        this.sessionType = type;
        this._startRequestController = new AbortController();
        if (session) {
            this._setupSessionAndStart(session, true);
        }
        else {
            this._createSessionAndStart();
        }
    }
    /**
     * Stop the current session with an optional comment
     * @param comment - user-provided comment to include in session session attributes
     */
    async stop(comment) {
        try {
            this._checkOperation('stop');
            this._stop();
            if (this.continuousRecording) {
                await this._apiService.stopContinuousDebugSession(this.sessionId);
                this.sessionType = SessionType.MANUAL;
            }
            else {
                const request = {
                    sessionAttributes: { comment },
                    stoppedAt: this._recorder.stoppedAt,
                };
                const response = await this._apiService.stopSession(this.sessionId, request);
                recorderEventBus.emit(SESSION_RESPONSE, response);
            }
            this._clearSession();
        }
        catch (error) {
            this.error = error.message;
        }
    }
    /**
     * Pause the current session
     */
    async pause() {
        try {
            this._checkOperation('pause');
            this._pause();
        }
        catch (error) {
            this.error = error.message;
        }
    }
    /**
     * Resume the current session
     */
    async resume() {
        try {
            this._checkOperation('resume');
            this._resume();
        }
        catch (error) {
            this.error = error.message;
        }
    }
    /**
     * Cancel the current session
     */
    async cancel() {
        try {
            this._checkOperation('cancel');
            this._stop();
            if (this.continuousRecording) {
                await this._apiService.stopContinuousDebugSession(this.sessionId);
                this.sessionType = SessionType.MANUAL;
            }
            else {
                await this._apiService.cancelSession(this.sessionId);
            }
            this._clearSession();
        }
        catch (error) {
            this.error = error.message;
        }
    }
    /**
     * Set the session attributes
     * @param attributes - the attributes to set
    */
    setSessionAttributes(attributes) {
        this._sessionAttributes = attributes;
    }
    /**
     * Updates the button click handler in the library.
     * @param handler - A function that will be invoked when the button is clicked.
     *                  The function receives the click event as its parameter and
     *                  should return `false` to prevent the default button action,
     *                  or `true` (or nothing) to allow it.
    */
    set recordingButtonClickHandler(handler) {
        this._sessionWidget.buttonClickExternalHandler = handler;
    }
    /**
     * Capture an exception manually and send it as an error trace.
     */
    captureException(error, errorInfo) {
        try {
            const normalizedError = this._normalizeError(error);
            const normalizedErrorInfo = this._normalizeErrorInfo(errorInfo);
            this._tracer.captureException(normalizedError, normalizedErrorInfo);
        }
        catch (e) {
            this.error = (e === null || e === void 0 ? void 0 : e.message) || 'Failed to capture exception';
        }
    }
    /**
     * @description Check if session should be started/stopped automatically
     * @param {ISession} [sessionPayload]
     * @returns {Promise<void>}
     */
    async checkRemoteContinuousSession(sessionPayload) {
        this._checkOperation('autoStartRemoteContinuousSession');
        if (!this._configs.showContinuousRecording) {
            return;
        }
        const payload = {
            sessionAttributes: {
                ...this.sessionAttributes,
                ...((sessionPayload === null || sessionPayload === void 0 ? void 0 : sessionPayload.sessionAttributes) || {}),
            },
            resourceAttributes: {
                ...getNavigatorInfo(),
                ...((sessionPayload === null || sessionPayload === void 0 ? void 0 : sessionPayload.resourceAttributes) || {}),
            },
        };
        const { state } = await this._apiService.checkRemoteSession(payload);
        if (state == 'START') {
            if (this.sessionState !== SessionState.started) {
                await this.start(SessionType.CONTINUOUS);
            }
        }
        else if (state == 'STOP') {
            if (this.sessionState !== SessionState.stopped) {
                await this.stop();
            }
        }
    }
    /**
     * Register session widget event listeners for controlling session actions
     */
    _registerWidgetEvents() {
        this._sessionWidget.on('toggle', (state, comment) => {
            this.error = '';
            if (state) {
                this.start(SessionType.MANUAL);
            }
            else {
                this.stop(comment === null || comment === void 0 ? void 0 : comment.trim());
            }
        });
        this._sessionWidget.on('pause', () => {
            this.error = '';
            this.pause();
        });
        this._sessionWidget.on('resume', () => {
            this.error = '';
            this.resume();
        });
        this._sessionWidget.on('cancel', () => {
            this.error = '';
            this.cancel();
        });
        this._sessionWidget.on('continuous-debugging', (enabled) => {
            this.error = '';
            if (enabled) {
                this.start(SessionType.CONTINUOUS);
            }
            else {
                this.stop();
            }
        });
        this._sessionWidget.on('save', () => {
            this.error = '';
            this.save();
        });
    }
    /**
     * Register session limit reaching listeners for controlling session end
     */
    _registerSessionLimitReach() {
        recorderEventBus.on(SESSION_STOPPED_EVENT, () => {
            this._stop();
            this._clearSession();
            this._sessionWidget.handleUIReseting();
        });
    }
    /**
     * Register session auto creation listeners during continuous recording
     */
    _registerSessionAutoCreation() {
        recorderEventBus.on(SESSION_AUTO_CREATED, (payload) => {
            var _a;
            if (!(payload === null || payload === void 0 ? void 0 : payload.data))
                return;
            this._sessionWidget.showToast({
                type: 'success',
                message: 'Your session was auto-saved due to an error',
                button: {
                    text: 'Open session',
                    url: (_a = payload === null || payload === void 0 ? void 0 : payload.data) === null || _a === void 0 ? void 0 : _a.url,
                },
            }, 5000);
        });
    }
    /**
     * Create a new session and start it
     */
    async _createSessionAndStart() {
        var _a;
        const signal = (_a = this._startRequestController) === null || _a === void 0 ? void 0 : _a.signal;
        try {
            const payload = {
                sessionAttributes: this.sessionAttributes,
                resourceAttributes: getNavigatorInfo(),
                name: this.sessionAttributes.userName
                    ? `${this.sessionAttributes.userName}'s session on ${getFormattedDate(Date.now(), { month: 'short', day: 'numeric' })}`
                    : `Session on ${getFormattedDate(Date.now())}`,
            };
            const request = !this.continuousRecording ?
                payload : { debugSessionData: payload };
            const session = this.continuousRecording
                ? await this._apiService.startContinuousDebugSession(request, signal)
                : await this._apiService.startSession(request, signal);
            if (session) {
                session.sessionType = this.continuousRecording
                    ? SessionType.CONTINUOUS
                    : SessionType.MANUAL;
                this._setupSessionAndStart(session, false);
            }
        }
        catch (error) {
            this.error = error.message;
            if (this.continuousRecording) {
                this.sessionType = SessionType.MANUAL;
            }
        }
    }
    /**
     * Start tracing and recording for the session
     */
    _start() {
        var _a;
        this.sessionState = SessionState.started;
        this.sessionType = this.sessionType;
        this._tracer.start(this.sessionId, this.sessionType);
        this._recorder.start(this.sessionId, this.sessionType);
        this._navigationRecorder.start({ sessionId: this.sessionId, sessionType: this.sessionType, });
        if (this.session) {
            recorderEventBus.emit(SESSION_STARTED_EVENT, this.session);
            this._recorder.subscribeToSession(this.session);
            this._sessionWidget.seconds = getTimeDifferenceInSeconds((_a = this.session) === null || _a === void 0 ? void 0 : _a.startedAt);
        }
    }
    /**
     * Stop tracing and recording for the session
     */
    _stop() {
        this.sessionState = SessionState.stopped;
        this._tracer.stop();
        this._recorder.stop();
        this._navigationRecorder.stop();
    }
    /**
     * Pause the session tracing and recording
     */
    _pause() {
        this._tracer.stop();
        this._recorder.stop();
        this._navigationRecorder.pause();
        this.sessionState = SessionState.paused;
    }
    /**
     * Resume the session tracing and recording
     */
    _resume() {
        this._tracer.start(this.sessionId, this.sessionType);
        this._recorder.start(this.sessionId, this.sessionType);
        this._navigationRecorder.resume();
        this.sessionState = SessionState.started;
    }
    _setupSessionAndStart(session, configureExporters = true) {
        if (configureExporters && session.tempApiKey) {
            this._configs.apiKey = session.tempApiKey;
            this._recorder.init(this._configs);
            this._tracer.setApiKey(session.tempApiKey);
            this._apiService.updateConfigs({ apiKey: this._configs.apiKey });
        }
        this._setSession(session);
        this._start();
    }
    /**
     * Set the session ID in localStorage
     * @param sessionId - the session ID to set or clear
     */
    _setSession(session) {
        this.session = { ...session, startedAt: session.startedAt || new Date().toISOString() };
        this.sessionId = (session === null || session === void 0 ? void 0 : session.shortId) || (session === null || session === void 0 ? void 0 : session._id);
    }
    _clearSession() {
        this.session = null;
        this.sessionId = null;
        this.sessionState = SessionState.stopped;
    }
    /**
     * Check the operation validity based on the session state and action
     * @param action - action being checked ('init', 'start', 'stop', 'cancel', 'pause', 'resume')
     */
    _checkOperation(action, payload) {
        if (!this._isInitialized) {
            throw new Error('Configuration not initialized. Call init() before performing any actions.');
        }
        switch (action) {
            case 'start':
                if (this.sessionState === SessionState.started) {
                    throw new Error('Session is already started.');
                }
                break;
            case 'stop':
                if (this.sessionState !== SessionState.paused && this.sessionState !== SessionState.started) {
                    throw new Error('Cannot stop. Session is not currently started.');
                }
                break;
            case 'cancel':
                if (this.sessionState === SessionState.stopped) {
                    throw new Error('Cannot cancel. Session has already been stopped.');
                }
                break;
            case 'pause':
                if (this.sessionState !== SessionState.started) {
                    throw new Error('Cannot pause. Session is not running.');
                }
                break;
            case 'resume':
                if (this.sessionState !== SessionState.paused) {
                    throw new Error('Cannot resume. Session is not paused.');
                }
                break;
            case 'save':
                if (!this.continuousRecording) {
                    throw new Error('Cannot save continuous recording session. Continuous recording is not enabled.');
                }
                if (this.sessionState !== SessionState.started) {
                    throw new Error('Cannot save continuous recording session. Session is not started.');
                }
                break;
            case 'autoStartRemoteContinuousSession':
                if (this.sessionState !== SessionState.stopped) {
                    throw new Error('Cannot start remote continuous session. Session is not stopped.');
                }
                break;
        }
    }
    _normalizeError(error) {
        if (error instanceof Error)
            return error;
        if (typeof error === 'string')
            return new Error(error);
        try {
            return new Error(JSON.stringify(error));
        }
        catch (_e) {
            return new Error(String(error));
        }
    }
    _normalizeErrorInfo(errorInfo) {
        if (!errorInfo)
            return {};
        try {
            return JSON.parse(JSON.stringify(errorInfo));
        }
        catch (_e) {
            return { errorInfo: String(errorInfo) };
        }
    }
}
//# sourceMappingURL=sessionRecorder.js.map