/**
 * Page behaviours
 *
 * This class adds various behaviours to the rendered page
 */
dw_page = {
    /**
     * initialize page behaviours
     */
    init: function(){
        dw_page.sectionHighlight();
        dw_page.currentIDHighlight();
        jQuery('a.fn_top').on('mouseover', dw_page.footnoteDisplay);
        dw_page.makeToggle('#dw__toc h3','#dw__toc > div');
        dw_page.copyCode();
    },

    /**
     * Highlight the section when hovering over the appropriate section edit button
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     */
    sectionHighlight: function() {
        jQuery('form.btn_secedit')
            /*
             * wrap the editable section in a div
             */
            .each(function () {
                let $tgt = jQuery(this).parent();
                const match = $tgt.attr('class').match(/(\s+|^)editbutton_(\d+)(\s+|$)/);
                if(!match) return;
                const nr = match[2];
                let $highlight = jQuery(); // holder for elements in the section to be highlighted
                const $highlightWrap = jQuery('<div class="section_highlight_wrapper"></div>');

                // the edit button should be part of the highlight
                $highlight = $highlight.add($tgt);

                // Walk the dom tree in reverse to find the sibling which is or contains the section edit marker
                while ($tgt.length > 0 && !($tgt.hasClass('sectionedit' + nr) || $tgt.find('.sectionedit' + nr).length)) {
                    $tgt = $tgt.prev();
                    $highlight = $highlight.add($tgt);
                }
                // wrap the elements to be highlighted in the section highlight wrapper
                $highlight.wrapAll($highlightWrap);
            })
            /*
             * highlight the section
             */
            .on('mouseover', function () {
                jQuery(this).parents('.section_highlight_wrapper').addClass('section_highlight');
            })
            /*
             * remove highlight
             */
            .on('mouseout', function () {
                jQuery(this).parents('.section_highlight_wrapper').removeClass('section_highlight');
            });
    },


    /**
     * Highlight internal link pointing to current page
     *
     * @author Henry Pan <dokuwiki@phy25.com>
     */
    currentIDHighlight: function(){
        jQuery('a.wikilink1, a.wikilink2').filter('[data-wiki-id="'+JSINFO.id+'"]').wrap('<span class="curid"></span>');
    },

    /**
     * Create/get a insitu popup used by the footnotes
     *
     * @param target - the DOM element at which the popup should be aligned at
     * @param popup_id - the ID of the (new) DOM popup
     * @return the Popup jQuery object
     */
    insituPopup: function(target, popup_id) {
        // get or create the popup div
        var $fndiv = jQuery('#' + popup_id);

        // popup doesn't exist, yet -> create it
        if($fndiv.length === 0){
            $fndiv = jQuery(document.createElement('div'))
                .attr('id', popup_id)
                .addClass('insitu-footnote JSpopup')
                .attr('aria-hidden', 'true')
                .on('mouseleave', function () {jQuery(this).hide().attr('aria-hidden', 'true');})
                .attr('role', 'tooltip');
            jQuery('.dokuwiki:first').append($fndiv);
        }

        // position() does not support hidden elements
        $fndiv.show().position({
            my: 'left top',
            at: 'left center',
            of: target
        }).hide();

        return $fndiv;
    },

    /**
     * Display an insitu footnote popup
     *
     * @author Andreas Gohr <andi@splitbrain.org>
     * @author Chris Smith <chris@jalakai.co.uk>
     * @author Anika Henke <anika@selfthinker.org>
     */
    footnoteDisplay: function () {
        var $content = jQuery(jQuery(this).attr('href')) // Footnote text anchor
                      .parent().siblings('.content').clone();

        if (!$content.length) {
            return;
        }

        // prefix ids on any elements with "insitu__" to ensure they remain unique
        jQuery('[id]', $content).each(function(){
            var id = jQuery(this).attr('id');
            jQuery(this).attr('id', 'insitu__' + id);
        });

        var content = $content.html().trim();
        // now put the content into the wrapper
        dw_page.insituPopup(this, 'insitu__fn').html(content)
        .show().attr('aria-hidden', 'false');
    },

    /**
     * Makes an element foldable by clicking its handle
     *
     * This is used for the TOC toggling, but can be used for other elements
     * as well. A state indicator is inserted into the handle and can be styled
     * by CSS.
     *
     * To properly reserve space for the expanded element, the sliding animation is
     * done on the children of the content. To make that look good and to make sure aria
     * attributes are assigned correctly, it's recommended to make sure that the content
     * element contains a single child element only.
     *
     * @param {selector} handle What should be clicked to toggle
     * @param {selector} content This element will be toggled
     * @param {int} state initial state (-1 = open, 1 = closed)
     */
    makeToggle: function(handle, content, state){
        var $handle, $content, $clicky, $child, setClicky;
        $handle = jQuery(handle);
        if(!$handle.length) return;
        $content = jQuery(content);
        if(!$content.length) return;

        // we animate the children
        $child = $content.children();

        // class/display toggling
        setClicky = function(hiding){
            if(hiding){
                $clicky.html('<span>+</span>');
                $handle.addClass('closed');
                $handle.removeClass('open');
            }else{
                $clicky.html('<span>−</span>');
                $handle.addClass('open');
                $handle.removeClass('closed');
            }
        };

        $handle[0].setState = function(state){
            var hidden;
            if(!state) state = 1;

            // Assert that content instantly takes the whole space
            $content.css('min-height', $content.height()).show();

            // stop any running animation
            $child.stop(true, true);

            // was a state given or do we toggle?
            if(state === -1) {
                hidden = false;
            } else if(state === 1) {
                hidden = true;
            } else {
                hidden = $child.is(':hidden');
            }

            // update the state
            setClicky(!hidden);

            // Start animation and assure that $toc is hidden/visible
            $child.dw_toggle(hidden, function () {
                $content.toggle(hidden);
                $content.attr('aria-expanded', hidden);
                $content.css('min-height',''); // remove min-height again
            }, true);
        };

        // the state indicator
        $clicky = jQuery(document.createElement('strong'));

        // click function
        $handle.css('cursor','pointer')
               .on('click', $handle[0].setState)
               .prepend($clicky);

        // initial state
        $handle[0].setState(state);
    },

    /**
     * Provides simple copy-to-clipboard functionality for code blocks.
     *
     * This is supposed to work only in secure context, which is required
     * by the Clipboard API. Intentionally there is no fallback using
     * the deprecated document.execCommand()
     */
    copyCode: function () {
        /**
         * Wrap code block in a container and attach a copy button
         * @param {Element} code
         */
        function addCopyButton(code) {
            const copyButton = document.createElement("button");
            copyButton.classList.add("code-copy-btn");
            copyButton.innerText = LANG["clipboard_button"];

            copyButton.addEventListener("click", copyToClipboard);
            code.appendChild(copyButton);
        }

        /**
         * Copy the code into system clipboard
         *
         * @returns {Promise<void>}
         */
        async function copyToClipboard(){
            const preElement =  this.closest('pre.code, pre.file');

            let textToCopy = "";

            // code with line numbers requires special unpacking to keep line breaks, we read it line by line
            let codeLines = preElement.querySelectorAll("li > div");
            if (codeLines.length === 0) {
                // hide button so its text is excluded from innerText
                this.hidden = true;
                textToCopy = preElement.innerText; // normal code block
                this.hidden = false;
            } else {
                textToCopy += Array.from(codeLines)
                    .map(line => line.innerText)
                    .join("\n");
            }

            // copy text and show operation status in the button
            try {
                await navigator.clipboard.writeText(textToCopy);
                this.textContent = LANG["clipboard_success"];
            } catch (err) {
                this.textContent = LANG["clipboard_error"];
            } finally {
                setTimeout(() => {
                    this.textContent = LANG["clipboard_button"]; // reset button text after a delay
                }, 2000);
            }
        }

        // do nothing unless we have code blocks and access to the Clipboard object (only in secure context)
        const codeBlocks = document.querySelectorAll("pre.code, pre.file");
        if (codeBlocks.length > 0 && typeof navigator.clipboard === "object") {
            codeBlocks.forEach(addCopyButton);
        }
    }
};

jQuery(dw_page.init);
