import * as Y from 'yjs';
const DEFAULT_REMOTE_ORIGIN = Symbol.for('syncular.crdt.remote');
export function createYjsProseMirrorBridge(options) {
    let doc = options.doc ?? new Y.Doc();
    let fragment = doc.getXmlFragment(options.containerKey);
    const remoteOrigin = options.remoteOrigin ?? DEFAULT_REMOTE_ORIGIN;
    const ignoredOrigins = new Set([
        remoteOrigin,
        ...(options.ignoredOrigins ?? []),
    ]);
    const localListeners = new Set();
    const docUnsubscribers = new Map();
    const attachListener = (listener) => {
        const handler = (update, origin) => {
            if (ignoredOrigins.has(origin))
                return;
            listener(update);
        };
        doc.on('update', handler);
        docUnsubscribers.set(listener, () => {
            doc.off('update', handler);
        });
    };
    const attachAllListeners = () => {
        for (const listener of localListeners)
            attachListener(listener);
    };
    const detachAllListeners = () => {
        for (const unsubscribe of docUnsubscribers.values())
            unsubscribe();
        docUnsubscribers.clear();
    };
    const replaceDoc = (nextDoc, receipt) => {
        detachAllListeners();
        doc.destroy();
        doc = nextDoc;
        fragment = doc.getXmlFragment(options.containerKey);
        attachAllListeners();
        options.onDocumentReplaced?.({ doc, fragment, receipt });
    };
    return {
        subscribeLocalUpdates(listener) {
            localListeners.add(listener);
            attachListener(listener);
            return () => {
                localListeners.delete(listener);
                docUnsubscribers.get(listener)?.();
                docUnsubscribers.delete(listener);
            };
        },
        applyRemoteUpdate(update) {
            Y.applyUpdate(doc, update, remoteOrigin);
        },
        replaceDocumentState(state, receipt) {
            this.replaceDocumentFromState(state, receipt);
        },
        replaceMaterializedValue() { },
        doc() {
            return doc;
        },
        fragment() {
            return fragment;
        },
        replaceDocumentFromState(state, receipt) {
            const nextDoc = new Y.Doc();
            if (state.length > 0) {
                Y.applyUpdate(nextDoc, state, remoteOrigin);
            }
            replaceDoc(nextDoc, receipt);
        },
        encodeStateAsUpdate() {
            return Y.encodeStateAsUpdate(doc);
        },
        destroy() {
            detachAllListeners();
            localListeners.clear();
            doc.destroy();
        },
    };
}
export function prosemirrorJsonProjection(value) {
    const text = extractProseMirrorText(value);
    const headings = extractProseMirrorHeadings(value);
    return {
        prosemirrorJson: value,
        title: headings[0] ?? firstLine(text) ?? '',
        preview: text.slice(0, 240),
        outline: headings,
        searchText: text,
    };
}
export function createProseMirrorReadModelProjection(options) {
    return {
        derive(materialization, event) {
            const derived = options.derive?.(materialization.value, event) ??
                prosemirrorJsonProjection(materialization.value);
            return {
                table: event.field.table,
                rowId: event.field.rowId,
                field: event.field.field,
                ...derived,
                stateVectorBase64: event.stateVectorBase64,
                source: event.source,
                reason: event.reason,
                operation: event.operation,
                commitId: event.commitId,
                commitSeq: event.commitSeq,
                serverVersion: event.serverVersion,
                documentKey: event.documentSnapshot?.documentKey,
                pendingUpdates: event.documentSnapshot?.pendingUpdates,
                flushedUpdates: event.documentSnapshot?.flushedUpdates,
                ackedUpdates: event.documentSnapshot?.ackedUpdates,
                logUpdates: event.documentSnapshot?.logUpdates,
                updatedAt: event.documentSnapshot?.updatedAt,
                compactedAt: event.documentSnapshot?.compactedAt,
                latestUpdateId: event.latestUpdate?.updateId,
                latestUpdateStatus: event.latestUpdate?.status,
            };
        },
        apply(model, event) {
            return options.store.upsert(model, event);
        },
    };
}
export function extractProseMirrorText(value) {
    const parts = [];
    const visit = (node) => {
        if (node == null || typeof node !== 'object')
            return;
        const record = node;
        if (typeof record.text === 'string') {
            parts.push(record.text);
        }
        const content = record.content;
        if (Array.isArray(content)) {
            for (const child of content)
                visit(child);
        }
    };
    visit(value);
    return parts.join(' ').replace(/\s+/g, ' ').trim();
}
export function extractProseMirrorHeadings(value) {
    const headings = [];
    const visit = (node) => {
        if (node == null || typeof node !== 'object')
            return;
        const record = node;
        if (record.type === 'heading') {
            const text = extractProseMirrorText(record);
            if (text !== '')
                headings.push(text);
        }
        const content = record.content;
        if (Array.isArray(content)) {
            for (const child of content)
                visit(child);
        }
    };
    visit(value);
    return headings;
}
function firstLine(value) {
    return value
        .split(/\r?\n/)
        .map((line) => line.trim())
        .find((line) => line !== '');
}
//# sourceMappingURL=yjs-prosemirror-bridge.js.map