/*!
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

function widgetsHelper() {
}

// a Promise for the first call to getAvailableWidgets. this should not be aborted,
// so any code that aborts all ajax requests should make sure this promise is resolved
// first.
widgetsHelper.firstGetAvailableWidgetsCall = null;

/**
 * Returns the available widgets fetched via AJAX (if not already done)
 *
 * @return {object} object containing available widgets
 */
widgetsHelper.getAvailableWidgets = function (callback) {

    function resetFirstGetAvailableWidgetsCall(promise) {
        if (widgetsHelper.firstGetAvailableWidgetsCall === promise) {
            widgetsHelper.firstGetAvailableWidgetsCall = null;
        }
    }

    if (!widgetsHelper.availableWidgets && widgetsHelper.firstGetAvailableWidgetsCall) {
        return widgetsHelper.firstGetAvailableWidgetsCall.then(function () {
            if (callback) {
                callback(widgetsHelper.availableWidgets);
            }
        });
    }

    function mergeCategoriesAndSubCategories(availableWidgets)
    {
        var categorized = {};

        $.each(availableWidgets, function (index, widget) {
            var category = widget.category.name;

            if (!categorized[category]) {
                categorized[category] = {'-': []};
            }

            var subcategory = '-';
            if (widget.subcategory && widget.subcategory.name) {
                subcategory = widget.subcategory.name;
            }

            if (!categorized[category][subcategory]) {
                categorized[category][subcategory] = [];
            }

            categorized[category][subcategory].push(widget);
        });

        var moved = {};

        $.each(categorized, function (category, widgets) {
            $.each(widgets, function (subcategory, subwidgets) {
                if (!subwidgets.length) {
                  return;
                }

                var categoryToUse = category;
                if (subwidgets.length >= 3 && subcategory !== '-') {
                    categoryToUse = category + ' - ' + subcategory;
                }

                if (!moved[categoryToUse]) {
                    moved[categoryToUse] = [];
                }

                $.each(subwidgets, function (index, widget) {
                    moved[categoryToUse].push(widget);
                });
            });
        });

        return moved;
    }

    var promise = new Promise(function (resolve, reject) {
      if (!widgetsHelper.availableWidgets) {
        var ajaxRequest = new ajaxHelper();
        ajaxRequest._mixinDefaultGetParams = function (params) {
          return params;
        };
        ajaxRequest.addParams({
          module: 'API',
          method: 'API.getWidgetMetadata',
          filter_limit: '-1',
          format: 'JSON',
          deep: '1',
          idSite:  piwik.idSite || broadcast.getValueFromUrl('idSite')
        }, 'get');
        ajaxRequest.setCallback(
          function (data) {
            widgetsHelper.availableWidgets = mergeCategoriesAndSubCategories(data);

            resetFirstGetAvailableWidgetsCall(promise);
            resolve();
          }
        );
        ajaxRequest.setErrorCallback(function (deferred, status) {
          resetFirstGetAvailableWidgetsCall(promise);

          if (status == 'abort') {
            reject(new Error('Loading widget metadata was aborted'));
            return;
          }

          if (!deferred || deferred.status < 400 || deferred.status >= 600) {
            reject(new Error('Loading widget metadata failed'));
            return;
          }

          $('#loadingError').show();
          reject(new Error('Loading widget metadata failed'));
        });
        ajaxRequest.send();
        return;
      }

      resolve();
    });

    if (!widgetsHelper.firstGetAvailableWidgetsCall) {
      widgetsHelper.firstGetAvailableWidgetsCall = promise;
    }

    return promise.then(function () {
      if (callback) {
        callback(widgetsHelper.availableWidgets);
      }
    });
};

/**
 * Clear the current collection of availableWidgets so that getAvailableWidgets() will load a fresh
 * collection from the API. This is useful when we added/deleted a report and need a refresh.
 */
