/* ===================== AUTOGENERATED SOURCE IMPORT ===================== */

import Template from "/emcJS/util/html/template/HTMLTemplate.js";

const TPL = new Template(`
    <div id="focus_catcher_top" tabindex="0"></div>
    <div id="init_focus"></div>
    <slot id="menu"></slot>
    <div id="focus_catcher_bottom" tabindex="0"></div>
`);

import GlobalStyle from "/emcJS/util/html/template/CSSTemplate.js";

const STYLE = new GlobalStyle(`
    :host {
        position: fixed;
        display: none;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        cursor: default;
        z-index: var(--z-index-contextmenu, 900700);
    }
    
    :host([active]:not([active="false"])) {
        display: block;
    }
    
    #menu {
        position: relative;
        display: inline-block;
        overflow: auto;
        max-width: calc(100dvw - 50px);
        max-height: calc(100dvh - 50px);
        background: var(--contextmenu-back-color, #ffffff);
        border: solid 2px var(--contextmenu-border-color, #cccccc);
    }
    
    ::slotted(.item),
    ::slotted([menu-action]) {
        display: flex;
        align-items: center;
        min-width: 150px;
        min-height: 40px;
        padding: 5px;
        color: var(--contextmenu-text-color, #000000);
        background: var(--contextmenu-back-color, #ffffff);
        cursor: pointer;
        user-select: none;
        box-sizing: border-box;
    }
    
    ::slotted(.item:hover),
    ::slotted([menu-action]:hover) {
        color: var(--contextmenu-hover-text-color, #ffffff);
        background: var(--contextmenu-hover-back-color, #696969);
    }
    
    ::slotted(.item:focus),
    ::slotted([menu-action]:focus) {
        box-shadow: inset 0 0 2px 2px var(--contextmenu-focus-color, #06b5ff);
        outline: none;
    }
    
    ::slotted(.splitter) {
        display: block;
        margin: 10px 5px;
        height: 2px;
        background: var(--contextmenu-text-color, #000000);
        cursor: default;
        user-select: none;
        box-sizing: border-box;
    }
    
    ::slotted(.hidden) {
        display: none !important;
    }
    
`);

/* ======================================================================= */

import {debounce} from "../../../util/Debouncer.js";
import {deepClone} from "../../../util/helper/DeepClone.js";
import CustomElement from "../../element/CustomElement.js";
import CtxMenuLayer from "./CtxMenuLayer.js";
import "./ContextMenuItem.js";

const Q_TAB = [
    "button:not([tabindex=\"-1\"])",
    "[href]:not([tabindex=\"-1\"])",
    "input:not([tabindex=\"-1\"])",
    "select:not([tabindex=\"-1\"])",
    "textarea:not([tabindex=\"-1\"])",
    "[tabindex]:not([tabindex=\"-1\"])"
].join(",");
const LAYER_MARGIN = 5;

function getLayerBounds(source) {
    const slot = source.assignedSlot;
    if (slot != null) {
        const host = slot.getRootNode().host;
        if (host instanceof CtxMenuLayer) {
            return slot.getBoundingClientRect();
        }
    }
    return document.body.getBoundingClientRect();
}

export default class ContextMenu extends CustomElement {

    #menuEl;

    #initFocusEl;

    #topFocusEl;

    #bottomFocusEl;

    #top = 0;

    #left = 0;

    #props;

    #items = null;

    #addedItems = null;

    #inactiveGroups = new Set();

