/*******************************************************************************
 * pretext.js
 *******************************************************************************
 * The main front-end controller for PreTeXt documents.
 *
 * Homepage: pretextbook.org
 * Repository: https://github.com/PreTeXtBook/JS_core
 *
 * Authors: David Farmer, Rob Beezer
 *
 *******************************************************************************
 */

// from https://stackoverflow.com/questions/34422189/get-item-offset-from-the-top-of-page
function getOffsetTop(e) {
    // recursively walk up the DOM via offsetParent, accumulating offsetTop as we go
    if (!e) return 0;
    return getOffsetTop(e.offsetParent) + e.offsetTop;
};

function scrollTocToActive() {
    //Try to figure out current TocItem from URL
    let fileNameWHash = window.location.href.split("/").pop();
    let fileName = fileNameWHash.split("#")[0];

    //Find just the filename in ToC
    let tocEntry = document.querySelector('#ptx-toc a[href="' + fileName + '"]');
    if (!tocEntry) {
        return; //complete failure, get out
    }

    let tocEntryTop = 0;
    //See if we can also match fileName#hash (assuming there is a fragment)
    if (fileNameWHash.includes('#')) {
        let tocEntryWHash = document.querySelector(
            '#ptx-toc a[href="' + fileNameWHash + '"]'
        );
        if (tocEntryWHash) {
            //Matched something below a subsection - activate the list item that contains it
            tocEntry.closest("li").querySelectorAll("li").forEach(li => {
                li.classList.remove("active");
            });
            tocEntryWHash.closest("li").classList.add("active");
            tocEntryTop = getOffsetTop(tocEntryWHash);
        }
    }
    if (!tocEntryTop) {
        tocEntryTop = getOffsetTop(tocEntry);
    }

    //Now activate ToC item for fileName and scroll to it
    //  Don't use scrollIntoView because it changes users tab position in Chrome
    //  and messes up keyboard navigation
    tocEntry.closest("li").classList.add("active");
    // Scroll only if the tocEntry is below the bottom half of the window,
    // scrolling to that position.
    let toc = document.querySelector("#ptx-toc");
    let tocTop = getOffsetTop(toc);
    toc.scrollTop = tocEntryTop - tocTop - 0.4 * self.innerHeight;
}

function toggletoc() {
   thesidebar = document.getElementById("ptx-sidebar");
   if (thesidebar.classList.contains("hidden") || thesidebar.classList.contains("visible")) {
       thesidebar.classList.toggle("hidden");
       thesidebar.classList.toggle("visible");
   } else if (thesidebar.offsetParent === null) {  /* not currently visible */
       thesidebar.classList.toggle("visible");
   } else {
       thesidebar.classList.toggle("hidden");
   }
   scrollTocToActive();
}

function samePageLink(a) {
    if (!(a instanceof HTMLAnchorElement)) return false;

    try {
        const linkUrl = new URL(a.href, document.baseURI);
        const currentUrl = new URL(window.location.href);

        const sameDocument =
              linkUrl.origin === currentUrl.origin &&
              linkUrl.pathname === currentUrl.pathname &&
              linkUrl.search === currentUrl.search;

        return sameDocument && !!linkUrl.hash;
    } catch (e) {
        // Invalid URL
        return false;
    }
}


window.addEventListener("DOMContentLoaded",function(event) {
    thetocbutton = document.getElementsByClassName("toc-toggle")[0];
    thetocbutton.addEventListener("click", (e) => {
        toggletoc();
        e.stopPropagation(); // keep global click handler from immediately toggling it back
    });

    // For themes that want it, install click handlers to auto close the toc
    // when the reader clicks anywhere outside it or selects a subsection.
    // (Selecting other sections or chapters navigates away from the page so
    // effectively closes the TOC.)
    if (getComputedStyle(document.documentElement).getPropertyValue('--auto-collapse-toc') == "yes") {

        const sidebar = document.getElementById("ptx-sidebar");

        // Handle all clicks outside the sidebar
        window.addEventListener("click", function(event) {
            if (sidebar.classList.contains("visible")) {
                if (!event.composedPath().includes(sidebar)) {
                    toggletoc();
                }
            }
        });

        // Handle clicks inside the sidebar but on link within a subsection.
        sidebar.addEventListener("click", function (event) {
            if (samePageLink(event.target.closest('a'))) {
                toggletoc();
            }
        });

        // Handle persistent sidebar if the page is restored from cache on back/forward buttons.
        window.addEventListener('pageshow', (e) => {
            if (e.persisted) {
                sidebar.classList.remove('visible');
                sidebar.classList.add('hidden');
            }
        });
    }
});


