'use strict';

class FlexiMenu {

    static ATTR = `data-fleximenu`;
    static initialize() {
        document.querySelectorAll(`[${FlexiMenu.ATTR}]`).forEach((fleximenu) => new FlexiMenu(fleximenu));
    }

    constructor(fleximenuElement) {
        if (!(fleximenuElement instanceof HTMLElement)) {
            return;
        }
        this.element = fleximenuElement;
        this.menu = this.element.querySelector('.fleximenu');
        this.hasSub = this.menu.querySelectorAll('.fleximenu-item--has-sub');
        this.hasSubLinks = this.menu.querySelectorAll('.fleximenu-item--has-sub > a');
        this.subMenu = this.menu.querySelectorAll('.fleximenu-sub');
        this.menuLinks = this.menu.querySelectorAll('.fleximenu-link');

        this.isIconMenu = this.menu.classList.contains('fleximenu--icon');
        this.isInlineMenu = this.menu.classList.contains('fleximenu--inline');

        this.config = this.#parseConfig();

        if (this._isObjEmpty(this.config)) return;

        this.eventHandlers = new WeakMap();

        this.init();
    }

    init() {
        const triggers = this._getValidTriggers();
        const targets = this.#getTargets();

        if (targets) {
            targets.forEach((target) => {
                if (target instanceof HTMLElement || target === document) {
                    triggers.forEach((trigger) => {
                        if (trigger === 'load') {
                            window.addEventListener(trigger, this.#handleEvent.bind(this));
                        }
                        target.addEventListener(trigger, this.#handleEvent.bind(this));
                    });
                }
            });
        }
    }

    #handleEvent(event) {
        event.preventDefault();

        this.#position();

        this.#showLabel();

        this.#openMenu(event);

        this.#closeMenu(event);

        this.#openSubmenu(event);

        window.addEventListener('resize', () => {
            this.#position();
        });

    }

    #showLabel() {
        this.menuLinks.forEach(menuLink => {
            menuLink.addEventListener('touchstart', (event) => {
                const parent = menuLink.closest('.fleximenu-item');
                if (parent.classList.contains('fleximenu-item--has-sub')) {
                    return;
                }

                this.#closeActiveLabel();

                const label = menuLink.querySelector('.fleximenu-label');

                if (label && !label.classList.contains('-active')) {
                    event.preventDefault();
                    label.classList.add('-active', '-temporarily');

                    menuLink.addEventListener('click', () => {
                        label.classList.remove('-active', '-temporarily');
                    });

                    setTimeout(() => {
                        label.classList.remove('-active', '-temporarily');
                    }, 3000);

                }

            });
        });
    }

