import { Plugin } from "@html_editor/plugin";
import { closestElement, selectElements } from "@html_editor/utils/dom_traversal";
import { leftPos, rightPos } from "@html_editor/utils/position";
import { QWebPicker } from "./qweb_picker";
import { isElement } from "@html_editor/utils/dom_info";
import { withSequence } from "@html_editor/utils/resource";

const isUnsplittableQWebElement = (node) =>
    isElement(node) &&
    (node.tagName === "T" ||
        [
            "t-field",
            "t-if",
            "t-elif",
            "t-else",
            "t-foreach",
            "t-value",
            "t-esc",
            "t-out",
            "t-raw",
        ].some((attr) => node.getAttribute(attr)));

const PROTECTED_QWEB_SELECTOR = "[t-esc], [t-raw], [t-out], [t-field]";
const QWEB_DATA_ATTRIBUTES = [
    "data-oe-t-group",
    "data-oe-t-inline",
    "data-oe-t-selectable",
    "data-oe-t-group-active",
];
const dataAttributesSelector = QWEB_DATA_ATTRIBUTES.map((attr) => `[${attr}]`).join(", ");

export const isUnremovableQWebElement = (node) =>
    node.getAttribute?.("t-set") || node.getAttribute?.("t-call");

export class QWebPlugin extends Plugin {
    static id = "qweb";
    static dependencies = ["overlay", "protectedNode", "selection"];
    /** @type {import("plugins").EditorResources} */
    resources = {
        /** Handlers */
        selectionchange_handlers: withSequence(8, this.onSelectionChange.bind(this)),
        clean_for_save_handlers: ({ root }) => {
            this.clearDataAttributes(root);
            for (const element of root.querySelectorAll(PROTECTED_QWEB_SELECTOR)) {
                element.removeAttribute("contenteditable");
                delete element.dataset.oeProtected;
            }
        },
        normalize_handlers: withSequence(0, this.normalize.bind(this)),

        system_attributes: QWEB_DATA_ATTRIBUTES,
        unremovable_node_predicates: isUnremovableQWebElement,
        unsplittable_node_predicates: isUnsplittableQWebElement,
        clipboard_content_processors: this.clearDataAttributes.bind(this),
        legit_empty_link_predicates: (linkEl) =>
            linkEl.getAttributeNames().some((name) => name.startsWith("t-")),
    };

    setup() {
        this.editable.classList.add("odoo-editor-qweb");
        this.picker = this.dependencies.overlay.createOverlay(QWebPicker, {
            positionOptions: { position: "top-start" },
        });
        this.addDomListener(this.editable, "click", this.onClick);
        this.groupIndex = 0;
    }

    isValidTargetForDomListener(ev) {
        if (
            ev.type === "click" &&
            ev.target &&
            closestElement(ev.target, PROTECTED_QWEB_SELECTOR)
        ) {
            // Allow clicking on a protected QWEB node to open the custom toolbar.
            return true;
        }
        return super.isValidTargetForDomListener(ev);
    }

    /**
     * @param { SelectionData } selectionData
     */
    onSelectionChange() {
        if (this.picker.isOpen) {
            this.picker.close();
        }
    }

    normalize(root) {
        this.normalizeInline(root);

        for (const element of selectElements(root, PROTECTED_QWEB_SELECTOR)) {
            this.dependencies.protectedNode.setProtectingNode(element, true);
        }
        this.applyGroupQwebBranching(root);
    }

    checkAllInline(el) {
        return [...el.children].every((child) => {
            if (child.tagName === "T") {
                return this.checkAllInline(child);
            } else {
                return (
                    child.nodeType !== Node.ELEMENT_NODE ||
                    this.window.getComputedStyle(child).display === "inline"
                );
            }
        });
    }

    normalizeInline(root) {
        for (const el of selectElements(root, "t")) {
            if (this.checkAllInline(el)) {
                el.setAttribute("data-oe-t-inline", "true");
            }
        }
    }

