const TOC_SCROLL_MARGIN_TOP = 48; // px

function createToc(contentContainer, tocContainer) {    
    function removeToc() {
        remove(tocContainer.parentElement.parentElement);
    }
    let bounds = contentContainer.getBoundingClientRect();
    if (bounds.height <= 0.8 * window.innerHeight) {
        removeToc();
        return;
    };
    let titles = contentContainer.querySelectorAll("h2");
    if (titles.length < 2) {
        removeToc();
        return;
    }
    tocContainer.innerHTML = "";
    titles.forEach(title => {
        let anchor = tocContainer.appendChild(document.createElement("a"));
        anchor.tabIndex = 3;
        anchor.classList.add("link-hidden");
        anchor.textContent = title.textContent;
        anchor.href = `#${title.id}`;
        anchor.onclick = (e) => {
            e.preventDefault();
            const titleBounds = title.getBoundingClientRect();
            window.scrollTo({
                top: window.scrollY + titleBounds.top - TOC_SCROLL_MARGIN_TOP,
                left: window.scrollX,
                behavior: "smooth"
            });
        }
    });
}

function bindWidgets() {

    const WIDGET_UPDATE_TIMEOUT = 1000;

    var widgetUpdateBuffer = [];
    var widgetUpdateTimeout = null;

    function submitWidgetUpdates() {
        const seenKeys = [];
        var selectedUpdates = [];
        while (widgetUpdateBuffer.length > 0) {
            const update = widgetUpdateBuffer.pop();
            const key = `${update.type}${update.index}`;
            if (seenKeys.includes(key)) {
                continue;
            }
            seenKeys.push(key);
            selectedUpdates.push(update);
        }
        if (selectedUpdates.length > 0) {
            const etagMeta = document.querySelector("meta[name='etag']");
            post("", {widgets: JSON.stringify(selectedUpdates), action: "continue"}, etagMeta.content, (newEtag) => {etagMeta.content = newEtag})
                .then(res => {showToast("Saved widgets")})
                .catch(msg => {showToast(msg, true)});
        }
        widgetUpdateTimeout = null;
    }

    function updateWidget(type, index, value) {
        widgetUpdateBuffer.push({type: type, index: index, value: value});
        if (widgetUpdateTimeout != null) {
            clearTimeout(widgetUpdateTimeout);
        }
        widgetUpdateTimeout = setTimeout(submitWidgetUpdates, WIDGET_UPDATE_TIMEOUT);
    }

    document.querySelectorAll(".widget-status").forEach((widget, index) => {
        widget.setAttribute("index", index);
        widget.addEventListener("click", () => {
            let newTextContent = null;
            if (widget.textContent == "✅") {
                newTextContent = "⏺️";
            } else if (widget.textContent == "⏺️") {
                newTextContent = "❌";
            } else {
                newTextContent = "✅";
            }
            widget.textContent = newTextContent;
            updateWidget("status", index, newTextContent);
        });
    });

    document.querySelectorAll(".widget-checkbox").forEach((widget, index) => {
        widget.setAttribute("index", index);
        widget.addEventListener("input", () => {
            updateWidget("checkbox", index, widget.checked);
        });
    });

    document.querySelectorAll(".widget-color-round").forEach((widget, index) => {
        widget.setAttribute("index", index);
        widget.addEventListener("click", () => {
            const color = ["🔴", "🟠", "🟡", "🟢", "🔵", "🟣", "🟤", "⚫", "⚪"];
            const oldEmojiIndex = color.indexOf(widget.textContent);
            const newEmojiIndex = (oldEmojiIndex + 1) % color.length;
            const newTextContent = color[newEmojiIndex];
            widget.textContent = newTextContent;
            updateWidget("color_round", index, newTextContent);
        });
    });

    document.querySelectorAll(".widget-color-square").forEach((widget, index) => {
        widget.setAttribute("index", index);
        widget.addEventListener("click", () => {
            const color = ["🟥", "🟧", "🟨", "🟩", "🟦", "🟪", "🟫", "⬛", "⬜"];
            const oldEmojiIndex = color.indexOf(widget.textContent);
            const newEmojiIndex = (oldEmojiIndex + 1) % color.length;
            const newTextContent = color[newEmojiIndex];
            widget.textContent = newTextContent;
            updateWidget("color_square", index, newTextContent);
        });
    });

}

function getCharPosition(textNode, charIndex) {
    if (textNode.nodeType != Node.TEXT_NODE) throw new Error(`Node ${textNode} is not a text node`);
    const range = document.createRange();
    range.setStart(textNode, charIndex);
    range.setEnd(textNode, charIndex + 1);
    const rects = range.getClientRects();
    if (rects.length == 0) throw new Error(`Could not find client rect for char at index ${charIndex} within '${textNode.textContent}'`);
    return rects[0];
}

