(function ($, document, window, viewport) {
    'use strict';

    // PLUGIN CLASS
    // ============
    const MainMenuPluginClass = {

        DEFAULTS: {
            "popupContainerSelector": "#et-menu-main-content",
            "openOn": "hover",
            "overflowMode": "wrap",
            "focusContainerSelector": "#shop-nav"
        },

        options: null,

        sizeClasses: [
            'et-menu-main-content-size-content',
            'et-menu-main-content-size-boxed',
            'et-menu-main-content-size-fullsizeBoxed',
            'et-menu-main-content-size-fullsize',
        ],
        $popupContainer: null,
        activeMenuItemId: null,
        $activeMenuItem: null,
        $menuItems: [],
        $currentlyHoveredElement: null,
        menuHovered: false,
        isKeyboardNavigation: false,

        // a cache to avoid re-cloning the content elements multiple times
        menuContentCache: {},

        // the additional buffer subtracted from the max height of the pop container before scrolling sets in
        maxHeightBuffer: 50,

        hidePopup: function () {
            this.$popupContainer.hide();
            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onHidePopup', [this]);
        },

        showPopup: function () {
            this.$popupContainer.show();
            if (this.isKeyboardNavigation) {
                const focusableTarget = this.$popupContainer.find('button, a').get(0);
                if (focusableTarget) {
                    focusableTarget.focus(({preventScroll: true}));
                }
            }
            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onShowPopup', [this]);
        },

        isActiveMenuItem: function (id) {
            return id === this.activeMenuItemId;
        },

        /**
         * Adjusts size and position of the popup.
         *
         * @param size
         */
        adjustPopupSizeAndPosition: function (size) {
            /*
             * possible value of size:
             *
             * content - only take the size of the content
             * boxed - container
             * fullsizeBoxed - container but extend background
             * fullsize - container-fluid
             */
            this.$popupContainer.removeClass(this.sizeClasses);
            this.$popupContainer.removeClass('container');
            this.$popupContainer.addClass('et-menu-main-content-size-' + size);
            if (!this.$activeMenuItem) {
                return;
            }
            if (size === 'content') {
                this.$popupContainer.css({
                    'min-width': this.$activeMenuItem.outerWidth() + 'px',
                    'position': 'absolute',
                    'right': '',
                    'left': this.$activeMenuItem.offset().left + 'px',
                    'maxWidth': Math.floor(window.innerWidth - this.$activeMenuItem.offset().left) + 'px'
                });
            } else if (size === 'boxed') {
                this.$popupContainer.addClass('container');
                this.$popupContainer.css({
                    'min-width': '',
                    'position': 'absolute',
                    'left': '0',
                    'right': '0',
                    'maxWidth': ''
                });
            } else if (size === 'fullsize') {
                this.$popupContainer.find('.et-menu-item-content').addClass('container-fluid');
                this.$popupContainer.css({
                    'min-width': '',
                    'position': 'absolute',
                    'left': '0',
                    'right': '0',
                    'maxWidth': ''
                });
            } else if (size === 'fullsizeBoxed') {
                this.$popupContainer.find('.et-menu-item-content').addClass('container');
                this.$popupContainer.css({
                    'min-width': '',
                    'position': 'absolute',
                    'left': '0',
                    'right': '0',
                    'maxWidth': ''
                });
            }
            this.updatePopupMaxHeight();
            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onAdjustPopupSizeAndPosition', [this, {size}]);
        },

        updatePopupMaxHeight: function() {
            let viewportOffsetY = this.$popupContainer.offset().top - $(window).scrollTop();
            this.$popupContainer.css({'max-height': window.innerHeight - viewportOffsetY - this.maxHeightBuffer + 'px'});
            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onUpdatePopupMaxHeight', [this]);
        },

        setPopupContentFromSource: function ($source) {
            this.$popupContainer.empty();
            if (!$source || !$source.length) {
                return;
            }

            let $content;
            if(this.menuContentCache[this.activeMenuItemId]) {
                $content = this.menuContentCache[this.activeMenuItemId];
            } else {
                $content = $source.clone(true).removeClass('d-none');
                if($source.data('et-menu-async-load')) {
                    let targetActiveMenuId = this.activeMenuItemId;
                    this.loadAsync(String($source.data('et-menu-async-load')), (html) => {
                        $content.html($(html).find('.et-menu-item-content').removeClass('d-none').html());
                        this.menuContentCache[targetActiveMenuId] = Object.assign({}, $content);
                    });
                } else {
                    this.menuContentCache[this.activeMenuItemId] = Object.assign({}, $content);
                }
            }
            this.$popupContainer.append($content);
            this.Events.publish(
                'easytemplate/Plugin/etMainMenuPlugin/onSetPopupContentFromSource',
                [this, {$content}]
            );
        },

        toggleMenuItem: function (event, $menuItem, size, $content, intent = 'toggle') {
            if (this.isActiveMenuItem($menuItem.attr('id')) && !(intent === 'open')) {
                // Should we follow a link or close the menu?
                if (!$menuItem.data('menu-navigate')) {
                    // prevent navigation
                    event.preventDefault();
                    this.closeMenu();
                }
            } else {
                // always close other menu points first
                this.closeMenu();
                if(!$content || !$content.length) {
                    // probably a link menu item with no content - no need to show the popup
                    return;
                }
                event.preventDefault();
                this.activeMenuItemId = $menuItem.attr('id');
                this.$activeMenuItem = $menuItem;
                this.setPopupContentFromSource($content);
                $menuItem.addClass('et-menu-open');
                $menuItem.attr('aria-expanded', true);
                this.showPopup();
                this.adjustPopupSizeAndPosition(size);
            }

            this.Events.publish(
                'easytemplate/Plugin/etMainMenuPlugin/onToggleMenuItem',
                [this, {$menuItem, size, $content, intent}]
            );
        },

        loadAsync: function(id, callback) {
            $.evo.io().call('et360_render_widget', [id, { menuType: 'main', ajax: true}], {}, function (error, data) {
                if(!error && data && data.result && typeof callback === 'function') {
                    // Only apply the callback if the active menu item has not changed in the meanwhile
                    callback(data.result);
                } else {
                    console.error(error);
                }
            });
        },

        closeMenu: function () {
            this.activeMenuItemId = null;
            if(this.isKeyboardNavigation && this.$activeMenuItem !== null) {
                this.$activeMenuItem.focus();
            }
            this.$activeMenuItem = null;
            this.$menuItems.removeClass('et-menu-open');
            this.$menuItems.attr('aria-expanded', false);
            this.hidePopup();

            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onCloseMenu', [this]);
        },

        registerEvents: function () {
            // register event handler to close the menu if the user clicks outside of a menu item or outside of the popup container
            $(document).click((event) => {
                const $target = $(event.target);
                if (!$target.closest('.et-menu-item').length &&
                    !$target.closest(this.options.popupContainerSelector).length) {
                    this.closeMenu();
                }
            });

            // Register on ESC to close menu
            $(document).on('keydown', (e) => {
                if(e.key === 'Escape') {
                    if(this.$activeMenuItem.length) {
                        this.closeMenu();
                    }
                }
                if(!this.isKeyboardNavigation && e.key === 'Tab') {
                    this.isKeyboardNavigation = true;
                }
            });

            // Register on focus loss of the popupcontainer - it's the same as closing it, somewhat
            $(document).on('focusin', (e) => {
                if(!this.isKeyboardNavigation) {
                    return;
                }
                const $target = $(e.target);
                if($target.closest(this.options.popupContainerSelector).length) {
                    return;
                }
                if($target.closest(this.options.focusContainerSelector).length) {
                    return;
                }
                this.closeMenu();
            });

            let leaveCheckFunction = window.JtlHelpers.debounce((event) => {
                if (this.$currentlyHoveredElement === null && !this.menuHovered) {
                    this.closeMenu();
                }
            }, 100);
            // register event handlers when a menu item is clicked
            this.$menuItems.each((index, menuItem) => {
                const $menuItem = $(menuItem);
                const $menuItemContent = $menuItem.find('.et-menu-item-content');
                const itemContentSize = $menuItem.data('menu-size') ?? 'content';
                $menuItem.on('click', (event) => this.toggleMenuItem(event, $menuItem, itemContentSize, $menuItemContent));
                $menuItem.on('keypress', (event) => {
                    if(event.key === ' ' || event.key === 'Enter') {
                        event.preventDefault();
                        if($menuItemContent.length) {
                            this.toggleMenuItem(event, $menuItem, itemContentSize, $menuItemContent);
                        } else {
                            // most likely a link - find the nested link and trigger a click on it.
                            let $nestedLink = $menuItem.find('a[href]');
                            if($nestedLink.length) {
                                // Note this is not the jQuery click, but the native click function, else it would not work
                                $menuItem.find('a[href]').get(0).click();
                            }
                        }
                    }
                });
                if(this.options.openOn === 'hover') {
                    if(this.options.hoverIntentEnabled && typeof $.fn.hoverIntent !== 'undefined') {
                        $menuItem.hoverIntent((event) => this.toggleMenuItem(event, $menuItem, itemContentSize, $menuItemContent, 'open'), leaveCheckFunction);
                    } else {
                        // keep track of what is currently hovered
                        $menuItem.on('mouseenter', (event) => {
                            this.$currentlyHoveredElement = $menuItem;
                        });
                        $menuItem.on('mouseleave', (event) => {
                            if(this.$currentlyHoveredElement !== null && $menuItem.attr('id') === this.$currentlyHoveredElement.attr('id')) {
                                this.$currentlyHoveredElement = null;
                            }
                        });
                        // toggle menus on hover
                        $menuItem.on('mouseenter', (event) => this.toggleMenuItem(event, $menuItem, itemContentSize, $menuItemContent, 'open'));
                        // the trickier part is leaving the menu because we need an intent handling and see if we actually left the menu
                        $menuItem.on('mouseleave', leaveCheckFunction);
                    }
                }
            });
            // register event handlers for hovering the popupcontainer and the whole menu itself - because as long as we are over it, we do not need to hide other elements.
            if(this.options.openOn === 'hover') {
                this.$popupContainer.on('mouseenter', (event) => {
                    this.$currentlyHoveredElement = this.$popupContainer;
                });
                this.$popupContainer.on('mouseleave', (event) => {
                    if(this.$currentlyHoveredElement !== null && this.$popupContainer.attr('id') === this.$currentlyHoveredElement.attr('id')) {
                        this.$currentlyHoveredElement = null;
                    }
                });
                this.$popupContainer.on('mouseleave', leaveCheckFunction);
                this.$el.on('mouseenter', (event) => {
                    this.menuHovered = true;
                });
                this.$el.on('mouseleave', (event) => {
                    this.menuHovered = false;
                });
                this.$el.on('mouseleave', leaveCheckFunction);
            }
            if(this.options.overflowMode === 'nowrap') {
                $(window).on('load resize', this.updateOverflowHandler.bind(this));
            }
            let $searchToggle = this.$el.find('.et-menu-main-search-toggle');
            if($searchToggle.length) {
                $searchToggle.on('click touchstart', (event) => {
                    this.$el.find('.et-menu-main-search-form').show();
                    $searchToggle.hide();
                    this.updateOverflowHandler();
                });
            }
            // update max height on resize
            $(window).on('resize', () => { this.updatePopupMaxHeight() });

            this.Events.publish('easytemplate/Plugin/etMainMenuPlugin/onRegisterEvents', [this]);
        },

        updateOverflowHandler: function() {
            let $menuItemRow = this.$el.find('.menu-item-row');
            if($menuItemRow.length && $menuItemRow.get(0).scrollHeight > $menuItemRow.get(0).clientHeight) {
                this.$el.addClass('has-overflow');
                let $overflowTrigger = this.$el.find('.et-menu-overflow-trigger');
                if($overflowTrigger.length && !$overflowTrigger.hasClass('initialized')) {
                    $overflowTrigger.addClass('initialized');
                    // Register click on overflow trigger, then on click, toggle expanded class
                    $overflowTrigger.on('click', () => {
                        if (this.$el.hasClass('expanded')) {
                            this.$el.removeClass('expanded');
                        } else {
                            this.$el.addClass('expanded');
                        }
                    });
                }
            } else {
                this.$el.removeClass('has-overflow');
                this.$el.removeClass('expanded');
            }
        },

        /**
         * @this {PluginBase & MainMenuPluginClass}
         * @param {HTMLElement} element
         * @param {Object} options
         */
        init: function (element, options) {
            this.element = element;
            this.options = $.extend(this.options, {
                "popupContainerSelector": this.$el.data('menu-popup-container-selector'),
                "openOn": window.isTouchCapable() ? 'click' : this.$el.data('menu-open-on'),
                "overflowMode": this.$el.data('menu-overflow-mode'),
                "hoverIntentEnabled": window.isTouchCapable() ? false : this.$el.data('menu-hover-intent')
            });
            this.$popupContainer = $(this.options.popupContainerSelector).first();
            this.$menuItems = this.$el.find('.et-menu-item');
            this.registerEvents();
        },
    };

    // REGISTER AND ADD THE PLUGIN
    // ===========================
    if (!window.easytemplate && !window.easytemplate.PluginManager) {
        throw Error('Cannot initialize Easytemplate Plugin. plugin-manager.js has to be loaded first!');
    }

    /** @type {PluginManager} manager */
    const manager = window.easytemplate.PluginManager;
    manager.addPlugin('#et-menu-main-wrapper', MainMenuPluginClass, 'etMainMenuPlugin');
})(jQuery, document, window, ResponsiveBootstrapToolkit);