    constructor() {
        super();
        this.shadowRoot.append(TPL.generate());
        STYLE.apply(this.shadowRoot);
        /* --- */
        this.#menuEl = this.shadowRoot.getElementById("menu");
        this.#initFocusEl = this.shadowRoot.getElementById("init_focus");
        this.#topFocusEl = this.shadowRoot.getElementById("focus_catcher_top");
        this.#bottomFocusEl = this.shadowRoot.getElementById("focus_catcher_bottom");
        this.#menuEl.style.left = `${LAYER_MARGIN}px`;
        this.#menuEl.style.top = `${LAYER_MARGIN}px`;
        this.#menuEl.addEventListener("click", (event) => {
            this.close();
            event.preventDefault();
            event.stopPropagation();
            return false;
        });
        this.addEventListener("click", (event) => {
            this.close();
            event.preventDefault();
            event.stopPropagation();
            return false;
        });
        this.addEventListener("contextmenu", (event) => {
            this.close();
            event.preventDefault();
            event.stopPropagation();
            return false;
        });
        this.addEventListener("keyup", (event) => {
            if (event.key == "Enter" || event.key == "Escape") {
                this.close();
                /* --- */
                event.preventDefault();
                return false;
            }
        });
        /* --- */
        this.#topFocusEl.onfocus = () => {
            this.focusLast();
        };
        this.#bottomFocusEl.onfocus = () => {
            this.focusFirst();
        };
        this.#initFocusEl.onblur = () => {
            this.#initFocusEl.setAttribute("tabindex", "");
        };
    }

    connectedCallback() {
        if (!this.hasAttribute("slot")) {
            this.setAttribute("slot", "ctxmnu");
        }
        this.initItems();
    }

    static get observedAttributes() {
        return ["slot"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        if (name == "slot" && newValue != "ctxmnu") {
            this.setAttribute("slot", "ctxmnu");
        }
    }

    get top() {
        return this.#top;
    }

    get left() {
        return this.#left;
    }

    get active() {
        const val = this.getAttribute("active");
        return !!val && val != "false";
    }

    set active(val) {
        this.setAttribute("active", val);
    }

    show(posX, posY, ...props) {
        this.#top = posY;
        this.#left = posX;
        this.#menuEl.style.visibility = "hidden";
        this.#props = deepClone(props);
        /* --- */
        if (!this.active) {
            this.active = true;
            this.dispatchEvent(new Event("show"));
        }
        /* --- */
        setTimeout(() => {
            this.#calculatePostition();
            this.initFocus();
        }, 0);
    }

    #calculatePostition() {
        const pRect = getLayerBounds(this);
        let posY = this.#top;
        let posX = this.#left;
        if (pRect.x >= 0 && posX < pRect.x + LAYER_MARGIN) {
            posX = pRect.x + LAYER_MARGIN;
        } else {
            const bWidth = Math.min(pRect.width + pRect.x, window.innerWidth);
            if (this.#menuEl.offsetWidth + posX > bWidth - LAYER_MARGIN) {
                posX = bWidth - this.#menuEl.offsetWidth - LAYER_MARGIN;
            }
        }
        if (pRect.y >= 0 && posY < pRect.y + LAYER_MARGIN) {
            posY = pRect.y + LAYER_MARGIN;
        } else {
            const bHeight = Math.min(pRect.height + pRect.y, window.innerHeight);
            if (this.#menuEl.offsetHeight + posY > bHeight - LAYER_MARGIN) {
                posY = bHeight - this.#menuEl.offsetHeight - LAYER_MARGIN;
            }
        }
        this.#menuEl.style.visibility = "";
        this.#menuEl.style.left = `${posX}px`;
        this.#menuEl.style.top = `${posY}px`;
    }

    close() {
        if (this.active) {
            this.active = false;
            this.dispatchEvent(new Event("close"));
        }
        /* --- */
        this.#menuEl.style.left = `${LAYER_MARGIN}px`;
        this.#menuEl.style.top = `${LAYER_MARGIN}px`;
    }

    initFocus() {
        this.#initFocusEl.setAttribute("tabindex", "0");
        this.#initFocusEl.focus();
    }

    focusFirst() {
        const a = Array.from(this.querySelectorAll(Q_TAB));
        if (a.length) {
            a[0].focus();
        }
    }

    focusLast() {
        const a = Array.from(this.querySelectorAll(Q_TAB));
        if (a.length) {
            a[a.length - 1].focus();
        }
    }

    initItems() {
        if (this.#items == null) {
            const config = [];
            const itemEls = this.querySelectorAll("div.splitter, [menu-action]");
            for (const itemEl of Array.from(itemEls)) {
                const attr = itemEl.getAttribute("menu-action");
                if (attr != null) {
                    config.push({
                        menuAction: attr,
                        content: itemEl.innerHTML
                    });
                } else {
                    config.push("splitter");
                }
            }
            this.setItems(config);
        }
    }

    setItems(config) {
        if (Array.isArray(config)) {
            this.#items = config;
            this.#renderItems();
        } else {
            this.#items = null;
            this.initItems();
        }
    }

    setAddedItems(config) {
        if (Array.isArray(config)) {
            this.#addedItems = config;
            this.#renderItems();
        } else {
            this.#addedItems = null;
            this.#renderItems();
        }
    }

    toggleGroupActive(name, value) {
        if (value) {
            this.#inactiveGroups.delete(name);
        } else {
            this.#inactiveGroups.add(name);
        }
        const itemEls = this.querySelectorAll(`[menu-group="${name}"]`);
        for (const itemEl of Array.from(itemEls)) {
            itemEl.classList.toggle("hidden", !value);
        }
    }

    #renderItems = debounce(() => {
        this.innerHTML = "";
        if (Array.isArray(this.#items)) {
            for (const entry of this.#items) {
                this.#addItem(entry);
            }
        }
        if (Array.isArray(this.#addedItems)) {
            for (const entry of this.#addedItems) {
                this.#addItem(entry);
            }
        }
        if (this.active) {
            this.#calculatePostition();
            this.initFocus();
        }
    });

    #addItem(entry) {
        if (entry == "splitter") {
            const el = document.createElement("div");
            el.classList.add("splitter");
            this.append(el);
        } else if (entry instanceof HTMLElement) {
            this.append(entry);
            const attr = entry.getAttribute("menu-action");
            if (attr) {
                entry.addEventListener("click", (event) => {
                    this.#onElementChoice(attr);
                    /* --- */
                    event.preventDefault();
                    return false;
                });
                entry.addEventListener("keyup", (event) => {
                    if (event.key == "Enter") {
                        this.#onElementChoice(attr);
                        /* --- */
                        event.preventDefault();
                        return false;
                    }
                });
            }
        } else if (typeof entry == "object" && !Array.isArray(entry)) {
            if (entry.type == "splitter") {
                const el = document.createElement("div");
                el.classList.add("splitter");
                if (entry.group) {
                    el.setAttribute("menu-group", entry.group);
                    if (this.#inactiveGroups.has(entry.group)) {
                        el.classList.add("hidden");
                    }
                }
                this.append(el);
            } else if (!entry.type || entry.type == "item") {
                const el = document.createElement("emc-contextmenuitem");
                el.classList.add("item");
                if (entry.group) {
                    el.setAttribute("menu-group", entry.group);
                    if (this.#inactiveGroups.has(entry.group)) {
                        el.classList.add("hidden");
                    }
                }
                el.setAttribute("tabindex", "0");
                el.innerHTML = entry.content;
                el.info = entry.info;

                /* --- */
                if (typeof entry.action == "function") {
                    el.addEventListener("click", (event) => {
                        entry.action();
                        /* --- */
                        event.preventDefault();
                        return false;
                    });
                    el.addEventListener("keyup", (event) => {
                        if (event.key == "Enter") {
                            entry.action();
                            /* --- */
                            event.preventDefault();
                            return false;
                        }
                    });
                }
                /* --- */
                if (typeof entry.menuAction == "string") {
                    el.setAttribute("menu-action", entry.menuAction);
                    el.addEventListener("click", (event) => {
                        this.#onElementChoice(entry.menuAction);
                        /* --- */
                        event.preventDefault();
                        return false;
                    });
                    el.addEventListener("keyup", (event) => {
                        if (event.key == "Enter") {
                            this.#onElementChoice(entry.menuAction);
                            /* --- */
                            event.preventDefault();
                            return false;
                        }
                    });
                }
                this.append(el);
            }
        }
    }

    #onElementChoice(name) {
        const ev = new Event(name);
        ev.left = this.left;
        ev.top = this.top;
        ev.props = deepClone(this.#props);
        this.dispatchEvent(ev);
    }

}

customElements.define("emc-contextmenu", ContextMenu);
