/* Highlighting utilities for Sphinx HTML documentation. */
"use strict";

const SPHINX_HIGHLIGHT_ENABLED = true

/**
 * highlight a given string on a node by wrapping it in
 * span elements with the given class name.
 */
const _highlight = (node, addItems, text, className) => {
    if (node.nodeType === Node.TEXT_NODE) {
        const val = node.nodeValue;
        const parent = node.parentNode;
        const pos = val.toLowerCase().indexOf(text);
        if (
            pos >= 0 &&
            !parent.classList.contains(className) &&
            !parent.classList.contains("nohighlight")
        ) {
            let span;

            const closestNode = parent.closest("body, svg, foreignObject");
            const isInSVG = closestNode && closestNode.matches("svg");
            if (isInSVG) {
                span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
            } else {
                span = document.createElement("span");
                span.classList.add(className);
            }

            span.appendChild(document.createTextNode(val.substr(pos, text.length)));
            const rest = document.createTextNode(val.substr(pos + text.length));
            parent.insertBefore(
                span,
                parent.insertBefore(
                    rest,
                    node.nextSibling
                )
            );
            node.nodeValue = val.substr(0, pos);
            /* There may be more occurrences of search term in this node. So call this
             * function recursively on the remaining fragment.
             */
            _highlight(rest, addItems, text, className);

            if (isInSVG) {
                const rect = document.createElementNS(
                    "http://www.w3.org/2000/svg",
                    "rect"
                );
                const bbox = parent.getBBox();
                rect.x.baseVal.value = bbox.x;
                rect.y.baseVal.value = bbox.y;
                rect.width.baseVal.value = bbox.width;
                rect.height.baseVal.value = bbox.height;
                rect.setAttribute("class", className);
                addItems.push({parent: parent, target: rect});
            }
        }
    } else if (node.matches && !node.matches("button, select, textarea")) {
        node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
    }
};
const _highlightText = (thisNode, text, className) => {
    let addItems = [];
    _highlight(thisNode, addItems, text, className);
    addItems.forEach((obj) =>
        obj.parent.insertAdjacentElement("beforebegin", obj.target)
    );
};

/**
 * Small JavaScript module for the documentation.
 */
const SphinxHighlight = {

    /**
     * highlight the search words provided in localstorage in the text
     */
    highlightSearchWords: () => {
        if (!SPHINX_HIGHLIGHT_ENABLED) return;  // bail if no highlight

        // get and clear terms from localstorage
        const url = new URL(window.location);
        const highlight =
            localStorage.getItem("sphinx_highlight_terms")
            || url.searchParams.get("highlight")
            || "";
        localStorage.removeItem("sphinx_highlight_terms")
        url.searchParams.delete("highlight");
        window.history.replaceState({}, "", url);

        // get individual terms from highlight string
        const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
        if (terms.length === 0) return; // nothing to do

        // There should never be more than one element matching "div.body"
        const divBody = document.querySelectorAll("div.body");
        const body = divBody.length ? divBody[0] : document.querySelector("body");
        window.setTimeout(() => {
            terms.forEach((term) => _highlightText(body, term, "highlighted"));
        }, 10);

        const searchBox = document.getElementById("searchbox");
        if (searchBox === null) return;
        searchBox.appendChild(
            document
                .createRange()
                .createContextualFragment(
                    '<p class="highlight-link">' +
                    '<a href="javascript:SphinxHighlight.hideSearchWords()">' +
                    _("Hide Search Matches") +
                    "</a></p>"
                )
        );
    },

    /**
     * helper function to hide the search marks again
     */
    hideSearchWords: () => {
        document
            .querySelectorAll("#searchbox .highlight-link")
            .forEach((el) => el.remove());
        document
            .querySelectorAll("span.highlighted")
            .forEach((el) => el.classList.remove("highlighted"));
        localStorage.removeItem("sphinx_highlight_terms")
    },

    initEscapeListener: () => {
        // only install a listener if it is really needed
        if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;

        document.addEventListener("keydown", (event) => {
            // bail for input elements
            if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
            // bail with special keys
            if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
            if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
                SphinxHighlight.hideSearchWords();
                event.preventDefault();
            }
        });
    },
};

_ready(() => {
    /* Do not call highlightSearchWords() when we are on the search page.
     * It will highlight words from the *previous* search query.
     */
    if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
    SphinxHighlight.initEscapeListener();
});
