import { matchingSelector } from "@devowl-wp/resolve-none-computed-style";

import { HTML_ATTRIBUTE_BLOCKER_CONNECTED_PRESERVED, HTML_ATTRIBUTE_VISUAL_PARENT } from "../dom/htmlAttributes.js";

const CHILDREN_SELECTOR_PREFIX = "children:";

/**
 * Find element where the visual content blocker should be placed relative to the blocked element.
 */
function findVisualParent(
    node: HTMLElement,
    /**
     * When one of the selectors matches, the defined visual parent will be used. Use `self` to use the
     * result of the selector. Use %s to match the node for which we search the visual parent.
     * This allows us to use the power of `:has()` CSS selector.
     *
     * Example:
     *
     * ```
     * {
     *     ".my-parent-class:has(%s)": "self", // -> ".my-parent-class" is used as visual parent
     *     ".my-parent-class": "self", // -> The same as above as we are automatically appending :has(%s) to the selector
     *     ".my-parent-class>%s": "self", // -> the blocked element is used as visual parent, use has-selector instead!
     * }
     * ```
     */
    visualParentSelectors: Record<string, number | string | boolean> = {},
): [
    HTMLElement,
    "none" | "parent" | "parentZ" | "parentSelector" | "childrenSelector",
    /**
     * Whether the visual parent was found or a fallback was used.
     */
    boolean,
] {
    if (!node.parentElement) {
        return [node, "none", true];
    }

    // Find visual parent attribute
    const forceVisualParent = ["a"].indexOf(node.parentElement.tagName.toLowerCase()) > -1; // for links always use the parent
    let useVisualParent: (typeof visualParentSelectors)[""] = forceVisualParent;

    if (node.hasAttribute(HTML_ATTRIBUTE_VISUAL_PARENT)) {
        useVisualParent = node.getAttribute(HTML_ATTRIBUTE_VISUAL_PARENT);
    } else {
        const connectedCounter = node.getAttribute(HTML_ATTRIBUTE_BLOCKER_CONNECTED_PRESERVED);
        for (const [selector, value] of Object.entries(visualParentSelectors)) {
            const useSelector = (selector.includes("%s") ? selector : `${selector}:has(%s)`).replace(
                "%s",
                `[${HTML_ATTRIBUTE_BLOCKER_CONNECTED_PRESERVED}="${connectedCounter}"]:not(.rcb-content-blocker)`,
            );
            const elem = document.querySelector(useSelector) as HTMLElement;
            if (elem) {
                if (value === "self") {
                    return [elem, "parentSelector", true];
                } else {
                    useVisualParent = value;
                    break;
                }
            }
        }
    }

    if (useVisualParent) {
        if (useVisualParent === true || useVisualParent === "true") {
            // Usual behavior, just take the parent
            return [node.parentElement || node, "parent", !!node.parentElement];
        } else if (!isNaN(+useVisualParent)) {
            // The attribute is a number, let's go up x parents
            let parent = node;
            for (let i = 0; i < +useVisualParent; i++) {
                if (!parent.parentElement) {
                    return [parent, "parentZ", false]; // Fallback to last found parent
                }
                parent = parent.parentElement;
            }
            return [parent, "parentZ", true];
        } else if (typeof useVisualParent === "string") {
            if (useVisualParent.startsWith(CHILDREN_SELECTOR_PREFIX)) {
                let childrenElement = node.querySelector(
                    useVisualParent.substr(CHILDREN_SELECTOR_PREFIX.length),
                ) as HTMLElement;
                const foundChildrenElement = !!childrenElement;

                // When using `children:<selector>` and the node does not exist, we still need to create the visual content blocker
                // within the blocked element due to the `children:` specifier. For this, we should hide all elements within the
                // blocked element (through custom CSS) and only show the visual content blocker
                // createBefore should be the first found element, and all other children should be hidden
                if (!foundChildrenElement) {
                    childrenElement = (node.children[0] as HTMLElement) || node;
                }

                return [childrenElement, "childrenSelector", foundChildrenElement];
            } else {
                // The attribute is a query selector
                for (let elem = node; elem; elem = elem.parentElement) {
                    if (matchingSelector(elem, useVisualParent)) {
                        return [elem, "parentSelector", true];
                    }
                }
            }
        }
    }

    return [node, "none", true];
}

export { findVisualParent };
