/** @typedef { import('jquery') } $ */
/**
 * @typedef {Object} PluginManager
 * @property {Events} Events Event Manager util
 * @property {PluginManager.init} init Initialize all registered plugins for the current viewport.
 * @property {PluginManager.reinitialize} reinitialize Reinit the plugin for the current viewport.
 * @property {PluginManager.addPlugin} addPlugin
 * @property {PluginManager.removePlugin} removePlugin
 */

/**
 * @callback PluginManager.init
 * @returns {PluginManager}
 */

/**
 * @callback PluginManager.reinitialize
 * @param {string} plugin
 * @returns {PluginManager}
 */

/**
 * @callback PluginManager.addPlugin
 * @param {string} selector
 * @param {Object|PluginBase} pluginClass Class of the plugin to register, if not already registered
 * @param {string} pluginName Name of the jQuery Plugin
 * @param {Object|Array} [config] Optional config object. If omitted, it acts as viewport param.
 * @param {Array|String} [viewport] The Viewport for which the plugin should be registered.
 * @returns {PluginManager}
 *
 * @example The Plugin will be available on all viewport states.
 * ```
 * PluginManager.addPlugin('.my-selector', pluginClass, 'pluginName');
 * ```
 *
 * @example The Plugin will only be available for the 'xs' viewport.
 * ```
 * PluginManager.addPlugin('.my-selector', pluginClass, 'pluginName', 'xs');
 * ```
 *
 * @example The Plugin will only be available for the 'md' and 'lg' viewports.
 * ```
 * PluginManager.addPlugin('.my-selector', pluginClass, 'pluginName', ['md', 'lg']);
 * ```
 *
 * @example The Plugin will only be available for the 'md' and 'lg' viewports and the passed config will be used.
 * ```
 * PluginManager.addPlugin('.my-selector', pluginClass, 'pluginName', {'foo': 'bar'}, ['md', 'lg']);
 * ```
 */

/**
 * @callback PluginManager.removePlugin
 * @param {string} selector
 * @param {string} pluginName Name of the jQuery Plugin
 * @param {Array|String|null} [viewport] The Viewport from which the plugin should be removed.
 * @returns {PluginManager}
 *
 * @example The Plugin will be removed on all viewport states.
 * ```
 * PluginManager.removePlugin('.my-selector', 'pluginName');
 * ```
 *
 * @example The Plugin will only be removed for the 'xs' viewport.
 * ```
 * PluginManager.removePlugin('.my-selector', 'pluginName', 'xs');
 * ```
 *
 * @example The Plugin will only be removed for the 'md' and 'lg' viewports.
 * ```
 * PluginManager.removePlugin('.my-selector', 'pluginName', ['md', 'lg']);
 * ```
 */
