import { CONNECTED_COMMAND, createUndoManager, initLocalState, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncYjsChangesToLexical, TOGGLE_CONNECT_COMMAND, } from "@lexical/yjs";
import { $createParagraphNode, $getRoot, $getSelection, BLUR_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, REDO_COMMAND, UNDO_COMMAND, } from "lexical";
import { UndoManager } from "yjs";
import { mergeRegister } from "@lexical/utils";
import { Portal } from "solid-js/web";
import { createEffect, createMemo, on, onCleanup, onMount, } from "solid-js";
export function useYjsCollaboration(editor, id, provider, docMap, name, color, shouldBootstrap, binding, setDoc, cursorsContainerRef, initialEditorState, awarenessData, syncCursorPositionsFn = syncCursorPositions) {
    let isReloadingDoc = false;
    const connect = () => provider().connect();
    const disconnect = () => {
        try {
            provider().disconnect();
        }
        catch (e) {
            // Do nothing
        }
    };
    createEffect(on(binding, () => {
        const { root } = binding();
        const { awareness } = provider();
        const onStatus = ({ status }) => {
            editor.dispatchCommand(CONNECTED_COMMAND, status === "connected");
        };
        const onSync = (isSynced) => {
            if (shouldBootstrap &&
                isSynced &&
                root.isEmpty() &&
                root._xmlText._length === 0 &&
                isReloadingDoc === false) {
                initializeEditor(editor, initialEditorState);
            }
            isReloadingDoc = false;
        };
        const onAwarenessUpdate = () => {
            syncCursorPositionsFn(binding(), provider());
        };
        const onYjsTreeChanges = (
        // The below `any` type is taken directly from the vendor types for YJS.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        events, transaction) => {
            const origin = transaction.origin;
            if (origin !== binding()) {
                const isFromUndoManager = origin instanceof UndoManager;
                syncYjsChangesToLexical(binding(), provider(), events, isFromUndoManager, syncCursorPositionsFn);
            }
        };
        initLocalState(provider(), name, color, document.activeElement === editor.getRootElement(), awarenessData || {});
        const onProviderDocReload = (ydoc) => {
            clearEditorSkipCollab(editor, binding());
            setDoc(ydoc);
            docMap.set(id, ydoc);
            isReloadingDoc = true;
        };
        provider().on("reload", onProviderDocReload);
        provider().on("status", onStatus);
        provider().on("sync", onSync);
        awareness.on("update", onAwarenessUpdate);
        // This updates the local editor state when we receive updates from other clients
        root.getSharedType().observeDeep(onYjsTreeChanges);
        const removeListener = editor.registerUpdateListener(({ prevEditorState, editorState, dirtyLeaves, dirtyElements, normalizedNodes, tags, }) => {
            if (tags.has("skip-collab") === false) {
                syncLexicalUpdateToYjs(binding(), provider(), prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags);
            }
        });
        const connectionPromise = connect();
        onCleanup(() => {
            if (isReloadingDoc === false) {
                if (connectionPromise) {
                    connectionPromise.then(disconnect);
                }
                else {
                    disconnect();
                }
            }
            provider().off("sync", onSync);
            provider().off("status", onStatus);
            provider().off("reload", onProviderDocReload);
            awareness.off("update", onAwarenessUpdate);
            root.getSharedType().unobserveDeep(onYjsTreeChanges);
            docMap.delete(id);
            removeListener();
        });
    }));
    const cursorsContainer = createMemo(() => {
        const ref = (element) => {
            binding().cursorsContainer = element;
        };
        const mountPoint = (cursorsContainerRef && cursorsContainerRef()) || document.body;
        return (<Portal mount={mountPoint}>
        <div ref={ref}/>
      </Portal>);
    });
    onCleanup(editor.registerCommand(TOGGLE_CONNECT_COMMAND, (payload) => {
        const shouldConnect = payload;
        if (shouldConnect) {
            // eslint-disable-next-line no-console
            console.log("Collaboration connected!");
            connect();
        }
        else {
            // eslint-disable-next-line no-console
            console.log("Collaboration disconnected!");
            disconnect();
        }
        return true;
    }, COMMAND_PRIORITY_EDITOR));
    return cursorsContainer;
}
export function useYjsFocusTracking(editor, provider, name, color, awarenessData) {
    onCleanup(mergeRegister(editor.registerCommand(FOCUS_COMMAND, () => {
        setLocalStateFocus(provider(), name, color, true, awarenessData || {});
        return false;
    }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(BLUR_COMMAND, () => {
        setLocalStateFocus(provider(), name, color, false, awarenessData || {});
        return false;
    }, COMMAND_PRIORITY_EDITOR)));
}
export function useYjsHistory(editor, binding) {
    const undoManager = createMemo(() => createUndoManager(binding(), binding().root.getSharedType()));
    onMount(() => {
        const undo = () => {
            undoManager().undo();
        };
        const redo = () => {
            undoManager().redo();
        };
        onCleanup(mergeRegister(editor.registerCommand(UNDO_COMMAND, () => {
            undo();
            return true;
        }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(REDO_COMMAND, () => {
            redo();
            return true;
        }, COMMAND_PRIORITY_EDITOR)));
    });
    const clearHistory = () => {
        undoManager().clear();
    };
    // Exposing undo and redo states
    createEffect(() => {
        const updateUndoRedoStates = () => {
            editor.dispatchCommand(CAN_UNDO_COMMAND, undoManager().undoStack.length > 0);
            editor.dispatchCommand(CAN_REDO_COMMAND, undoManager().redoStack.length > 0);
        };
        undoManager().on("stack-item-added", updateUndoRedoStates);
        undoManager().on("stack-item-popped", updateUndoRedoStates);
        undoManager().on("stack-cleared", updateUndoRedoStates);
        onCleanup(() => {
            undoManager().off("stack-item-added", updateUndoRedoStates);
            undoManager().off("stack-item-popped", updateUndoRedoStates);
            undoManager().off("stack-cleared", updateUndoRedoStates);
        });
    });
    return clearHistory;
}
function initializeEditor(editor, initialEditorState) {
    editor.update(() => {
        const root = $getRoot();
        if (root.isEmpty()) {
            if (initialEditorState) {
                switch (typeof initialEditorState) {
                    case "string": {
                        const parsedEditorState = editor.parseEditorState(initialEditorState);
                        editor.setEditorState(parsedEditorState, {
                            tag: "history-merge",
                        });
                        break;
                    }
                    case "object": {
                        editor.setEditorState(initialEditorState, {
                            tag: "history-merge",
                        });
                        break;
                    }
                    case "function": {
                        editor.update(() => {
                            const root1 = $getRoot();
                            if (root1.isEmpty()) {
                                initialEditorState(editor);
                            }
                        }, { tag: "history-merge" });
                        break;
                    }
                }
            }
            else {
                const paragraph = $createParagraphNode();
                root.append(paragraph);
                const { activeElement } = document;
                if ($getSelection() !== null ||
                    (activeElement !== null &&
                        activeElement === editor.getRootElement())) {
                    paragraph.select();
                }
            }
        }
    }, {
        tag: "history-merge",
    });
}
function clearEditorSkipCollab(editor, binding) {
    // reset editor state
    editor.update(() => {
        const root = $getRoot();
        root.clear();
        root.select();
    }, {
        tag: "skip-collab",
    });
    if (binding.cursors == null) {
        return;
    }
    const cursors = binding.cursors;
    if (cursors == null) {
        return;
    }
    const cursorsContainer = binding.cursorsContainer;
    if (cursorsContainer == null) {
        return;
    }
    // reset cursors in dom
    const cursorsArr = Array.from(cursors.values());
    for (let i = 0; i < cursorsArr.length; i++) {
        const cursor = cursorsArr[i];
        const selection = cursor.selection;
        if (selection && selection.selections != null) {
            const selections = selection.selections;
            for (let j = 0; j < selections.length; j++) {
                cursorsContainer.removeChild(selections[i]);
            }
        }
    }
}