//-----------------------------------------------------------------------------
// Dynamic TOC logic
//-----------------------------------------------------------------------------

//item is assumed to be expander in toc-item
function toggleTOCItem(expander) {
    let listItem = expander.closest(".toc-item");
    listItem.classList.toggle("expanded");
    let expanded = listItem.classList.contains("expanded");

    let itemType = getTOCItemType(listItem);
    if(expanded) {
        expander.title = "Close" + (itemType !== "" ? " " + itemType : "");
    } else {
        expander.title = "Expand" + (itemType !== "" ? " " + itemType : "");
    }

    //should be one of each... for/of for safety and built in null avoidance
    for (const childUL of listItem.querySelectorAll(":scope > ul.toc-item-list")) {
        for (const childItem of childUL.querySelectorAll(":scope > li.toc-item")) {
            if(expanded) {
                childItem.classList.add("visible");
                childItem.classList.remove("hidden");
            } else {
                childItem.classList.remove("visible");
                childItem.classList.add("hidden");
            }
        }
    }
}

//finds item type from classes or empty string on failure
function getTOCItemType(item) {
    //Type should be class that looks like toc-X where X is not item. Find it and return X
    for(let className of item.classList) {
        if(className !== "toc-item" && className.length > 3 && className.slice(0,4) === "toc-")
            return className.slice(4);
    }
    return "";
}

//finds depth of toc-item as defined by number .toc-item-lists it is in
function getTOCItemDepth(item) {
    let depth = 0;
    let curParent = item.closest(".toc-item-list");
    while(curParent !== null) {
        depth++;
        curParent = curParent.parentElement.closest(".toc-item-list");
    }
    return depth;
}

window.addEventListener("DOMContentLoaded", function(event) {
    if(document.querySelector(".ptx-toc.focused") === null)
        return;  //only in focused mode

    let maxDepth = 1000;  //how deep TOC goes
    //check toc for depth class and get value from there
    for(let className of document.querySelector(".ptx-toc").classList)
        if(className.length > 5 && className.slice(0,5) === "depth")
            maxDepth = Number(className.slice(5));

    let preexpandedLevels = 1; //how many levels to preexpand
    let tocDataSet = document.querySelector(".ptx-toc").dataset;
    if(typeof tocDataSet.preexpandedLevels !== 'undefined')
        preexpandedLevels = Number(tocDataSet.preexpandedLevels);

    let tocItems = document.querySelectorAll(".ptx-toc ul.structural > .toc-item");
    for (const tocItem of tocItems) {
        let hasChildren = tocItem.querySelector('ul.structural') !== null;
        let depth = getTOCItemDepth(tocItem);

        if(hasChildren && depth < maxDepth) {
            let expander = document.createElement("button");
            expander.classList.add('toc-expander');
            expander.classList.add('toc-chevron-surround');
            expander.title = 'toc-expander';
            // content of span is set by CSS :before rule.
            expander.innerHTML = '<span class="icon material-symbols-outlined" aria-hidden="true"></span>';
            tocItem.querySelector(".toc-title-box").append(expander);
            expander.addEventListener('click', () => {
                toggleTOCItem(expander);
            });

            let isActive = tocItem.classList.contains("contains-active") || tocItem.classList.contains("active");
            let preExpanded = isActive || depth < preexpandedLevels;
            let itemType = getTOCItemType(tocItem);
            if(preExpanded) {
                toggleTOCItem(expander);
            } else {
                expander.title = "Expand" + (itemType !== "" ? " " + itemType : "");
            }
        }
    }

    //Do we have a hash in the URL? If so, we need to identify up to make sure
    // all parents of that item are expanded
    if(window.location.hash) {
        let hash = window.location.hash;
        // find the link in the TOC that has an href ending in this hash
        let hashLink = document.querySelector(`.ptx-toc a[href$="${hash}"]`);
        if(hashLink) {
            let parentTocItem = hashLink.closest(".toc-item");
            while(parentTocItem && !parentTocItem.classList.contains("contains-active")) {
                parentTocItem.classList.add("contains-active");
                let expander = parentTocItem.querySelector(".toc-expander");
                if(expander) {
                    //make sure it is expanded
                    if(!parentTocItem.classList.contains("expanded")) {
                        toggleTOCItem(expander);
                    }
                }
                parentTocItem = parentTocItem.parentElement.closest(".toc-item");
            }
        }
    }

});

// This needs to be after the TOC's geometry is settled
window.addEventListener("DOMContentLoaded",function(event) {
    scrollTocToActive();
});

window.onhashchange = scrollTocToActive;