(function($, document, window, viewports) {
    'use strict';

    /**
     * @class
     * @public
     */
    const PluginManager = function() {
        /** @type {Events} Events */
        this.Events = window.easytemplate.Events;
        this._plugins = {'xs': null, 'sm': null, 'md': null, 'lg': null, 'xl': null};
    };

    PluginManager.prototype = {
        constructor: PluginManager,

        initializedPlugins: {},

        /**
         * Initialize all registered plugins for the current viewport.
         *
         * @public
         * @returns {PluginManager}
         */
        init: function() {
            this.Events.publish('easytemplate/PluginManager/onInitStart', [this]);

            for (const viewport in this._plugins) {
                if (!viewports.is(viewport)) {
                    continue;
                }
                const plugins = this._plugins[viewport];
                if(plugins !== null) {
                    for (const plugin of plugins) {
                        // Skip invalid plugin format
                        if (!plugin.selector || !plugin.pluginName) {
                            console.warn('Not a valid plugin. Skipping.', plugin);
                            continue;
                        }

                        // Skip plugin if it already is initialized
                        // This is needed to avoid weird viewport lib bug where the current viewport
                        // is ALL the viewports which might break some plugins
                        const key = plugin.selector + ':' + plugin.pluginName;
                        if (key in this.initializedPlugins) {
                            continue;
                        }

                        // Initialize Plugin
                        this.initializedPlugins[key] = $(plugin.selector)[plugin.pluginName](
                            plugin.pluginName,
                            plugin.config || {}
                        );
                    }
                }
            }

            this.Events.publish('easytemplate/PluginManager/onInitEnd', [this]);

            return this;
        },

        /**
         * @public
         * @param {string} pluginClass
         */
        reinitialize: function(pluginClass) {
            this.Events.publish('easytemplate/PluginManager/onReInitStart', [this, pluginClass]);

            if (viewports.current() in this._plugins) {
                for (const plugin of this._plugins[viewports.current()]) {
                    // Skip invalid plugin format
                    if (!plugin.selector || !plugin.pluginName) {
                        console.warn('Not a valid plugin. Skipping.', plugin);
                        continue;
                    }

                    if (plugin.pluginName !== pluginClass) {
                        continue;
                    }

                    const key = plugin.selector + ':' + plugin.pluginName;
                    this.initializedPlugins[key] = $(plugin.selector)[plugin.pluginName](
                        plugin.pluginName,
                        plugin.config || {},
                        true
                    );
                }
            }

            this.Events.publish('easytemplate/PluginManager/onReInitEnd', [this, pluginClass]);
        },

        /**
         * @public
         * @method
         * @param {string} selector
         * @param {Object|PluginBase} pluginClass Class of the plugin to register, if not already registered
         * @param {string} pluginName Name of the jQuery Plugin
         * @param {Object|Array} [config] Optional config object. If omitted, it acts as viewport param.
         * @param {Array|String} [viewport] The Viewport for which the plugin should be registered.
         * @returns {PluginManager}
         */
        addPlugin: function(selector, pluginClass, pluginName, config, viewport) {
            // Make sure to register the plugin if not already done so.
            if (!$.fn.hasOwnProperty(pluginName)) {
                /** @type {PluginBase} base */
                const base = window.easytemplate.PluginBase;
                base.registerPlugin(pluginName, pluginClass);
            }

            // If the third parameter are the viewport states
            if (typeof config === 'string' || config instanceof Array) {
                viewport = config;
                config = {};
            }

            if (typeof viewport === 'string') {
                viewport = [viewport];
            }

            // Default viewports
            if (!(viewport instanceof Array)) {
                viewport = [];

                for (const state in viewports.breakpoints) {
                    viewport.push(state);
                }
            }

            // Add Plugin for viewports
            for (let i = 0, len = viewport.length; i < len; i++) {
                let selectors = this._plugins[viewport[i]] || (this._plugins[viewport[i]] = []);

                selectors.push({
                    selector: selector,
                    pluginName: pluginName,
                    config: config || {}
                });
            }

            this.Events.publish(
                'easytemplate/PluginManager/onAddedPlugin',
                [this, selector, pluginName, config, viewport]
            );

            return this;
        },

        /**
         * @public
         * @method
         * @param {string} selector
         * @param {string} pluginName Name of the jQuery Plugin
         * @param {Array|String} [viewport] The Viewport from which the plugin should be removed.
         * @returns {PluginManager}
         */
        removePlugin: function(selector, pluginName, viewport) {
            if (typeof viewport === 'string') {
                viewport = [viewport];
            }

            // Default viewports
            if (!(viewport instanceof Array)) {
                viewport = [];

                for (const state in viewports.breakpoints) {
                    viewport.push(state);
                }
            }

            for (let i = 0, len = viewport.length; i < len; i++) {
                console.log(viewport[i], selector, pluginName);
                let selectors = this._plugins[viewport[i]] || (this._plugins[viewport[i]] = []);
                const key = selectors.findIndex(function (el) {
                    return el.selector === selector && el.pluginName === pluginName;
                });

                this._plugins[viewport[i]].splice(key, 1);
            }

            this.Events.publish(
                'easytemplate/PluginManager/onRemovedPlugin',
                [this, selector, pluginName, viewport]
            );

            return this;
        }
    };

    // Expose to global namespace
    window.easytemplate = window.easytemplate || {};
    window.easytemplate.PluginManager = new PluginManager();

    // Initialize all registered plugins when DOM is ready
    // ATTENTION: jQuery 3 has a bug of some sorts that may lead to the document ready event being fire AFTER window load.
    // This means, all plugins that rely on the window.load event may fail to work under that condition as the event will not fire again and jQuery obviously does not check if it was fired before!
    // We therefore do NOT use jQuery's event handler here.
    // Another problem is that the ResponsiveBootstrapToolkit does things on $(document).ready which will NOT have run when the events occur in opposite order and will also not have run on DOMContentLoaded here, either.
    if(document.readyState === 'complete') {
        window.easytemplate.PluginManager.init();
    } else {
        window.addEventListener('DOMContentLoaded', (event) => {
            window.easytemplate.PluginManager.init();
        });
    }
})(jQuery, document, window, ResponsiveBootstrapToolkit);