function closeSmdeDropdown() {
    document.querySelectorAll(".smde-dropdown, .smde-highlight").forEach(remove);
}

const REFERENCE_DELIMITER_CHARS = " ),:;.";

/**
 * @see https://codemirror.net/5/doc/manual.html
 */
function onCmCursorActivity(cmInstance, suggestionsUrl, documentsUrl) {
    const cursor = cmInstance.getCursor();
    const line = cmInstance.getLine(cursor.line);
    let iStart = Math.max(0, cursor.ch - 1);
    while (iStart > 0 && !REFERENCE_DELIMITER_CHARS.includes(line.charAt(iStart))) iStart--;
    let iEnd = iStart + 1;
    while (iEnd < line.length && !REFERENCE_DELIMITER_CHARS.includes(line.charAt(iEnd))) iEnd++;
    const word = line.substring(iStart, iEnd).trim();
    if (word.match(/^@(note|sheet|map|embednote|embedsheet|embedmap)\/([a-zA-Z0-9]+)?$/)) {

        function findTextNode() {
            // Locate the text node where the cursor is
            const lineNode = cmInstance.display.view[cursor.line].node;
            let chStart = 0;
            let buffer = [lineNode];
            let textNode;
            while (buffer.length > 0) {
                const node = buffer.splice(0, 1)[0];
                if (node.nodeType == Node.TEXT_NODE) {
                    const chEnd = chStart + node.length;
                    if (cursor.ch >= chStart && cursor.ch < chEnd) {
                        return [node, chStart];
                    }
                    chStart = chEnd;
                } else {
                    buffer = Array.from(node.childNodes).concat(buffer);
                }
            }
            throw new Error("Could not find any text node containing the cursor");
        }
        const [textNode, chStart] = findTextNode();

        // Locate the word within the text node
        const anchor = getCharPosition(textNode, iStart - chStart + 1);

        // Close any previously opened dropdown
        closeSmdeDropdown();
        
        // Create and position the dropdown itself
        const dropdown = create(document.body, "div", "smde-dropdown search");

        const verticalPadding = 4; // px
        dropdown.style.top = (anchor.bottom + verticalPadding) + "px";
        dropdown.style.left = anchor.left + "px";

        // Parse current widget
        const [match, objectTypeKey, objectNonce] = word.match(/@(\w+)\/([a-zA-Z0-9]+)?/);
        const objectType = {
            note: "note",
            sheet: "sheet",
            map: "map",
            embednote: "note",
            embedsheet: "sheet",
            embedmap: "map"
        }[objectTypeKey];
        if (objectType == null || objectType == undefined) {
            console.error("Invalid type key", objectTypeKey);
        }
        const iEnd = iStart + match.length;

        const textAreaContainer = document.querySelector(".CodeMirror");
        function updateHighlights() {
            const [newTextNode, _] = findTextNode();
            textAreaContainer.querySelectorAll(".smde-highlight").forEach(remove);
            // Highlight text node
            for (let i = iStart - chStart + 1; i < iEnd - chStart + 1; i++) {
                const bounds = getCharPosition(newTextNode, i);
                const highlight = create(textAreaContainer, "div", "smde-highlight");
                highlight.style.top = bounds.top + "px";
                highlight.style.left = bounds.left + "px";
                highlight.style.width = bounds.width + "px";
                highlight.style.height = bounds.height + "px";
            }
        }

        updateHighlights();

        function setNonce(newNonce) {
            const replaceFrom = {line: cursor.line, ch: iStart + 1};
            const replaceTo = {line: cursor.line, ch: iEnd + 1};
            cmInstance.replaceRange(`@${objectTypeKey}/${newNonce}`, replaceFrom, replaceTo);
            updateHighlights();
        }

        const searchbar = create(create(dropdown, "div", "search-bar"), "input", "search-input");

        // Set pinned value
        if (objectNonce != undefined) {
            const pinnedButton = create(dropdown, "button", "search-pin ellipsis");
            fetch(`${documentsUrl}?part=snippet&nonce=${objectNonce}`)
                .then(res => res.json())
                .then(data => {
                    const result = data.results[0];
                    if (result.error == null) {
                        pinnedButton.title = result.title;
                        pinnedButton.innerHTML = `${result.title} <i class="ri-close-line"></i>`;
                    } else {
                        pinnedButton.textContent = result.error;
                    }
            });
            pinnedButton.addEventListener("click", () => { setNonce("") });
        }

        create(dropdown, "ul", "search-suggestions menu");

        bindSearch(dropdown, suggestionsUrl, {t: objectType},
            (entry) => {
                if (entry == null) {
                    unfocusSmdeDropdown();
                } else {
                    setNonce(entry.ref)
                }
            });

        function unfocusSmdeDropdown() {
            cmInstance.setCursor({line: cursor.line, ch: iEnd});
            cmInstance.focus();
        }

    } else {
        closeSmdeDropdown();
    }
}