    getNodeGroups(node) {
        const branchNode = node.closest("[data-oe-t-group]");
        if (!branchNode) {
            return [];
        }
        const groupId = branchNode.getAttribute("data-oe-t-group");
        const group = [];
        for (const node of branchNode.parentElement.querySelectorAll(
            `[data-oe-t-group='${groupId}']`
        )) {
            let label = "";
            if (node.hasAttribute("t-if")) {
                label = `if: ${node.getAttribute("t-if")}`;
            } else if (node.hasAttribute("t-elif")) {
                label = `elif: ${node.getAttribute("t-elif")}`;
            } else if (node.hasAttribute("t-else")) {
                label = "else";
            }
            group.push({
                groupId,
                node,
                label,
                isActive: node.getAttribute("data-oe-t-group-active") === "true",
            });
        }
        return this.getNodeGroups(branchNode.parentElement).concat([group]);
    }

    onClick(ev) {
        if (this.picker.isOpen) {
            this.picker.close();
        }
        if (ev.detail > 1) {
            const selectionData = this.dependencies.selection.getSelectionData();
            const selection = selectionData.documentSelection;
            const qwebNode =
                selection &&
                selection.anchorNode &&
                closestElement(selection.anchorNode, "[t-field],[t-esc],[t-out]");
            if (qwebNode && this.editable.contains(qwebNode)) {
                // select the whole qweb node
                const [anchorNode, anchorOffset] = leftPos(qwebNode);
                const [focusNode, focusOffset] = rightPos(qwebNode);
                this.dependencies.selection.setSelection({
                    anchorNode,
                    anchorOffset,
                    focusNode,
                    focusOffset,
                });
            }
        }
        const targetNode = ev.target;
        if (targetNode.closest("[data-oe-t-group]")) {
            this.selectNode(targetNode);
        }
    }

    selectNode(node) {
        const editableSelection = this.dependencies.selection.getSelectionData().editableSelection;
        if (!editableSelection.isCollapsed) {
            return;
        }
        this.selectedNode = node;
        this.picker.open({
            target: node,
            props: {
                groups: this.getNodeGroups(node),
                select: this.select.bind(this),
            },
        });
    }

    applyGroupQwebBranching(root) {
        const tNodes = selectElements(root, "[t-if], [t-elif], [t-else]");
        const groupsEncounter = new Set();
        for (const node of tNodes) {
            const prevNode = node.previousElementSibling;

            let groupId;
            if (prevNode && !node.hasAttribute("t-if")) {
                // Make the first t-if selectable, if prevNode is not a t-if,
                // it's already data-oe-t-selectable.
                prevNode.setAttribute("data-oe-t-selectable", "true");
                groupId = parseInt(prevNode.getAttribute("data-oe-t-group"));
                node.setAttribute("data-oe-t-selectable", "true");
            } else {
                groupId = this.groupIndex++;
            }
            groupsEncounter.add(groupId);
            node.setAttribute("data-oe-t-group", groupId);
        }
        for (const groupId of groupsEncounter) {
            const isOneElementActive = root.querySelector(
                `[data-oe-t-group='${groupId}'][data-oe-t-group-active]`
            );
            // If there is no element in groupId activated, activate the first
            // one.
            if (!isOneElementActive) {
                const firstElementToActivate = selectElements(
                    root,
                    `[data-oe-t-group='${groupId}']`
                ).next().value;
                firstElementToActivate.setAttribute("data-oe-t-group-active", "true");
            }
        }
    }

    select(node) {
        const groupId = node.getAttribute("data-oe-t-group");
        const activeElement = node.parentElement.querySelector(
            `[data-oe-t-group='${groupId}'][data-oe-t-group-active]`
        );
        if (activeElement === node) {
            return;
        }
        activeElement.removeAttribute("data-oe-t-group-active");
        node.setAttribute("data-oe-t-group-active", "true");
        this.selectedNode = node;
        this.picker.close();
        this.selectNode(node);
        // Force Chrome to clear the selection.
        // Without this, Chrome's optimization may skip the 'selectionchange' event
        // if the new node is structurally identical to the previous one
        const selection = this.document.getSelection();
        selection.removeAllRanges();
    }

    clearDataAttributes(root) {
        for (const node of root.querySelectorAll(dataAttributesSelector)) {
            QWEB_DATA_ATTRIBUTES.forEach((attr) => node.removeAttribute(attr));
        }
    }
}