widgetsHelper.clearAvailableWidgets = function () {
    delete widgetsHelper.availableWidgets;
    widgetsHelper.firstGetAvailableWidgetsCall = null;
};

/**
 * Determines the complete widget object by its unique id and sends it to callback method
 *
 * @param {string} uniqueId
 * @param {function} callback
 */
widgetsHelper.getWidgetObjectFromUniqueId = function (uniqueId, callback) {
    widgetsHelper.getAvailableWidgets(function(widgets){
        callback(widgetsHelper.findWidgetObjectFromUniqueId(uniqueId, widgets));
    });
};

widgetsHelper.findWidgetObjectFromUniqueId = function (uniqueId, widgets) {
    if (!widgets) {
        return false;
    }

    for (var widgetCategory in widgets) {
        var widgetInCategory = widgets[widgetCategory];
        for (var i in widgetInCategory) {
            if (widgetInCategory[i]["uniqueId"] == uniqueId) {
                return widgetInCategory[i];
            }
        }
    }

    return false;
};

/**
 * Determines the name of a widget by its unique id and sends it to the callback
 *
 * @param {string} uniqueId  unique id of the widget
 * @param {function} callback
 * @return {string}
 */
widgetsHelper.getWidgetNameFromUniqueId = function (uniqueId, callback) {
    this.getWidgetObjectFromUniqueId(uniqueId, function(widget) {
        if (widget == false) {
            callback(false);
        }
        callback(widget["name"]);
    });
};

/**
 * Sends an ajax request to query for the widget's html
 *
 * @param {string} widgetUniqueId             unique id of the widget
 * @param {object} widgetParameters           parameters to be used for loading the widget
 * @param {function} onWidgetLoadedCallback   callback to be executed after widget is loaded
 * @return {object}
 */
widgetsHelper.loadWidgetAjax = function (widgetUniqueId, widgetParameters, onWidgetLoadedCallback, onWidgetErrorCallback) {
    var disableLink = broadcast.getValueFromUrl('disableLink');
    widgetParameters['disableLink'] = disableLink.length || $('body#standalone').length;

    widgetParameters['widget'] = 1;

    var widgetRequest = {
        aborted: false,
        ajaxRequest: null,
        abort: function () {
            this.aborted = true;
            if (this.ajaxRequest) {
                this.ajaxRequest.abort();
            }
        }
    };

    function loadLegacyWidget() {
        if (widgetRequest.aborted) {
            return;
        }

        var ajaxRequest = new ajaxHelper();
        ajaxRequest.addParams(widgetParameters, 'get');
        ajaxRequest.setCallback(function () {
            if (widgetRequest.aborted) {
                return;
            }

            onWidgetLoadedCallback.apply(this, arguments);
        });
        if (onWidgetErrorCallback) {
            ajaxRequest.setErrorCallback(function () {
                if (widgetRequest.aborted) {
                    return;
                }

                onWidgetErrorCallback.apply(this, arguments);
            });
        }
        ajaxRequest.setFormat('html');
        ajaxRequest.send();
        widgetRequest.ajaxRequest = ajaxRequest;
    }

    var metadataReady = widgetsHelper.availableWidgets
        ? Promise.resolve()
        : widgetsHelper.getAvailableWidgets();

    metadataReady.then(function () {
        if (widgetRequest.aborted) {
            return;
        }

        var clientWidget = widgetsHelper.findWidgetObjectFromUniqueId(
            widgetUniqueId,
            widgetsHelper.availableWidgets
        );

        if (clientWidget && clientWidget.clientComponent) {
            clientWidget = $.extend(true, {}, clientWidget);
            clientWidget.parameters = $.extend({}, clientWidget.parameters, widgetParameters);

            var html = '<div vue-entry="CoreHome.Widget"'
                + ' widget="' + piwikHelper.htmlEntities(JSON.stringify(clientWidget)) + '"'
                + ' widgetized="true"></div>';
            onWidgetLoadedCallback(html);
            return;
        }

        loadLegacyWidget();
    }).catch(function () {
        loadLegacyWidget();
    });

    return widgetRequest;
};

