import { openTreecrdtDb } from './open.js';
import { CommonWorkerSession, createCommonWorkerRpcHandlers, openedToRpcInitResult, } from './common-worker.js';
class SharedCommonWorkerSession extends CommonWorkerSession {
    constructor() {
        super(...arguments);
        this.storedConfig = null;
        this.initResult = null;
    }
    onAfterReset() {
        this.storedConfig = null;
        this.initResult = null;
    }
}
const ports = new Set();
const session = new SharedCommonWorkerSession();
const coreHandlers = createCommonWorkerRpcHandlers(session);
let callQueue = Promise.resolve();
const settleQueue = (promise) => promise.then(() => undefined, () => undefined);
function broadcastMaterialized(event, exclude) {
    if (event.changes.length === 0)
        return;
    for (const port of ports) {
        if (port === exclude)
            continue;
        port.postMessage({ type: 'materialized', event });
    }
}
self.onconnect = (ev) => {
    const port = ev.ports[0];
    if (!port)
        return;
    ports.add(port);
    port.onmessage = (message) => {
        const request = message.data;
        const respond = (ok, result, error) => {
            port.postMessage({ id: request.id, ok, result, error });
        };
        const run = callQueue.then(() => handleRequest(port, request));
        callQueue = settleQueue(run);
        run.then((result) => respond(true, result), (err) => respond(false, null, err instanceof Error ? err.message : String(err)));
    };
    port.start();
};
async function handleRequest(sourcePort, request) {
    if (request.method === 'init') {
        const [baseUrl, filename, storage, docId] = request.params;
        return (await init(baseUrl, filename, storage, docId));
    }
    if (request.method === 'broadcastMaterialized') {
        const [event] = request.params;
        broadcastMaterialized(event, sourcePort);
        return undefined;
    }
    if (request.method === 'close') {
        await close(sourcePort);
        return undefined;
    }
    if (request.method === 'drop') {
        await session.drop();
        return undefined;
    }
    const methodFn = coreHandlers[request.method];
    if (!methodFn)
        throw new Error(`unknown method: ${request.method}`);
    return (await methodFn(...(request.params ?? [])));
}
async function init(baseUrl, filename, storageParam, docId) {
    const requestedFilename = storageParam === 'opfs' ? (filename ?? '/treecrdt.db') : ':memory:';
    if (session.storedConfig && session.initResult) {
        const cfg = session.storedConfig;
        if (cfg.baseUrl !== baseUrl ||
            cfg.requestedFilename !== requestedFilename ||
            cfg.requestedStorage !== storageParam ||
            cfg.docId !== docId) {
            throw new Error('shared worker already initialized with a different TreeCRDT database');
        }
        return session.initResult;
    }
    const opened = await openTreecrdtDb({
        baseUrl,
        filename,
        storage: storageParam,
        docId,
        requireOpfs: false,
        opfsVfs: storageParam === 'opfs' ? 'any-context' : undefined,
        onMaterialized: (event) => broadcastMaterialized(event),
    });
    session.applyOpened(opened);
    session.storedConfig = { baseUrl, requestedFilename, requestedStorage: storageParam, docId };
    session.initResult = openedToRpcInitResult(opened);
    return session.initResult;
}
async function close(port) {
    ports.delete(port);
    if (ports.size > 0)
        return;
    await session.closeDbAndReset();
}