    #closeActiveLabel() {
        const temporarily = this.menu.querySelectorAll('.fleximenu-label.-temporarily');
        if (temporarily.length > 0) {
            temporarily.forEach(temporarily => {
                temporarily.classList.remove('-active', '-temporarily');
            });
        }
    }

    #openSubmenu(event) {

        if (this.subMenu.length === 0) {
            return;
        }

        this.hasSubLinks.forEach((link) => {
            link.addEventListener('click', (e) => e.preventDefault());
        });

        this.hasSub.forEach((li) => {

            const clickHandler = (e) => {
                e.stopPropagation();
                const siblings = Array.from(li.parentElement.children).filter((child) => child !== li);
                siblings.forEach((sibling) => {
                    sibling.classList.remove('-open');
                    sibling.querySelectorAll('.fleximenu-item.-open').forEach((nestedSub) => {
                        nestedSub.classList.remove('-open');
                    });
                });
                li.classList.toggle('-open');
            };
            this.eventHandlers.set(li, clickHandler);
            li.addEventListener('click', clickHandler);
        });

    }

    #position() {
        if (!this.config?.position) {
            return;
        }
        const element = this.config?.position?.sel ? document.querySelector(this.config.position.sel) : null;

        if (element) {
            const computedStyle = getComputedStyle(element);
            if (computedStyle.position === 'static') {
                element.style.position = 'relative';
            }
            element.append(this.element);
        }
    }

    #openMenu(e) {

        const menuWidth = this.element.offsetWidth;
        const menuHeight = this.element.offsetHeight;
        const windowWidth = document.documentElement.clientWidth;
        const windowHeight = document.documentElement.clientHeight;

        const computedStyles = getComputedStyle(this.element);

        let paddingX = parseFloat(computedStyles.marginInlineEnd);
        let paddingY = parseFloat(computedStyles.marginBlockEnd);

        let maxLabelWidth = 0;

        if (this.isIconMenu) {
            const labels = this.element.querySelectorAll('.fleximenu > .fleximenu-item .fleximenu-label');
            labels.forEach((label) => {
                const width = label.getBoundingClientRect().width;
                if (width > maxLabelWidth) {
                    maxLabelWidth = width;
                }
            });
        }

        let x = e.clientX;
        let y = e.clientY;

        let rect = null;
        if (this.config?.type && this.config.type === 'dropdown') {
            const element = this.config?.target ? document.querySelector(this.config.target) : null;
            if (element !== null) {
                rect = element.getBoundingClientRect();
                x = rect.left;
                y = rect.bottom + paddingY;
            }
        }

        if (this.config?.type && this.config.type === 'selectText') {
            const selection = window.getSelection();
            if (!selection.rangeCount || !selection.toString().trim()) return;

            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();
            x = rect.left;
            y = rect.bottom + paddingY;
            // console.log(paddingY)
        }


        if (this.isIconMenu && x !== undefined) {
            this.menu.classList.remove('-label-opposite');
            if (this.isInlineMenu) {
                paddingX = paddingX + maxLabelWidth;
            }
            if (x + menuWidth + paddingX + maxLabelWidth >= windowWidth) {
                this.menu.classList.add('-label-opposite');
            }
        }

        if (x + menuWidth + paddingX >= windowWidth) {
            x = windowWidth - menuWidth - paddingX;
            if (rect !== null) {
                x = rect.right - menuWidth;
            }

        }

        if (y + menuHeight + paddingY > windowHeight) {
            y = windowHeight - menuHeight - paddingY;

            if (rect !== null) {
                y = rect.top - menuHeight - paddingY;
            }
        }


        if (this.config?.type && (this.config.type === 'dropdown' || this.config.type === 'context' || this.config.type === 'selectText' )) {
            this.element.style.left = `${x}px`;
            this.element.style.top = `${y}px`;
        }


        this.element.classList.toggle('-active');

        this.subMenu.forEach((sub) => {
            sub.classList.remove('-opposite-x', '-opposite-y');
            const subWidth = sub.offsetWidth;
            const subHeight = sub.offsetHeight;
            const rect = sub.getBoundingClientRect();
            const sx = rect.left;
            const sy = rect.top;
            const padding = 8;

            let maxSubLabel = 0;

            if (this.isIconMenu) {
                const labels = sub.querySelectorAll('.fleximenu-item .fleximenu-label');
                labels.forEach((label) => {
                    const width = label.getBoundingClientRect().width;
                    if (width > maxSubLabel) {
                        maxSubLabel = width;
                    }
                });
            }

            let subHitsRight = (sx + subWidth + padding >= windowWidth);

            if (this.isIconMenu && this.isInlineMenu) {
                subHitsRight = (sx + subWidth + padding + maxSubLabel >= windowWidth);
            } else if (this.isIconMenu) {
                subHitsRight = (sx + subWidth + padding + maxSubLabel >= windowWidth);
            }

            const subHitsBottom = (sy + subHeight + padding >= windowHeight);

            if (subHitsRight) {
                sub.classList.add('-opposite-x');
            }

            if (subHitsBottom) {
                sub.classList.add('-opposite-y');
            }

        });

    }

    #closeMenu() {
        const triggers = this._getValidTriggersOut();

        if (!Array.isArray(triggers)) {
            return;
        }

        this.boundHandleClickOutside = this.#handleClickOutside.bind(this);

        triggers.forEach((trigger) => {
            if (trigger === 'esc') {
                document.addEventListener('keydown', (event) => {
                    if (event.key === 'Escape' || event.key === 'Esc') {
                        this.boundHandleClickOutside(event);
                    }
                });
            } else {
                document.addEventListener(trigger, this.boundHandleClickOutside);
            }

        });

        this.activeTriggers = triggers;
    }

    #handleClickOutside(e) {

        if (!this.element.contains(e.target)) {

            this.element.classList.remove('-active');

            this.subMenu.forEach((sub) => {
                sub.classList.remove('-opposite-x', '-opposite-y');
            });

            this.hasSub.forEach((li) => {
                const handler = this.eventHandlers.get(li);
                if (handler) {
                    li.removeEventListener('click', handler);
                    this.eventHandlers.delete(li);
                }
                li.classList.remove('-open');
            });

            this.activeTriggers.forEach((trigger) => {
                document.removeEventListener(trigger, this.boundHandleClickOutside);
            });

            this.activeTriggers = [];

            if (this.config?.trigger === 'load') {
                this.#handleEvent(e);
            }
        }
    }

    _getValidTriggers() {
        const validEvents = ['click', 'mouseover', 'scroll', 'dblclick', 'contextmenu', 'clickOut', 'load', 'mouseup', 'mouseenter'];
        const trigger = this.config?.trigger;
        if (Array.isArray(trigger)) return trigger.filter((event) => validEvents.includes(event)) || ['click'];
        return validEvents.includes(trigger) ? [trigger] : ['click'];
    }

    _getValidTriggersOut() {
        const validEvents = ['click', 'mouseover', 'mousedown', 'scroll', 'dblclick', 'contextmenu', 'clickOut', 'esc'];
        const trigger = this.config?.triggerOut;
        if (Array.isArray(trigger)) return trigger.filter((event) => validEvents.includes(event)) || ['click'];
        return validEvents.includes(trigger) ? [trigger] : null;
    }

    #getTargets() {
        let targets = [];

        if (!this.config?.target) {
            targets.push(document);
            return targets;
        }

        const target = this.config.target;

        if (target === 'document') {
            targets.push(document);
        } else if (this._isArray(target)) {
            target.forEach((selector) => {
                const elements = this.#getCachedElements(selector);
                targets.push(...elements);
            });
        } else {
            const elements = this.#getCachedElements(target);
            targets.push(...elements);
        }

        return targets;

    }

    #getCachedElements(selector) {
        if (!this.elementCache) {
            this.elementCache = {};
        }

        if (this.elementCache[selector]) {
            return this.elementCache[selector];
        }

        let elements;

        elements = document.querySelectorAll(selector);

        this.elementCache[selector] = Array.from(elements);

        return this.elementCache[selector];
    }

    #parseConfig() {
        const options = this.element.getAttribute(FlexiMenu.ATTR);
        return JSON.parse(options);
    }


    _isObjEmpty(obj) {
        return obj && typeof obj === 'object' && Object.keys(obj).length === 0;
    }

    _isArray(value) {
        return Array.isArray(value);
    }
}

document.addEventListener('DOMContentLoaded', function () {
    FlexiMenu.initialize();
});