(function ($, require) {
    var exports = require('piwik/UI/Dashboard');

    /**
     * Singleton instance that creates widget elements. Normally not needed even
     * when embedding/re-using dashboard widgets, but it can be useful when creating
     * elements with the same look and feel as dashboard widgets, but different
     * behavior (such as the widget preview in the dashboard manager control).
     *
     * @constructor
     */
    var WidgetFactory = function () {
        // empty
    };

    /**
     * Creates an HTML element for displaying a widget.
     *
     * @param {string} uniqueId     unique id of the widget
     * @param {string} widgetName   name of the widget
     * @return {Element} the empty widget
     */
    WidgetFactory.prototype.make = function (uniqueId, widgetName) {
        var $result = this.getWidgetTemplate().clone();
        $result.attr('id', uniqueId).find('.widgetName').append(widgetName);
        return $result;
    };

    /**
     * Returns the base widget template element. The template is stored in the
     * element with id == 'widgetTemplate'.
     *
     * @return {Element} the widget template
     */
    WidgetFactory.prototype.getWidgetTemplate = function () {
        if (!this.widgetTemplate) {
            this.widgetTemplate = $('#widgetTemplate').find('>.widget').detach();
        }
        return this.widgetTemplate;
    };

    exports.WidgetFactory = new WidgetFactory();
})(jQuery, require);

/**
 * widgetPreview jQuery Extension
 *
 * Converts a dom element to a widget preview
 * Widget preview contains a categorylist, a widgetlist and a preview
 */
