export const SYNCULAR_CRDT_WEBVIEW_PROTOCOL = 'syncular.crdt.host.v1';
export const SYNCULAR_CRDT_WEBVIEW_REQUEST = 'syncular.crdt.host.request';
export const SYNCULAR_CRDT_WEBVIEW_RESPONSE = 'syncular.crdt.host.response';
export const SYNCULAR_CRDT_WEBVIEW_ROWS_CHANGED = 'syncular.crdt.host.rowsChanged';
export function createSyncularCrdtJsonTransport(options) {
    return {
        postMessage(message) {
            options.postJsonMessage(JSON.stringify(message));
        },
        addMessageListener(listener) {
            return options.addJsonMessageListener((message) => {
                try {
                    const parsed = JSON.parse(message);
                    if (isSyncularCrdtHostMessage(parsed)) {
                        listener(parsed);
                    }
                }
                catch (error) {
                    options.onInvalidMessage?.(error, message);
                }
            });
        },
    };
}
export function createSyncularCrdtWebViewHost(options) {
    const timeoutMs = options.timeoutMs ?? 15_000;
    const nextRequestId = options.requestId ?? randomRequestId;
    const pending = new Map();
    const rowsChangedListeners = new Set();
    let closed = false;
    const unsubscribe = options.transport.addMessageListener((message) => {
        if (!isSyncularCrdtHostMessage(message))
            return;
        if (message.type === SYNCULAR_CRDT_WEBVIEW_RESPONSE) {
            const request = pending.get(message.id);
            if (!request)
                return;
            pending.delete(message.id);
            if (request.timer != null)
                clearTimeout(request.timer);
            if (message.ok) {
                request.resolve(message.response);
            }
            else {
                request.reject(errorFromPayload(message.error));
            }
            return;
        }
        if (message.type === SYNCULAR_CRDT_WEBVIEW_ROWS_CHANGED) {
            for (const listener of rowsChangedListeners)
                listener(message.event);
        }
    });
    const request = (method, payload) => {
        if (closed) {
            return Promise.reject(new Error('Syncular CRDT WebView host is closed'));
        }
        const id = nextRequestId();
        const message = {
            protocol: SYNCULAR_CRDT_WEBVIEW_PROTOCOL,
            type: SYNCULAR_CRDT_WEBVIEW_REQUEST,
            id,
            method,
            request: payload,
        };
        return new Promise((resolve, reject) => {
            const timer = timeoutMs > 0
                ? setTimeout(() => {
                    pending.delete(id);
                    reject(new Error(`Syncular CRDT WebView host request timed out: ${method}`));
                }, timeoutMs)
                : undefined;
            pending.set(id, { resolve, reject, timer });
            try {
                options.transport.postMessage(message);
            }
            catch (error) {
                pending.delete(id);
                if (timer != null)
                    clearTimeout(timer);
                reject(error);
            }
        });
    };
    return {
        host: {
            openCrdtField(field) {
                return request('openCrdtField', field);
            },
            applyCrdtFieldYjsUpdate(update) {
                return request('applyCrdtFieldYjsUpdate', update);
            },
            enqueueCrdtFieldYjsUpdate(update) {
                return request('enqueueCrdtFieldYjsUpdate', update);
            },
            materializeCrdtField(field) {
                return request('materializeCrdtField', field);
            },
            crdtDocumentSnapshot(field) {
                return request('crdtDocumentSnapshot', field);
            },
            crdtUpdateLog(field) {
                return request('crdtUpdateLog', field);
            },
            snapshotCrdtFieldStateVector(field) {
                return request('snapshotCrdtFieldStateVector', field);
            },
            compactCrdtField(field) {
                return request('compactCrdtField', field);
            },
            addRowsChangedListener(listener) {
                rowsChangedListeners.add(listener);
                return () => {
                    rowsChangedListeners.delete(listener);
                };
            },
        },
        close() {
            if (closed)
                return;
            closed = true;
            unsubscribe();
            rowsChangedListeners.clear();
            for (const [id, pendingRequest] of pending) {
                pending.delete(id);
                if (pendingRequest.timer != null)
                    clearTimeout(pendingRequest.timer);
                pendingRequest.reject(new Error('Syncular CRDT WebView host closed before response'));
            }
        },
    };
}
export function createSyncularCrdtWebViewHostResponder(options) {
    let closed = false;
    const unsubscribeRequests = options.transport.addMessageListener((message) => {
        if (!isSyncularCrdtHostMessage(message))
            return;
        if (message.type !== SYNCULAR_CRDT_WEBVIEW_REQUEST)
            return;
        void createSyncularCrdtHostResponseMessage(options.host, message).then((response) => {
            options.transport.postMessage(response);
        });
    });
    const unsubscribeRowsChanged = options.publishRowsChanged === false
        ? undefined
        : options.host.addRowsChangedListener((event) => {
            if (closed)
                return;
            options.transport.postMessage(createSyncularCrdtRowsChangedMessage(event));
        });
    return {
        close() {
            if (closed)
                return;
            closed = true;
            unsubscribeRequests();
            unsubscribeRowsChanged?.();
        },
    };
}
export async function createSyncularCrdtHostResponseMessage(host, message) {
    try {
        const response = await dispatchSyncularCrdtHostRequest(host, message);
        return {
            protocol: SYNCULAR_CRDT_WEBVIEW_PROTOCOL,
            type: SYNCULAR_CRDT_WEBVIEW_RESPONSE,
            id: message.id,
            ok: true,
            response,
        };
    }
    catch (error) {
        return {
            protocol: SYNCULAR_CRDT_WEBVIEW_PROTOCOL,
            type: SYNCULAR_CRDT_WEBVIEW_RESPONSE,
            id: message.id,
            ok: false,
            error: errorPayload(error),
        };
    }
}
export function createSyncularCrdtRowsChangedMessage(event) {
    return {
        protocol: SYNCULAR_CRDT_WEBVIEW_PROTOCOL,
        type: SYNCULAR_CRDT_WEBVIEW_ROWS_CHANGED,
        event,
    };
}
export function syncularCrdtRowsChangedMessageFromNativeEvent(event) {
    if (!isNativeRowsChangedEventLike(event))
        return undefined;
    const changedRows = event.changedRows ?? event.payload_json?.changedRows ?? [];
    const changedTables = event.tables && event.tables.length > 0
        ? event.tables
        : Array.from(new Set(changedRows.map((row) => row.table)));
    return createSyncularCrdtRowsChangedMessage({
        source: event.payload_json?.source ?? 'native',
        changedTables,
        changedRows,
    });
}
export function syncularCrdtRowsChangedMessageFromNativeEventJson(eventJson) {
    return syncularCrdtRowsChangedMessageFromNativeEvent(JSON.parse(eventJson));
}
export function isSyncularCrdtHostMessage(message) {
    if (!isRecord(message))
        return false;
    if (message.protocol !== SYNCULAR_CRDT_WEBVIEW_PROTOCOL)
        return false;
    return (message.type === SYNCULAR_CRDT_WEBVIEW_REQUEST ||
        message.type === SYNCULAR_CRDT_WEBVIEW_RESPONSE ||
        message.type === SYNCULAR_CRDT_WEBVIEW_ROWS_CHANGED);
}
export function dispatchSyncularCrdtHostRequest(host, message) {
    switch (message.method) {
        case 'openCrdtField':
            return host.openCrdtField(message.request);
        case 'applyCrdtFieldYjsUpdate':
            return host.applyCrdtFieldYjsUpdate(message.request);
        case 'enqueueCrdtFieldYjsUpdate':
            if (host.enqueueCrdtFieldYjsUpdate) {
                return host.enqueueCrdtFieldYjsUpdate(message.request);
            }
            return host.applyCrdtFieldYjsUpdate(message.request).then((receipt) => {
                return receipt.clientCommitId;
            });
        case 'materializeCrdtField':
            return host.materializeCrdtField(message.request);
        case 'crdtDocumentSnapshot':
            if (!host.crdtDocumentSnapshot) {
                throw new Error('Host does not expose crdtDocumentSnapshot');
            }
            return host.crdtDocumentSnapshot(message.request);
        case 'crdtUpdateLog':
            if (!host.crdtUpdateLog) {
                throw new Error('Host does not expose crdtUpdateLog');
            }
            return host.crdtUpdateLog(message.request);
        case 'snapshotCrdtFieldStateVector':
            return host.snapshotCrdtFieldStateVector(message.request);
        case 'compactCrdtField':
            return host.compactCrdtField(message.request);
        default:
            return assertNever(message);
    }
}
function errorPayload(error) {
    if (error instanceof Error) {
        return {
            message: error.message,
            name: error.name,
        };
    }
    return {
        message: String(error),
    };
}
function errorFromPayload(payload) {
    const error = new Error(payload.message);
    error.name = payload.name ?? 'Error';
    return error;
}
function isRecord(value) {
    return typeof value === 'object' && value !== null;
}
function isNativeRowsChangedEventLike(event) {
    if (!isRecord(event))
        return false;
    if (event.kind !== 'RowsChanged')
        return false;
    return (Array.isArray(event.tables) ||
        Array.isArray(event.changedRows) ||
        (isRecord(event.payload_json) &&
            Array.isArray(event.payload_json.changedRows)));
}
function randomRequestId() {
    return `syncular-crdt-${Date.now().toString(36)}-${Math.random()
        .toString(36)
        .slice(2)}`;
}
function assertNever(value) {
    throw new Error(`Unhandled Syncular CRDT WebView method: ${String(value)}`);
}
//# sourceMappingURL=webview-host-facade.js.map