/*
 * Copyright (c) 2023-2025 Datalayer, Inc.
 * Distributed under the terms of the Modified BSD License.
 */
import { WebsocketProvider } from 'y-websocket';
import { URLExt } from '@jupyterlab/coreutils';
import { Signal } from '@lumino/signaling';
// Import CollaborationStatus enum separately since it's exported
export var CollaborationStatus;
(function (CollaborationStatus) {
    CollaborationStatus["Disconnected"] = "disconnected";
    CollaborationStatus["Connecting"] = "connecting";
    CollaborationStatus["Connected"] = "connected";
    CollaborationStatus["Error"] = "error";
})(CollaborationStatus || (CollaborationStatus = {}));
import { requestDatalayerCollaborationSessionId } from './DatalayerCollaboration.js';
/**
 * Datalayer collaboration provider
 *
 * This provider connects to Datalayer's collaboration service using WebSockets.
 */
export class DatalayerCollaborationProvider {
    type = 'datalayer';
    _status = CollaborationStatus.Disconnected;
    _provider = null;
    _sharedModel = null;
    _statusChanged = new Signal(this);
    _errorOccurred = new Signal(this);
    _syncStateChanged = new Signal(this);
    _isDisposed = false;
    _config;
    _onSync = null;
    _onConnectionClose = null;
    constructor(config) {
        this._config = config;
    }
    get status() {
        return this._status;
    }
    get isConnected() {
        return this._status === CollaborationStatus.Connected;
    }
    get isDisposed() {
        return this._isDisposed;
    }
    get events() {
        return {
            statusChanged: this._statusChanged,
            errorOccurred: this._errorOccurred,
            syncStateChanged: this._syncStateChanged,
        };
    }
    setStatus(status) {
        if (this._status !== status) {
            this._status = status;
            this._statusChanged.emit(status);
        }
    }
    async connect(sharedModel, documentId, options) {
        if (this.isConnected) {
            console.warn('Already connected to Datalayer collaboration service');
            return;
        }
        this.setStatus(CollaborationStatus.Connecting);
        try {
            // Use ONLY explicitly provided configuration
            // NO fallback to coreStore - if config is wrong, we want to fail fast
            // This is critical for VS Code extension where config must come from settings
            const runUrl = this._config.runUrl;
            const token = this._config.token;
            if (!runUrl) {
                throw new Error('Datalayer runUrl is not configured - must be explicitly provided in DatalayerCollaborationProvider config');
            }
            if (!token) {
                throw new Error('Datalayer token is not configured - must be explicitly provided in DatalayerCollaborationProvider config');
            }
            const { ydoc, awareness } = sharedModel;
            // Build URLs.
            const documentURL = URLExt.join(runUrl, '/api/spacer/v1/documents');
            const wsUrl = URLExt.join(runUrl, '/api/spacer/v1/documents/ws').replace(/^http/, 'ws');
            // Request collaboration session from Datalayer
            const sessionId = await requestDatalayerCollaborationSessionId({
                url: URLExt.join(documentURL, documentId),
                token,
                fetchFn: this._config.fetchFn,
            });
            // Create WebSocket provider
            this._provider = new WebsocketProvider(wsUrl, documentId, ydoc, {
                disableBc: true,
                params: {
                    sessionId,
                    token,
                },
                awareness,
                ...options,
            });
            this._sharedModel = sharedModel;
            // Set up event handlers
            this._onSync = (isSynced) => {
                this.handleSync(isSynced);
            };
            this._onConnectionClose = (event) => {
                if (event) {
                    this.handleConnectionClose(event);
                }
            };
            this._provider.on('sync', this._onSync);
            this._provider.on('connection-close', this._onConnectionClose);
            console.log('Connected to Datalayer collaboration service');
        }
        catch (error) {
            this.setStatus(CollaborationStatus.Error);
            this._errorOccurred.emit(error);
            throw error;
        }
    }
    disconnect() {
        if (this._provider) {
            if (this._onSync) {
                this._provider.off('sync', this._onSync);
            }
            if (this._onConnectionClose) {
                this._provider.off('connection-close', this._onConnectionClose);
            }
            this._provider.disconnect();
            this._provider = null;
        }
        this._sharedModel = null;
        this.setStatus(CollaborationStatus.Disconnected);
    }
    getProvider() {
        return this._provider;
    }
    getSharedModel() {
        return this._sharedModel;
    }
    handleConnectionClose(event) {
        console.warn('Collaboration connection closed:', event);
        this.setStatus(CollaborationStatus.Disconnected);
        // Handle session expiration (code 4002)
        if (event.code === 4002) {
            console.warn('Datalayer collaboration session expired');
            // Attempt to reconnect could be implemented here
        }
    }
    handleSync(isSynced) {
        this._syncStateChanged.emit(isSynced);
        if (isSynced) {
            this.setStatus(CollaborationStatus.Connected);
        }
    }
    dispose() {
        if (this._isDisposed) {
            return;
        }
        this.disconnect();
        // Signals don't need explicit disposal in Lumino
        // They are cleaned up when the object is garbage collected
        this._isDisposed = true;
    }
}
// Export the provider for direct instantiation
export default DatalayerCollaborationProvider;