(function ($) {
    $.extend({
        widgetPreview: new function () {

            /**
             * Default settings for widgetPreview
             * @type {object}
             */
            var defaultSettings = {
                /**
                 * handler called after a widget preview is loaded in preview element
                 * @type {function}
                 */
                onPreviewLoaded: function () {},
                /**
                 * handler called on click on element in widgetlist or widget header
                 * @type {function}
                 */
                onSelect: function () {},
                /**
                 * callback used to determine if a widget is available or not
                 * unavailable widgets aren't selectable in widgetlist
                 * @type {function}
                 */
                isWidgetAvailable: function (widgetUniqueId) { return true; },
                /**
                 * should the lists and preview be reset on widget selection?
                 * @type {boolean}
                 */
                resetOnSelect: false,
                /**
                 * css classes for various elements
                 * @type {string}
                 */
                baseClass: 'widgetpreview-base',
                categorylistClass: 'widgetpreview-categorylist',
                widgetlistClass: 'widgetpreview-widgetlist',
                widgetpreviewClass: 'widgetpreview-preview',
                choosenClass: 'widgetpreview-choosen',
                unavailableClass: 'widgetpreview-unavailable'
            };

            /**
             * Returns the div to show category list in
             * - if element doesn't exist, it will be created and added
             * - if element already exists, its content will be removed
             *
             * @return {$} category list element
             */
            function createWidgetCategoryList(widgetPreview, availableWidgets) {

                var settings = widgetPreview.settings;

                if (!$('.' + settings.categorylistClass, widgetPreview).length) {
                    $(widgetPreview).append('<ul class="' + settings.categorylistClass + '"></ul>');
                } else {
                    $('.' + settings.categorylistClass, widgetPreview).empty();
                }

                for (var widgetCategory in availableWidgets) {
                    $('.' + settings.categorylistClass, widgetPreview).append($('<li>').text(widgetCategory));
                }

                return $('.' + settings.categorylistClass, widgetPreview);
            }

            /**
             * Returns the div to show widget list in
             * - if element doesn't exist, it will be created and added
             * - if element already exists, its content will be removed
             *
             * @return {$} widget list element
             */
            function createWidgetList(widgetPreview) {
                var settings = widgetPreview.settings;

                if (!$('.' + settings.widgetlistClass, widgetPreview).length) {
                    $(widgetPreview).append('<ul class="' + settings.widgetlistClass + '"></ul>');
                } else {
                    $('.' + settings.widgetlistClass + ' li', widgetPreview).off('mouseover');
                    $('.' + settings.widgetlistClass + ' li', widgetPreview).off('click');
                    $('.' + settings.widgetlistClass, widgetPreview).empty();
                }

                if ($('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).length) {
                    var position = $('.' + settings.categorylistClass + ' .' + settings.choosenClass, widgetPreview).position().top -
                        $('.' + settings.categorylistClass, widgetPreview).position().top;

                    $('.' + settings.widgetlistClass, widgetPreview).css({
                        top: position,
                        marginBottom: position + 10
                    });
                }

                return $('.' + settings.widgetlistClass, widgetPreview);
            }

            /**
             * Display the given widgets in a widget list
             *
             * @param {object} widgets widgets to be displayed
             * @return {void}
             */
            function showWidgetList(widgets, widgetPreview) {
                var settings = widgetPreview.settings;

                var widgetList = createWidgetList(widgetPreview),
                    widgetPreviewTimer;

                for (var j = 0; j < widgets.length; j++) {
                    var widgetName = widgets[j]["name"];
                    var widgetUniqueId = widgets[j]["uniqueId"];
                    var widgetCategoryId = widgets[j].category ? widgets[j].category.id : null;
                    var widgetClass = '';
                    if (!settings.isWidgetAvailable(widgetUniqueId) && widgetCategoryId !== 'General_KpiMetric') {
                        widgetClass += ' ' + settings.unavailableClass;
                    }

                    widgetName = piwikHelper.escape(piwikHelper.htmlEntities(widgetName));

                    widgetList.append('<li class="' + widgetClass + '" uniqueid="' + widgetUniqueId + '">' + widgetName + '</li>');
                }

                // delay widget preview a few milliseconds
                $('li', widgetList).on('mouseenter', function () {
                    var that = this,
                        widgetUniqueId = $(this).attr('uniqueid');
                    clearTimeout(widgetPreview);
                    widgetPreviewTimer = setTimeout(function () {
                        $('li', widgetList).removeClass(settings.choosenClass);
                        $(that).addClass(settings.choosenClass);

                        showPreview(widgetUniqueId, widgetPreview);
                    }, 400);
                });

                // clear timeout after mouse has left
                $('li:not(.' + settings.unavailableClass + ')', widgetList).on('mouseleave', function () {
                    clearTimeout(widgetPreview);
                });

                $('li', widgetList).on('click', function () {
                    if (!$('.widgetLoading', widgetPreview).length) {
                        settings.onSelect($(this).attr('uniqueid'));
                        if (settings.resetOnSelect) {
                            resetWidgetPreview(widgetPreview);
                        }
                    }
                    return false;
                });
            }

            /**
             * Returns the div to show widget preview in
             * - if element doesn't exist, it will be created and added
             * - if element already exists, its content will be removed
             *
             * @return {$} preview element
             */
            function createPreviewElement(widgetPreview) {
                var settings = widgetPreview.settings;

                if (!$('.' + settings.widgetpreviewClass, widgetPreview).length) {
                    $(widgetPreview).append('<div class="' + settings.widgetpreviewClass + '"></div>');
                } else {
                    $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).off('click');
                    $('.' + settings.widgetpreviewClass, widgetPreview).empty();
                }

                return $('.' + settings.widgetpreviewClass, widgetPreview);
            }

            /**
             * Show widget with the given uniqueId in preview
             *
             * @param {string} widgetUniqueId unique id of widget to display
             * @param          widgetPreview
             * @return {void}
             */
            function showPreview(widgetUniqueId, widgetPreview) {
                // do not reload id widget already displayed
                if ($('[id="' + widgetUniqueId + '"]', widgetPreview).length) return;

                var settings = widgetPreview.settings;

                var previewElement = createPreviewElement(widgetPreview);

                widgetsHelper.getWidgetObjectFromUniqueId(widgetUniqueId, function(widget) {
                    var widgetParameters = widget['parameters'];

                    var emptyWidgetHtml = require('piwik/UI/Dashboard').WidgetFactory.make(
                        widgetUniqueId,
                        $('<span/>')
                            .attr('title', _pk_translate("Dashboard_AddPreviewedWidget"))
                            .text(_pk_translate('Dashboard_WidgetPreview'))
                    );
                    previewElement.html(emptyWidgetHtml);

                    var onWidgetLoadedCallback = function (response) {
                        var widgetElement = previewElement.children('.widget').first();
                        $('.widgetContent', widgetElement).html($(response));
                        piwikHelper.compileVueEntryComponents($('.widgetContent', widgetElement));
                        // `widget:create` handlers should only rely on `.element`, the widget's
                        // root jQuery node. Dashboard widgets pass their full plugin instance;
                        // previews pass `{ element }` so handlers scope to this preview instead
                        // of grabbing the first matching node in the document.
                        $('.widgetContent', widgetElement).trigger('widget:create', [{ element: widgetElement }]);
                        settings.onPreviewLoaded(widgetUniqueId, widgetElement);
                        $('.' + settings.widgetpreviewClass + ' .widgetTop', widgetPreview).on('click', function () {
                            settings.onSelect(widgetUniqueId);
                            if (settings.resetOnSelect) {
                                resetWidgetPreview(widgetPreview);
                            }
                            return false;
                        });
                    };

                    // abort previous sent request
                    if (widgetPreview.widgetAjaxRequest) {
                        widgetPreview.widgetAjaxRequest.abort();
                    }

                    widgetPreview.widgetAjaxRequest = widgetsHelper.loadWidgetAjax(widgetUniqueId, widgetParameters, onWidgetLoadedCallback);
                });
            }

            /**
             * Reset function
             *
             * @return {void}
             */
            function resetWidgetPreview(widgetPreview) {
                var settings = widgetPreview.settings;

                $('.' + settings.categorylistClass + ' li', widgetPreview).removeClass(settings.choosenClass);
                createWidgetList(widgetPreview);
                createPreviewElement(widgetPreview);
            }

            /**
             * Constructor
             *
             * @param {object} userSettings Settings to be used
             * @return {void}
             */
            this.construct = function (userSettings) {

                if (userSettings == 'reset') {
                    resetWidgetPreview(this);
                    return;
                }

                this.widgetAjaxRequest = null;

                $(this).addClass('widgetpreview-base');

                this.settings = jQuery.extend({}, defaultSettings, userSettings);

                // set onSelect callback
                if (typeof this.settings.onSelect == 'function') {
                    this.onSelect = this.settings.onSelect;
                }

                // set onPreviewLoaded callback
                if (typeof this.settings.onPreviewLoaded == 'function') {
                    this.onPreviewLoaded = this.settings.onPreviewLoaded;
                }

                var self = this;
                widgetsHelper.getAvailableWidgets(function (availableWidgets) {

                    var categoryList = createWidgetCategoryList(self, availableWidgets);

                    $('li', categoryList).on('mouseover', function () {
                        var category = $(this).text();
                        var widgets = availableWidgets[category];
                        $('li', categoryList).removeClass(self.settings.choosenClass);
                        $(this).addClass(self.settings.choosenClass);
                        showWidgetList(widgets, self);
                        createPreviewElement(self); // empty preview
                    });
                });

            };
        }
    });

    /**
     * Makes widgetPreview available with $().widgetPreview()
     */
    $.fn.extend({
        widgetPreview: $.widgetPreview.construct
    })
})(jQuery);
