import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { mergeRegister } from "@lexical/utils";
import { type LexicalEditor, SELECTION_CHANGE_COMMAND, TextNode } from "lexical";
import { useEffect } from "react";
import { $createFormattedTextMarkerNode, $createFormattedTextNode, $isFormattedTextMarkerNode, $isFormattedTextNode, FormattedTextMarkerNode, FormattedTextNode } from "../nodes/formatted_text";
import { type TextFormat, textFormats } from "../serialized_editor_content";

export function FormattedTextPlugin() {
    const [editor] = useLexicalComposerContext();

    useEffect(() => mergeRegister(
        // Create formatted text
        ...Object.keys(textFormats).map((format) =>
            editor.registerNodeTransform(TextNode, createFormattedTextNode(format as TextFormat, editor))
        ),
        // Remove formatted text marker nodes when not inside formatted text node
        editor.registerNodeTransform(FormattedTextMarkerNode, (node) => {
            const parent = node.getParent();
            if (!parent) return;

            if ($isFormattedTextNode(parent)) {
                const format = parent.getStyle();
                const markerChars = textFormats[format];
                if (node.getTextContent() === markerChars) return;
            }

            node.getChildren().reverse().forEach((child) => node.insertAfter(child));
            node.remove();
        }),
        // Remove formatted text nodes without matching markers
        editor.registerNodeTransform(FormattedTextNode, (node) => {
            const format = node.getStyle();
            const markerChars = textFormats[format];

            const firstMarker = node.getFirstChild();
            const lastMarker = node.getLastChild();
            if (
                !$isFormattedTextMarkerNode(firstMarker) ||
                !$isFormattedTextMarkerNode(lastMarker)
            ) {
                node.getChildren().reverse().forEach((child) => node.insertAfter(child));
                node.remove();
                return;
            }
            const firstMarkerContent = firstMarker.getTextContent();
            const lastMarkerContent = lastMarker.getTextContent();

            if (firstMarkerContent !== markerChars || lastMarkerContent !== markerChars) {
                node.getChildren().reverse().forEach((child) => node.insertAfter(child));
                node.remove();
                return;
            }
        }),
        // Remove formatted text nodes without content
        editor.registerNodeTransform(FormattedTextNode, (node) => {
            const format = node.getStyle();
            const formattedChars = textFormats[format];

            const content = node.getTextContent();
            if (content !== formattedChars + formattedChars) return;

            node.getChildren().reverse().forEach((child) => node.insertAfter(child));
            node.remove();
        }),
    ));

    return null;
}

const createFormattedTextNode = (format: TextFormat, editor: LexicalEditor) => (node: TextNode) => {
    const markerChars = textFormats[format];
    const markerCharsLen = markerChars.length;

    const content = node.getTextContent();

    // TODO: search in all text nodes within the parent block node
    const start = content.indexOf(markerChars);
    if (start === -1) return;
    const end = content.indexOf(markerChars, start + markerCharsLen);
    if (end === -1) return;
    if (start === end - markerCharsLen) return;

    const startIndex = start > 0 ? 1 : 0;
    const contentIndex = startIndex + 1;
    const endIndex = contentIndex + 1;

    const textNodes = node.splitText(start, start + markerCharsLen, end, end + markerCharsLen);

    const formattedTextNode = $createFormattedTextNode(format);
    textNodes[startIndex]!.insertBefore(formattedTextNode);


    const startMarker = $createFormattedTextMarkerNode();
    startMarker.append(textNodes[startIndex]!);

    const endMarker = $createFormattedTextMarkerNode();
    endMarker.append(textNodes[endIndex]!);

    formattedTextNode.append(startMarker, textNodes[contentIndex]!, endMarker);

    editor.dispatchCommand(SELECTION_CHANGE_COMMAND, undefined);
}
