const ckEditors = new Map();
/**
 * Takes a given route, retrieves the HTML, and then updates the content
 *
 * @param route
 * @param link
 * @param method
 * @param target
 * @param showPageLoading
 * @param callback
 * @param data
 */
Mautic.loadContent = function (route, link, method, target, showPageLoading, callback, data) {
    if (typeof Mautic.loadContentXhr == 'undefined') {
        Mautic.loadContentXhr = {};
    } else if (typeof Mautic.loadContentXhr[target] != 'undefined') {
        Mautic.loadContentXhr[target].abort();
    }

    showPageLoading = (typeof showPageLoading == 'undefined' || showPageLoading) ? true : false;

    Mautic.loadContentXhr[target] = mQuery.ajax({
        showLoadingBar: showPageLoading,
        url: route,
        type: method,
        dataType: "json",
        data: data,
        success: function (response) {
            if (response) {
                response.stopPageLoading = showPageLoading;

                if (response.callback) {
                    window["Mautic"][response.callback].apply('window', [response]);
                    return;
                }
                if (response.redirect) {
                    Mautic.redirectWithBackdrop(response.redirect);
                } else if (target || response.target) {
                    if (target) response.target = target;
                    Mautic.processPageContent(response);
                } else {
                    //clear the live cache
                    MauticVars.liveCache = new Array();
                    MauticVars.lastSearchStr = '';

                    //set route and activeLink if the response didn't override
                    if (typeof response.route === 'undefined') {
                        response.route = route;
                    }

                    if (typeof response.activeLink === 'undefined' && link) {
                        response.activeLink = link;
                    }

                    Mautic.processPageContent(response);
                }

                //restore button class if applicable
                Mautic.stopIconSpinPostEvent();
            }
            MauticVars.routeInProgress = '';
        },
        error: function (request, textStatus, errorThrown) {
            Mautic.processAjaxError(request, textStatus, errorThrown, true);

            //clear routeInProgress
            MauticVars.routeInProgress = '';

            //restore button class if applicable
            Mautic.stopIconSpinPostEvent();

            //stop loading bar
            Mautic.stopPageLoadingBar();
        },
        complete: function () {
            if (typeof callback !== 'undefined') {
                if (typeof callback == 'function') {
                    callback();
                } else {
                    window["Mautic"][callback].apply('window', []);
                }
            }
            Mautic.generatePageTitle( route );
            delete Mautic.loadContentXhr[target];
        }
    });

    //prevent firing of href link
    //mQuery(link).attr("href", "javascript: void(0)");
    return false;
};

/**
 *  Load results with  ajax in batch mode
 *
 * @param elementName
 * @param route
 * @param callback
 */
Mautic.loadAjaxColumn = function(elementName, route, callback){
    var className = '.'+elementName;
    if (mQuery(className).length) {
        var ids = [];
        mQuery(className).each(function () {
            if(!mQuery(this).text()) {
                var id = mQuery(this).attr('data-value');
                ids.push(id);
            }
        });

        var batchIds;

        // If not gonna load any data, then just callback
        if(ids.length == 0) {
            Mautic.getCallback(callback);
        }

        // Get all stats numbers in batches of 10
        while (ids.length > 0) {
            batchIds = ids.splice(0, 10);
            Mautic.ajaxActionRequest(
                route,
                {ids: batchIds, entityId: Mautic.getEntityId()},
                function (response) {
                    if (response.success && response.stats) {
                        for (var i = 0; i < response.stats.length; i++) {
                            var stat = response.stats[i];
                            if (mQuery('#' + elementName + '-' + stat.id).length) {
                                mQuery('#' + elementName + '-' + stat.id).html(stat.data);
                            }
                        }
                        if(batchIds.length < 10) {
                            Mautic.getCallback(callback);
                        }
                    }
                },
                false,
                true,
                "GET"
            );
        }
    }
}

/**
 *  Sort table by column
 *
 * @param tableId
 * @param sortElement
 */
Mautic.sortTableByColumn = function(tableId, sortElement, removeZero){
    var tbody = mQuery(tableId).find('tbody');
    tbody.find('tr').each(function () {
        if(parseInt(mQuery(this).find(sortElement).text()) == 0) {
            mQuery(this).remove();
        }
    })
    tbody.find('tr').sort(function(a, b) {
        var tda = parseFloat(mQuery(a).find(sortElement).text()); // target order attribute
        var tdb = parseFloat(mQuery(b).find(sortElement).text()); // target order attribute
        // if a < b return 1
        return tda < tdb ? 1
            // else if a > b return -1
            : tda > tdb ? -1
            // else they are equal - return 0
            : 0;
    }).appendTo(tbody);
}

/**
 * @param callback
 */
Mautic.getCallback = function (callback) {
    if (callback && typeof callback !== 'undefined') {
        if (typeof callback == 'function') {
            callback();
        } else {
            window["Mautic"][callback].apply('window', []);
        }
    }
}


/**
 * Generates the title of the current page
 *
 * @param route
 */
Mautic.generatePageTitle = function(route){

    if (-1 !== route.indexOf('timeline')) {
        return
    } else if (-1 !== route.indexOf('/view')) {
        //loading view of module title
        var currentModule = route.split('/')[3];

        //check if we find spans
        var titleWithHTML = mQuery('.page-header h1').find('span.span-block');
        var currentModuleItem = '';

        if( 1 < titleWithHTML.length ){
            currentModuleItem = titleWithHTML.eq(0).text() + ' - ' + titleWithHTML.eq(1).text();
        } else {
            currentModuleItem = mQuery('.page-header h1').text();
        }

        // Safely set the text content to prevent XSS
        currentModuleItem = mQuery('<div>').text(currentModuleItem).html();

        mQuery('title').html( currentModule[0].toUpperCase() + currentModule.slice(1) + ' | ' + currentModuleItem + ' | Mautic' );
    } else {
        //loading basic title
        mQuery('title').html( mQuery('.page-header h1').text() + ' | Mautic' );
    }
};

/**
 * Updates new content
 * @param response
 */
Mautic.processPageContent = function (response) {
    if (response) {
        Mautic.deactivateBackgroup();

        if (response.errors && 'dev' == mauticEnv) {
            alert(response.errors[0].message);
            console.log(response.errors);
        }

        if (!response.target) {
            response.target = '#app-content';
        }

        //inactive tooltips, etc
        Mautic.onPageUnload(response.target, response);

        //set content
        if (response.newContent) {
            if (response.replaceContent && response.replaceContent == 'true') {
                mQuery(response.target).replaceWith(response.newContent);
            } else {
                mQuery(response.target).html(response.newContent);
            }
        }

        if (response.notifications) {
            Mautic.setNotifications(response.notifications);
        }

        if (response.route) {
            //update URL in address bar
            history.pushState(null, "Mautic", response.route);

            //update Title
            Mautic.generatePageTitle( response.route );
        }

        if (response.target == '#app-content') {
            //update type of content displayed
            if (response.mauticContent) {
                mauticContent = response.mauticContent;
            }

            if (response.activeLink) {
                var link = response.activeLink;
                if (link !== undefined && link.charAt(0) != '#') {
                    link = "#" + link;
                }

                var parent = mQuery(link).parent();

                //remove current classes from menu items
                mQuery(".nav-sidebar").find(".active").removeClass("active");

                //add current to parent <li>
                parent.addClass("active");

                //get parent
                var openParent = parent.closest('li.open');

                //remove ancestor classes
                mQuery(".nav-sidebar").find(".open").each(function () {
                    if (!openParent.hasClass('open') || (openParent.hasClass('open') && openParent[0] !== mQuery(this)[0])) {
                        mQuery(this).removeClass('open');
                    }
                });

                //add current_ancestor classes
                //mQuery(parent).parentsUntil(".nav-sidebar", "li").addClass("current_ancestor");
            }

            mQuery('body').animate({
                scrollTop: 0
            }, 0);

        } else {
            var overflow = mQuery(response.target).css('overflow');
            var overflowY = mQuery(response.target).css('overflowY');
            if (overflow == 'auto' || overflow == 'scroll' || overflowY == 'auto' || overflowY == 'scroll') {
                mQuery(response.target).animate({
                    scrollTop: 0
                }, 0);
            }
        }

        if (response.overlayEnabled) {
            mQuery(response.overlayTarget + ' .content-overlay').remove();
        }

        //activate content specific stuff
        Mautic.onPageLoad(response.target, response);
    }
};

/**
 * Initiate various functions on page load, manual or ajax
 */
Mautic.onPageLoad = function (container, response, inModal) {
    Mautic.initDateRangePicker(container + ' #daterange_date_from', container + ' #daterange_date_to');

    //initiate links
    Mautic.makeLinksAlive(mQuery(container + " a[data-toggle='ajax']"));

    //initialize forms
    mQuery(container + " form[data-toggle='ajax']").each(function (index) {
        Mautic.ajaxifyForm(mQuery(this).attr('name'));
    });

    //initialize ajax'd modals
    Mautic.makeModalsAlive(mQuery(container + " *[data-toggle='ajaxmodal']"))

    //initialize embedded modal forms
    Mautic.activateModalEmbeddedForms(container);

    //initalize live search boxes
    mQuery(container + " *[data-toggle='livesearch']").each(function (index) {
        Mautic.activateLiveSearch(mQuery(this), "lastSearchStr", "liveCache");
    });

    //initialize tooltips
    var pageTooltips = mQuery(container + " *[data-toggle='tooltip']");
    pageTooltips.tooltip({html: true, container: 'body'});

    // Enable tooltips on checkbox & radio input's to
    // show when hovering their parent LABEL element
    pageTooltips.each(function(i) {
        var thisTooltip   = mQuery(pageTooltips.get(i));
        var elementParent = thisTooltip.parent();

        if (elementParent.get(0).tagName === 'LABEL') {
            elementParent.append('<i class="ri-question-line"></i>');

            elementParent.hover(function () {
                thisTooltip.tooltip('show')
            }, function () {
                thisTooltip.tooltip('hide');
            });
        }
    });


    //initialize sortable lists
    mQuery(container + " *[data-toggle='sortablelist']").each(function (index) {
        Mautic.activateSortable(this);
    });

    //downloads
    mQuery(container + " a[data-toggle='download']").off('click.download');
    mQuery(container + " a[data-toggle='download']").on('click.download', function (event) {
        event.preventDefault();

        Mautic.initiateFileDownload(mQuery(this).attr('href'));
    });

    Mautic.makeConfirmationsAlive(mQuery(container + " a[data-toggle='confirmation']"));

    //initialize date/time
    mQuery(container + " *[data-toggle='datetime']").each(function() {
        Mautic.activateDateTimeInputs(this, 'datetime');
    });

    mQuery(container + " *[data-toggle='date']").each(function() {
        Mautic.activateDateTimeInputs(this, 'date');
    });

    mQuery(container + " *[data-toggle='time']").each(function() {
        Mautic.activateDateTimeInputs(this, 'time');
    });

    // Initialize callback options
    mQuery(container + " *[data-onload-callback]").each(function() {
        var callback = function(el) {
            if (typeof window["Mautic"][mQuery(el).attr('data-onload-callback')] == 'function') {
                window["Mautic"][mQuery(el).attr('data-onload-callback')].apply('window', [el]);
            }
        }

        mQuery(document).ready(callback(this));
    });


    mQuery(container + " input[data-toggle='color']").each(function() {
        Mautic.activateColorPicker(this);
    });

    mQuery(container + " select").not('.multiselect, .not-chosen').each(function() {
        Mautic.activateChosenSelect(this);
    });

    mQuery(container + " select.multiselect").each(function() {
        Mautic.activateMultiSelect(this);
    });

    Mautic.activateLookupTypeahead(mQuery(container));

    // Fix dropdowns in responsive tables - https://github.com/twbs/bootstrap/issues/11037#issuecomment-163746965
    mQuery(container + " .table-responsive").on('shown.bs.dropdown', function (e) {
        var table = mQuery(this),
            menu = mQuery(e.target).find(".dropdown-menu"),
            tableOffsetHeight = table.offset().top + table.height(),
            menuOffsetHeight = menu.offset().top + menu.outerHeight(true);

        if (menuOffsetHeight > tableOffsetHeight)
            table.css("padding-bottom", menuOffsetHeight - tableOffsetHeight + 16)
    });
    mQuery(container + " .table-responsive").on("hide.bs.dropdown", function () {
        mQuery(this).css("padding-bottom", 0);
    })

    //initialize tab/hash activation
    mQuery(container + " .nav-tabs[data-toggle='tab-hash']").each(function() {
        // Show tab based on hash
        var hash  = document.location.hash;
        var prefix = 'tab-';

        if (hash) {
            var hashPieces = hash.split('?');
            hash           = hashPieces[0].replace("#", "#" + prefix);
            var activeTab  = mQuery(this).find('a[href=' + hash + ']').first();

            if (mQuery(activeTab).length) {
                mQuery('.nav-tabs li').removeClass('active');
                mQuery('.tab-pane').removeClass('in active');
                mQuery(activeTab).parent().addClass('active');
                mQuery(hash).addClass('in active');
            }
        }

        mQuery(this).find('a').on('shown.bs.tab', function (e) {
            window.location.hash = e.target.hash.replace("#" + prefix, "#");
        });
    });

    // Initialize tab overflow
    mQuery(container + " .nav-overflow-tabs ul").each(function() {
        Mautic.activateOverflowTabs(this);
    });

    mQuery(container + " .nav.sortable").each(function() {
        Mautic.activateSortableTabs(this);
    });

    // Initialize tab delete buttons
    Mautic.activateTabDeleteButtons(container);

    //spin icons on button click
    mQuery(container + ' .btn:not(.btn-nospin)').on('click.spinningicons', function (event) {
        Mautic.startIconSpinOnEvent(event);
    });

    mQuery(container + ' input[class=list-checkbox]').on('change', function () {
        var disabled = Mautic.batchActionPrecheck(container) ? false : true;
        var color    = (disabled) ? 'btn-ghost' : 'btn-info';
        var button   = container + ' th.col-actions .btn.dropdown-toggle';
        mQuery(button).prop('disabled', disabled);
        mQuery(button).removeClass('btn-ghost btn-info').addClass(color);
    });

    //Copy form buttons to the toolbar
    mQuery(container + " .bottom-form-buttons").each(function() {
        if (inModal || mQuery(this).closest('.modal').length) {
            var modal = (inModal) ? container : mQuery(this).closest('.modal');
            if (mQuery(modal).find('.modal-form-buttons').length) {
                //hide the bottom buttons
                mQuery(modal).find('.bottom-form-buttons').addClass('hide');
                var buttons = mQuery(modal).find('.bottom-form-buttons').html();

                //make sure working with a clean slate
                mQuery(modal).find('.modal-form-buttons').html('');

                mQuery(buttons).filter("button").each(function (i, v) {
                    //get the ID
                    var id = mQuery(this).attr('id');
                    var button = mQuery("<button type='button' />")
                        .addClass(mQuery(this).attr('class'))
                        .addClass('btn-copy')
                        .html(mQuery(this).html())
                        .appendTo(mQuery(modal).find('.modal-form-buttons'))
                        .on('click.ajaxform', function (event) {
                            if (mQuery(this).hasClass('disabled')) {

                                return false;
                            }

                            // Disable the form buttons until this action is complete
                            if (!mQuery(this).hasClass('btn-dnd')) {
                                mQuery(this).parent().find('button').prop('disabled', true);
                            }

                            event.preventDefault();
                            if (!mQuery(this).hasClass('btn-nospin')) {
                                Mautic.startIconSpinOnEvent(event);
                            }
                            mQuery('#' + id).click();
                        });
                });
            }
        } else {
            //hide the toolbar actions if applicable
            mQuery('.toolbar-action-buttons').addClass('hide');

            if (mQuery('.toolbar-form-buttons').hasClass('hide')) {
                //hide the bottom buttons
                mQuery(container + ' .bottom-form-buttons').addClass('hide');
                var buttons = mQuery(container + " .bottom-form-buttons").html();

                //make sure working with a clean slate
                mQuery(container + ' .toolbar-form-buttons .toolbar-standard').html('');
                mQuery(container + ' .toolbar-form-buttons .toolbar-dropdown .drop-menu').html('');

                var lastIndex = mQuery(buttons).filter("button").length - 1;
                mQuery(buttons).filter("button").each(function (i, v) {
                    //get the ID
                    var id = mQuery(this).attr('id');

                    var buttonClick = function (event) {
                        event.preventDefault();

                        // Disable the form buttons until this action is complete
                        if (!mQuery(this).hasClass('btn-dnd')) {
                            mQuery(this).parent().find('button').prop('disabled', true);
                        }

                        Mautic.startIconSpinOnEvent(event);
                        mQuery('#' + id).click();
                    };

                    mQuery("<button type='button' />")
                        .addClass(mQuery(this).attr('class'))
                        .addClass('btn-copy')
                        .attr('id', mQuery(this).attr('id') + '_toolbar')
                        .html(mQuery(this).html())
                        .on('click.ajaxform', buttonClick)
                        .appendTo('.toolbar-form-buttons .toolbar-standard');

                    if (i === lastIndex) {
                        mQuery(".toolbar-form-buttons .toolbar-dropdown .btn-main")
                            .off('.ajaxform')
                            .attr('id', mQuery(this).attr('id') + '_toolbar_mobile')
                            .html(mQuery(this).html())
                            .on('click.ajaxform', buttonClick);
                    } else {
                        mQuery("<a />")
                            .attr('id', mQuery(this).attr('id') + '_toolbar_mobile')
                            .html(mQuery(this).html())
                            .on('click.ajaxform', buttonClick)
                            .appendTo(mQuery('<li />').prependTo('.toolbar-form-buttons .toolbar-dropdown .dropdown-menu'))
                    }

                });
                mQuery('.toolbar-form-buttons').removeClass('hide');
            }
        }
    });
    Mautic.activateGlobalFroalaOptions();
    Mautic.getBuilderContainer = function() {
        return container;
    }

    // This turns all textarea elements with class "editor" into CKEditor ones, except for Dynamic Content elements, which can be initialized with Mautic.setDynamicContentEditors().
    if (mauticFroalaEnabled && mQuery(container + ' textarea.editor:not(".editor-dynamic-content")').length && Mautic.getActiveBuilderName() === 'legacy') {
        mQuery(container + ' textarea.editor:not(".editor-dynamic-content")').each(function () {
            mQuery(this).froalaEditor();
        });
    } else if (mQuery(container + ' textarea.editor:not(".editor-dynamic-content")').length) {
        mQuery(container + ' textarea.editor:not(".editor-dynamic-content")').each(function () {
            const textarea = mQuery(this);

            const maxButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'heading', 'fontfamily', 'fontsize', 'fontColor', 'fontBackgroundColor', 'alignment', 'numberedList', 'bulletedList', 'blockQuote', 'TokenPlugin', 'removeFormat', 'link', 'ckfinder', 'mediaEmbed', 'insertTable', 'sourceEditing'];
            let minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline'];

            if (textarea.hasClass('editor-dynamic-content') || textarea.hasClass('editor-basic')) {
                minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'heading', 'fontfamily', 'fontsize', 'fontColor', 'fontBackgroundColor', 'alignment', 'numberedList', 'bulletedList', 'blockQuote', 'removeFormat', 'link', 'ckfinder', 'mediaEmbed', 'insertTable', 'sourceEditing'];
            }

            let ckEditorToolbar = minButtons;
            if (textarea.hasClass('editor-advanced') || textarea.hasClass('editor-basic-fullpage')) {
                ckEditorToolbar = maxButtons;
            }

            Mautic.ConvertFieldToCkeditor(textarea, ckEditorToolbar);
        });
    }

    //prevent auto closing dropdowns for dropdown forms
    if (mQuery(container + ' .dropdown-menu-form').length) {
        mQuery(container + ' .dropdown-menu-form').on('click', function (e) {
            e.stopPropagation();
        });
    }

    if (response && response.updateSelect && typeof response.id !== 'undefined') {
        // An new item is to be injected
        Mautic.updateEntitySelect(response);
    }

    //run specific on loads
    var contentSpecific = false;
    if (response && response.mauticContent) {
        contentSpecific = response.mauticContent;
    } else if (container == 'body') {
        contentSpecific = mauticContent;
    }

    if (response && response.sidebar) {
        var sidebarContent = mQuery('.app-sidebar.sidebar-left');
        var newSidebar     = mQuery(response.sidebar);
        var nav            = sidebarContent.find('li');

        if (nav.length) {
            var openNavIndex;

            nav.each(function(i, el) {
                var $el = mQuery(el);

                if ($el.hasClass('open')) {
                    openNavIndex = i;
                }
            });

            var openNav = mQuery(newSidebar.find('li')[openNavIndex]);

            openNav.addClass('open');
            openNav.find('ul').removeClass('collapse');
        }

        sidebarContent.html(newSidebar);
    }

    if (container == '#app-content' || container == 'body') {
        //register global keyboard shortcuts
        Mautic.bindGlobalKeyboardShortcuts();

        mQuery(".sidebar-left a[data-toggle='ajax']").on('click.ajax', function (event) {
            mQuery("html").removeClass('sidebar-open-ltr');
        });
    }

    if (contentSpecific && typeof Mautic[contentSpecific + "OnLoad"] == 'function') {
        if (inModal || typeof Mautic.loadedContent[contentSpecific] == 'undefined') {
            Mautic.loadedContent[contentSpecific] = true;
            Mautic[contentSpecific + "OnLoad"](container, response);
        }
    }

    if (!inModal && container == 'body') {
        //prevent notification dropdown from closing if clicking an action
        mQuery('#notificationsDropdown').on('click', function (e) {
            if (mQuery(e.target).hasClass('do-not-close')) {
                e.stopPropagation();
            }
        });

        const $modal = mQuery('#gsearchModal'),
        $input = mQuery('#globalSearchInput'),
        $results = mQuery('.gsearch--results'),
        $panel = mQuery('#globalSearchPanel');

        $input.on('change keyup paste', function () {
            const hasValue = mQuery(this).val();
            $results.toggleClass('hide', !hasValue);
            if (!hasValue) $panel.empty();
        });

        Mautic.activateLiveSearch("#globalSearchInput", "lastGlobalSearchStr", "globalLivecache");

        $modal
            .on('shown.bs.modal', () => setTimeout(() => $input.focus(), 100))
            .on('hidden.bs.modal', () => {
                $input.val('');
                $results.addClass('hide');
                $panel.empty();
            });

        $results.on('click', 'a', () => $modal.modal('hide'));
    }

    Mautic.renderCharts(container);
    Mautic.stopIconSpinPostEvent();

    //stop loading bar
    if ((response && typeof response.stopPageLoading != 'undefined' && response.stopPageLoading) || container == '#app-content' || container == '.page-list') {
        Mautic.stopPageLoadingBar();
    }

    const maps= mQuery(container).find('[data-load="map"]');
    if(maps.length) {
        maps.each((index,map) => map.addEventListener('click', () => {
            const scopeId = event.target.getAttribute('href');
            const scope = mQuery(scopeId);

            if (scope.length) {
                // Check if map is not already rendered
                if (scope.children('.map-rendered').length) {
                    return;
                }

                // Loaded via AJAX not to block loading a whole page
                const mapUrl = scope.attr('data-map-url');

                scope.load(mapUrl, '', () => {
                    const map = Mautic.initMap(scope, 'regions');
                });
            }
        }, false));
    }
};

Mautic.setDynamicContentEditors = function(container) {
    // The editor for dynamic content should only be initialized when the modal is opened due to conflicts and not being able to edit content otherwise
    if (mauticFroalaEnabled && mQuery(container + ' textarea.editor-dynamic-content').length && Mautic.getActiveBuilderName() === 'legacy') {
        console.log('[Builder] Using Froala for the Dynamic Content editor (legacy)');
        mQuery(container + ' textarea.editor-dynamic-content').each(function () {
            mQuery(this).froalaEditor();
        });
    } else if (mQuery(container + ' textarea.editor-dynamic-content').length) {
        console.log('[Builder] Using CKEditor for the Dynamic Content editor');
        mQuery(container + ' textarea.editor-dynamic-content').each(function () {
            const textarea = mQuery(this);
            const maxButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'heading', 'fontfamily', 'fontsize', 'fontColor', 'fontBackgroundColor', 'alignment', 'numberedList', 'bulletedList', 'blockQuote', 'removeFormat', 'link', 'ckfinder', 'mediaEmbed', 'insertTable', 'TokenPlugin', 'sourceEditing'];
            let minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline'];

            if (textarea.hasClass('editor-dynamic-content') || textarea.hasClass('editor-basic')) {
                minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'heading', 'fontfamily', 'fontsize', 'fontColor', 'fontBackgroundColor', 'alignment', 'numberedList', 'bulletedList', 'blockQuote', 'removeFormat', 'link', 'ckfinder', 'mediaEmbed', 'insertTable', 'sourceEditing'];
            }

            let ckEditorToolbar = minButtons;
            if (textarea.hasClass('editor-advanced') || textarea.hasClass('editor-basic-fullpage')) {
                ckEditorToolbar = maxButtons;
            }

            Mautic.ConvertFieldToCkeditor(textarea, ckEditorToolbar);
        });
    }
}

/**
 * @param jQueryObject
 */
Mautic.activateLookupTypeahead = function(containerEl) {
    containerEl.find("*[data-toggle='field-lookup']").each(function () {
        var lookup = mQuery(this),
            callback = lookup.attr('data-callback') ? lookup.attr('data-callback') : 'activateFieldTypeahead';

        Mautic[callback](
            lookup.attr('id'),
            lookup.attr('data-target'),
            lookup.attr('data-options'),
            lookup.attr('data-action')
        );
    });
};

/**
 * @param jQueryObject
 */
Mautic.makeConfirmationsAlive = function(jQueryObject) {
    jQueryObject.off('click.confirmation');
    jQueryObject.on('click.confirmation', function (event) {
        event.preventDefault();
        MauticVars.ignoreIconSpin = true;
        return Mautic.showConfirmation(this);
    });
};

/**
 *
 * @param jQueryObject
 */
Mautic.makeModalsAlive = function(jQueryObject) {
    jQueryObject.off('click.ajaxmodal');
    jQueryObject.on('click.ajaxmodal', function (event) {
        event.preventDefault();

        Mautic.ajaxifyModal(this, event);
    });
};

/**
 *
 * @param jQueryObject
 */
Mautic.makeLinksAlive = function(jQueryObject) {
    jQueryObject.off('click.ajax');
    jQueryObject.on('click.ajax', function (event) {
        event.preventDefault();

        return Mautic.ajaxifyLink(this, event);
    });
};

/**
 * Functions to be ran on ajax page unload
 */
Mautic.onPageUnload = function (container, response) {
    //unload tooltips so they don't double show
    if (typeof container != 'undefined') {
        mQuery(container + " *[data-toggle='tooltip']").tooltip('destroy');

        //unload lingering modals from body so that there will not be multiple modals generated from new ajaxed content
        if (typeof MauticVars.modalsReset == 'undefined') {
            MauticVars.modalsReset = {};
        }

        if (mauticFroalaEnabled && Mautic.getActiveBuilderName() === 'legacy') {
            mQuery('textarea').froalaEditor('destroy');
        } else {
            if (ckEditors.size > 0) {
                ckEditors.forEach(function(value, key, map){
                    map.get(key).destroy()
                })
                ckEditors.clear();
            }
        }

        mQuery(container + " input[data-toggle='color']").each(function() {
            mQuery(this).minicolors('destroy');
        });
    }

    //run specific unloads
    var contentSpecific = false;
    if (container == '#app-content') {
        //full page gets precedence
        Mousetrap.reset();

        contentSpecific = mauticContent;

        // trash created chart objects to save some memory
        if (typeof Mautic.chartObjects !== 'undefined') {
            mQuery.each(Mautic.chartObjects, function (i, chart) {
                chart.destroy();
            });
            Mautic.chartObjects = [];
        }

        // trash created map objects to save some memory
        if (typeof Mautic.mapObjects !== 'undefined') {
            mQuery.each(Mautic.mapObjects, (i, map) => {
                map.destroyMap();
            });
            Mautic.mapObjects = [];
        }

        // trash tokens to save some memory
        if (typeof Mautic.builderTokens !== 'undefined') {
            Mautic.builderTokens = {};
        }
    } else if (response && response.mauticContent) {
        contentSpecific = response.mauticContent;
    }

    if (contentSpecific) {
        if (typeof Mautic[contentSpecific + "OnUnload"] == 'function') {
            Mautic[contentSpecific + "OnUnload"](container, response);
        }

        if (typeof Mautic.loadedContent[contentSpecific] !== 'undefined') {
            delete Mautic.loadedContent[contentSpecific];
        }
    }
};

/**
 * Retrieves content of href via ajax
 * @param el
 * @param event
 * @returns {boolean}
 */
Mautic.ajaxifyLink = function (el, event) {
    if (mQuery(el).hasClass('disabled')) {
        return false;
    }

    var route = mQuery(el).attr('href');
    if (route.indexOf('javascript') >= 0 || MauticVars.routeInProgress === route) {
        return false;
    }

    if (route.indexOf('batchExport') >= 0) {
        Mautic.initiateFileDownload(route);
        return true;
    }

    if (event.ctrlKey || event.metaKey) {
        //open the link in a new window
        route = route.split("?")[0];
        window.open(route, '_blank');
        return;
    }

    //prevent leaving if currently in a form
    if (mQuery(".form-exit-unlock-id").length) {
        if (mQuery(el).attr('data-ignore-formexit') != 'true') {
            var unlockParameter = (mQuery('.form-exit-unlock-parameter').length) ? mQuery('.form-exit-unlock-parameter').val() : '';
            Mautic.unlockEntity(mQuery('.form-exit-unlock-model').val(), mQuery('.form-exit-unlock-id').val(), unlockParameter);
        }
    }

    var link = mQuery(el).attr('data-menu-link');
    if (link !== undefined && link.charAt(0) != '#') {
        link = "#" + CSS.escape(link);
    }

    var method = mQuery(el).attr('data-method');
    if (!method) {
        method = 'GET'
    }

    MauticVars.routeInProgress = route;

    var target = mQuery(el).attr('data-target');
    if (!target) {
        target = null;
    }

    //give an ajaxified link the option of not displaying the global loading bar
    var showLoadingBar = (mQuery(el).attr('data-hide-loadingbar')) ? false : true;

    Mautic.loadContent(route, link, method, target, showLoadingBar);
};

/**
 * Convert to chosen select
 *
 * @param el
 */
Mautic.activateChosenSelect = function(el, ignoreGlobal, jQueryVariant) {
    var mQuery = (typeof jQueryVariant != 'undefined') ? jQueryVariant : window.mQuery;
    if (mQuery(el).parents('.no-chosen').length && !ignoreGlobal) {
        // Globally ignored chosens because they are handled manually due to hidden elements, etc
        return;
    }

    var noResultsText = mQuery(el).data('no-results-text');
    if (!noResultsText) {
        noResultsText = mauticLang['chosenNoResults'];
    }

    var isLookup = mQuery(el).attr('data-chosen-lookup');

    if (isLookup) {
        if (mQuery(el).attr('data-new-route')) {
            // Register method to initiate new
            mQuery(el).on('change', function () {
                var url = mQuery(el).attr('data-new-route');
                // If the element is already in a modal then use a popup
                if (mQuery(el).val() == 'new' && (mQuery(el).attr('data-popup') == "true" || mQuery(el).closest('.modal').length > 0)) {
                    var queryGlue = url.indexOf('?') >= 0 ? '&' : '?';
                    // De-select the new select option
                    mQuery(el).find('option[value="new"]').prop('selected', false);
                    mQuery(el).trigger('chosen:updated');

                    Mautic.loadNewWindow({
                        "windowUrl": url + queryGlue + "contentOnly=1&updateSelect=" + mQuery(el).attr('id')
                    });
                } else {
                    Mautic.loadAjaxModalBySelectValue(this, 'new', url, mQuery(el).attr('data-header'));
                }
            });
        }

        var multiPlaceholder = mauticLang['mautic.core.lookup.search_options'],
            singlePlaceholder = mauticLang['mautic.core.lookup.search_options'];
    } else {
        var multiPlaceholder = mauticLang['chosenChooseMore'],
            singlePlaceholder = mauticLang['chosenChooseOne'];
    }

    if (typeof mQuery(el).data('chosen-placeholder') !== 'undefined') {
        multiPlaceholder = singlePlaceholder = mQuery(el).data('chosen-placeholder');
    }

    mQuery(el).chosen({
        placeholder_text_multiple: multiPlaceholder,
        placeholder_text_single: singlePlaceholder,
        no_results_text: noResultsText,
        width: "100%",
        allow_single_deselect: true,
        include_group_label_in_selected: true,
        search_contains: true
    });

    if (isLookup) {
        var searchTerm = mQuery(el).attr('data-model');

        if (searchTerm) {
            mQuery(el).ajaxChosen({
                type: 'GET',
                url: mauticAjaxUrl + '?action=' + mQuery(el).attr('data-chosen-lookup'),
                dataType: 'json',
                afterTypeDelay: 2,
                minTermLength: 2,
                jsonTermKey: searchTerm,
                keepTypingMsg: "Keep typing...",
                lookingForMsg: "Looking for"
            });
        }
    }
};

/**
 * Check and destroy chosen select
 *
 * @param el
 */
Mautic.destroyChosen = function(el) {
    if(el.get(0)) {
        var eventObject = mQuery._data(el.get(0), 'events');
    }

    // Check if object has chosen event
    if (eventObject !== undefined && eventObject['chosen:activate'] !== undefined) {
        el.chosen('destroy');
        el.off('chosen:activate chosen:close chosen:open chosen:updated'); //Clear chosen events because chosen('destroy') doesn't
    }
};

/**
 * Activate a typeahead lookup
 */
Mautic.activateFieldTypeahead = function (field, target, options, action) {
    var fieldId = '#' + field;
    var fieldEl = mQuery('#' + field);

    if (fieldEl.length && fieldEl.parent('.twitter-typeahead').length) {
        return; // If the parent exist then the typeahead was already initialized. Abort.
    }

    if (options && typeof options === 'String') {
        var keys = values = [];

        options = options.split('||');
        if (options.length == 2) {
            keys = options[1].split('|');
            values = options[0].split('|');
        } else {
            values = options[0].split('|');
        }

        var fieldTypeahead = Mautic.activateTypeahead(fieldId, {
            dataOptions: values,
            dataOptionKeys: keys,
            minLength: 0
        });
    } else {
        var typeAheadOptions = {
            prefetch: true,
            remote: true,
            action: action + "&field=" + target
        };

        if (('undefined' !== typeof options) && ('undefined' !== typeof options.limit)) {
            typeAheadOptions.limit = options.limit;
        }

        if (('undefined' !== typeof options) && ('undefined' !== typeof options.noRrecordMessage)) {
            typeAheadOptions.noRrecordMessage = options.noRrecordMessage;
        }

        var fieldTypeahead = Mautic.activateTypeahead(fieldId, typeAheadOptions);
    }

    var callback = function (event, datum) {
        if (fieldEl.length && datum["value"]) {
            fieldEl.val(datum["value"]);

            var lookupCallback = mQuery(fieldId).data('lookup-callback');
            if (lookupCallback && typeof Mautic[lookupCallback] == 'function') {
                Mautic[lookupCallback](field, datum);
            }
        }
    };

    mQuery(fieldTypeahead).on('typeahead:selected', callback).on('typeahead:autocompleted', callback);
};

/**
 * Convert to multiselect
 *
 * @param el
 */
Mautic.activateMultiSelect = function(el) {
    var moveOption = function(v, prev) {
        var theOption = mQuery(el).find('option[value="' + v + '"]').first();
        var lastSelected = mQuery(el).find('option:not(:disabled)').filter(function () {
            return mQuery(this).prop('selected');
        }).last();

        if (typeof prev !== 'undefined') {
            if (prev) {
                var prevOption = mQuery(el).find('option[value="' + prev + '"]').first();
                theOption.insertAfter(prevOption);
                return;
            }
        } else if (lastSelected.length) {
            theOption.insertAfter(lastSelected);
            return;
        }
        theOption.prependTo(el);
    };

    mQuery(el).multiSelect({
        afterInit: function(container) {
            var funcName = mQuery(el).data('afterInit');
            if (funcName) {
                Mautic[funcName]('init', container);
            }

            var selectThat = this,
                $selectableSearch      = this.$selectableUl.prev(),
                $selectionSearch       = this.$selectionUl.prev(),
                selectableSearchString = '#' + this.$container.attr('id') + ' .ms-elem-selectable:not(.ms-selected)',
                selectionSearchString  = '#' + this.$container.attr('id') + ' .ms-elem-selection.ms-selected';

            this.qs1 = $selectableSearch.quicksearch(selectableSearchString)
                .on('keydown', function (e) {
                    if (e.which === 40) {
                        selectThat.$selectableUl.focus();
                        return false;
                    }
                });

            this.qs2 = $selectionSearch.quicksearch(selectionSearchString)
                .on('keydown', function (e) {
                    if (e.which == 40) {
                        selectThat.$selectionUl.focus();
                        return false;
                    }
                });

            var selectOrder = mQuery(el).data('order');
            if (selectOrder && selectOrder.length > 1) {
                this.deselect_all();
                mQuery.each(selectOrder, function(k, v) {
                    selectThat.select(v);
                });
            }

            var isSortable = mQuery(el).data('sortable');
            if (isSortable) {
                mQuery(el).parent('.choice-wrapper').find('.ms-selection').first().sortable({
                    items: '.ms-elem-selection',
                    helper: function (e, ui) {
                        ui.width(mQuery(el).width());
                        return ui;
                    },
                    axis: 'y',
                    scroll: false,
                    update: function(event, ui) {
                        var prev      = ui.item.prev();
                        var prevValue = (prev.length) ? prev.data('ms-value') : '';
                        moveOption(ui.item.data('ms-value'), prevValue);
                    }
                });
            }
        },
        afterSelect: function(value) {
            var funcName = mQuery(el).data('afterSelect');
            if (funcName) {
                Mautic[funcName]('select', value);
            }
            this.qs1.cache();
            this.qs2.cache();

            moveOption(value);
        },
        afterDeselect: function(value) {
            var funcName = mQuery(el).data('afterDeselect');
            if (funcName) {
                Mautic[funcName]('deselect', value);
            }

            this.qs1.cache();
            this.qs2.cache();
        },
        selectableHeader: "<input type='text' class='ms-search form-control' autocomplete='off'>",
        selectionHeader:  "<input type='text' class='ms-search form-control' autocomplete='off'>",
        keepOrder: true
    });
};

/**
 * Activate modal buttons for embedded forms
 *
 * @param container
 */
Mautic.activateModalEmbeddedForms = function(container) {
    mQuery(container + " *[data-embedded-form='cancel']").off('click.embeddedform');
    mQuery(container + " *[data-embedded-form='cancel']").on('click.embeddedform', function (event) {
        event.preventDefault();

        var modal = mQuery(this).closest('.modal');
        mQuery(modal).modal('hide');

        if (mQuery(this).attr('data-embedded-form-clear') === 'true') {
            Mautic.resetForm(modal);
        }

        if (typeof mQuery(this).attr('data-embedded-form-callback') != 'undefined') {
            if (typeof window["Mautic"][mQuery(this).attr('data-embedded-form-callback')] == 'function') {
                window["Mautic"][mQuery(this).attr('data-embedded-form-callback')].apply('window', [this, modal]);
            }
        }
    });

    // Configure the modal
    mQuery(container + " *[data-embedded-form='add']").each(function() {
        var submitButton = this;
        var modal = mQuery(this).closest('.modal');
        if (typeof mQuery(modal).data('bs.modal') !== 'undefined' && typeof mQuery(modal).data('bs.modal').options !== 'undefined') {
            mQuery(modal).data('bs.modal').options.keyboard = false;
            mQuery(modal).data('bs.modal').options.backdrop = 'static';
        } else {
            mQuery(modal).attr('data-keyboard', false);
            mQuery(modal).attr('data-backdrop', 'static');
        }

        mQuery(modal).on('show.bs.modal', function () {
            // Don't allow submitting with enter key
            mQuery(this).on("keydown.embeddedForm", ":input:not(textarea)", function(event) {
                if (event.keyCode == 13) {
                    event.preventDefault();
                    if (event.metaKey || event.ctrlKey) {
                        // Submit the modal
                        mQuery(submitButton).click();
                    }
                }
            });
        });

        //clean slate upon close
        mQuery(modal).on('hidden.bs.modal', function () {
            mQuery(this).off("keydown.embeddedForm", ":input:not(textarea)");
        });
    });

    mQuery(container + " *[data-embedded-form='add']").off('click.embeddedform');
    mQuery(container + " *[data-embedded-form='add']").on('click.embeddedform', function (event) {
        event.preventDefault();

        var modal = mQuery(this).closest('.modal');
        mQuery(modal).modal('hide');

        if (typeof mQuery(this).attr('data-embedded-form-callback') != 'undefined') {
            if (typeof window["Mautic"][mQuery(this).attr('data-embedded-form-callback')] == 'function') {
                window["Mautic"][mQuery(this).attr('data-embedded-form-callback')].apply('window', [this, modal]);
            }
        }
    });
};

/**
 * Activate containers datetime inputs
 * @param container
 */
Mautic.activateDateTimeInputs = function(el, type) {
    if (typeof type == 'undefined') {
        type = 'datetime';
    }

    var format = mQuery(el).data('format');
    if (type == 'datetime') {
        mQuery(el).datetimepicker({
            format: (format) ? format : 'Y-m-d H:i',
            lazyInit: true,
            validateOnBlur: false,
            allowBlank: true,
            scrollMonth: false,
            scrollInput: false
        });
    } else if(type == 'date') {
        mQuery(el).datetimepicker({
            timepicker: false,
            format: (format) ? format : 'Y-m-d',
            lazyInit: true,
            validateOnBlur: false,
            allowBlank: true,
            scrollMonth: false,
            scrollInput: false,
            closeOnDateSelect: true
        });
    } else if (type == 'time') {
        mQuery(el).datetimepicker({
            datepicker: false,
            format: (format) ? format : 'H:i',
            lazyInit: true,
            validateOnBlur: false,
            allowBlank: true,
            scrollMonth: false,
            scrollInput: false
        });
    }

    mQuery(el).addClass('calendar-activated');
};

/**
 * Activates Typeahead.js command lists for search boxes
 * @param elId
 * @param modelName
 */
Mautic.activateSearchAutocomplete = function (elId, modelName) {
    if (mQuery('#' + elId).length) {
        var livesearch = (mQuery('#' + elId).attr("data-toggle=['livesearch']")) ? true : false;

        var typeaheadObject = Mautic.activateTypeahead('#' + elId, {
            prefetch: true,
            remote: false,
            limit: 0,
            action: 'commandList&model=' + modelName,
            multiple: true
        });
        mQuery(typeaheadObject).on('typeahead:selected', function (event, datum) {
            if (livesearch) {
                //force live search update,
                MauticVars.lastSearchStr = '';
                mQuery('#' + elId).keyup();
            }
        }).on('typeahead:autocompleted', function (event, datum) {
            if (livesearch) {
                //force live search update
                MauticVars.lastSearchStr = '';
                mQuery('#' + elId).keyup();
            }
        });
    }
};

/**
 * Activate live search feature
 *
 * @param el
 * @param searchStrVar
 * @param liveCacheVar
 */
Mautic.activateLiveSearch = function (el, searchStrVar, liveCacheVar) {
    if (!mQuery(el).length) {
        return;
    }

    //find associated button
    var btn = "button[data-livesearch-parent='" + mQuery(el).attr('id') + "']";

    mQuery(el).on('focus', function () {
        Mautic.currentSearchString = mQuery(this).val().trim();
    });
    mQuery(el).on('change keyup paste', {}, function (event) {
        var searchStr = mQuery(el).val().trim();

        var spaceKeyPressed = (event.which == 32 || event.keyCode == 32);
        var enterKeyPressed = (event.which == 13 || event.keyCode == 13);
        var deleteKeyPressed = (event.which == 8 || event.keyCode == 8);

        if (!enterKeyPressed && Mautic.currentSearchString && Mautic.currentSearchString == searchStr) {
            return;
        }

        var target = mQuery(el).attr('data-target');
        var diff = searchStr.length - MauticVars[searchStrVar].length;

        if (diff < 0) {
            diff = parseInt(diff) * -1;
        }

        var overlayEnabled = mQuery(el).attr('data-overlay');
        if (!overlayEnabled || overlayEnabled == 'false') {
            overlayEnabled = false;
        } else {
            overlayEnabled = true;
        }

        var overlayTarget = mQuery(el).attr('data-overlay-target');
        if (!overlayTarget) overlayTarget = target;

        if (overlayEnabled) {
            mQuery(el).off('blur.livesearchOverlay');
            mQuery(el).on('blur.livesearchOverlay', function() {
                mQuery(overlayTarget + ' .content-overlay').remove();
            });
        }

        if (!deleteKeyPressed && overlayEnabled) {
            var overlay = mQuery('<div />', {"class": "content-overlay"}).html(mQuery(el).attr('data-overlay-text'));
            if (mQuery(el).attr('data-overlay-background')) {
                overlay.css('background', mQuery(el).attr('data-overlay-background'));
            }
            if (mQuery(el).attr('data-overlay-color')) {
                overlay.css('color', mQuery(el).attr('data-overlay-color'));
            }
        }

        //searchStr in MauticVars[liveCacheVar] ||
        if ((!searchStr && MauticVars[searchStrVar].length) || diff >= 3 || spaceKeyPressed || enterKeyPressed) {
            MauticVars[searchStrVar] = searchStr;
            event.data.livesearch = true;

            Mautic.filterList(event,
                mQuery(el).attr('id'),
                mQuery(el).attr('data-action'),
                target,
                liveCacheVar,
                overlayEnabled,
                overlayTarget
            );
        } else if (overlayEnabled) {
            if (!mQuery(overlayTarget + ' .content-overlay').length) {
                mQuery(overlayTarget).prepend(overlay);
            }
        }
    });

    if (mQuery(btn).length) {
        mQuery(btn).on('click', {'parent': mQuery(el).attr('id')}, function (event) {
            var searchStr = mQuery(el).val().trim();
            MauticVars[searchStrVar] = searchStr;

            Mautic.filterButtonClicked = true;
            Mautic.filterList(event,
                event.data.parent,
                mQuery('#' + event.data.parent).attr('data-action'),
                mQuery('#' + event.data.parent).attr('data-target'),
                'liveCache',
                mQuery(this).attr('data-livesearch-action')
            );
        });

        if (mQuery(el).val()) {
            mQuery(btn).attr('data-livesearch-action', 'clear');
            mQuery(btn + ' i').removeClass('ri-search-line').addClass('ri-eraser-line');
        } else {
            mQuery(btn).attr('data-livesearch-action', 'search');
            mQuery(btn + ' i').removeClass('ri-eraser-line').addClass('ri-search-line');
        }
    }
};

/**
 * Converts an input to a color picker
 * @param el
 */
Mautic.activateColorPicker = function(el, options) {
    let input = mQuery(el);
    var pickerOptions = input.data('color-options');
    if (!pickerOptions) {
        pickerOptions = {
            theme: 'bootstrap',
            change: function (hex) {
                input.trigger('change.minicolors', hex);
            }
        };
    }

    if (typeof options == 'object') {
        pickerOptions = mQuery.extend(pickerOptions, options);
    }

    input.minicolors(pickerOptions);

    // The previous version of the Minicolors library did not use the # in the value. This is for backwards compatibility.
    input.val(input.val().replace('#', ''));

    input.on('blur', function() {
        input.val(input.val().replace('#', ''));
    });
};

/**
 * Activates typeahead
 * @param el
 * @param options
 * @returns {*}
 */
Mautic.activateTypeahead = function (el, options) {
    if (typeof options == 'undefined' || !mQuery(el).length) {
        return;
    }

    if (typeof options.remote == 'undefined') {
        options.remote = (options.action) ? true : false;
    }

    if (typeof options.prefetch == 'undefined') {
        options.prefetch = false;
    }

    if (typeof options.limit == 'undefined') {
        options.limit = 5;
    }

    if (!options.displayKey) {
        options.displayKey = 'value';
    }

    if (typeof options.multiple == 'undefined') {
        options.multiple = false;
    }

    if (typeof options.minLength == 'undefined') {
        options.minLength = 2;
    }

    if (options.prefetch || options.remote) {
        if (typeof options.action == 'undefined') {
            return;
        }

        var sourceOptions = {
            datumTokenizer: Bloodhound.tokenizers.obj.whitespace(options.displayKey),
            queryTokenizer: Bloodhound.tokenizers.whitespace,
            dupDetector: function (remoteMatch, localMatch) {
                return (remoteMatch[options.displayKey] == localMatch[options.displayKey]);
            },
            ttl: 15000,
            limit: options.limit
        };

        var filterClosure = function (list) {
            if (typeof list.ignore_wdt != 'undefined') {
                delete list.ignore_wdt;
            }

            if (typeof list.success != 'undefined') {
                delete list.success;
            }

            if (typeof list == 'object') {
                if (typeof list[0] != 'undefined') {
                    //meant to be an array and not an object
                    list = mQuery.map(list, function (el) {
                        return el;
                    });
                } else {
                    //empty object so return empty array
                    list = [];
                }
            }

            return list;
        };

        if (options.remote) {
            sourceOptions.remote = {
                url: mauticAjaxUrl + "?action=" + options.action + "&filter=%QUERY",
                filter: filterClosure,
                wildcard: '%QUERY',
            };
        }

        if (options.prefetch) {
            sourceOptions.prefetch = {
                url: mauticAjaxUrl + "?action=" + options.action,
                filter: filterClosure
            };
        }

        var theBloodhound = new Bloodhound(sourceOptions);
        theBloodhound.initialize();
    } else {
        var substringMatcher = function (strs, strKeys) {
            return function findMatches(q, cb) {
                var matches, substrRegex;

                // an array that will be populated with substring matches
                matches = [];

                // regex used to determine if a string contains the substring `q`
                substrRegex = new RegExp(q, 'i');

                // iterate through the pool of strings and for any string that
                // contains the substring `q`, add it to the `matches` array
                mQuery.each(strs, function (i, str) {
                    if (typeof str == 'object') {
                        str = str[options.displayKey];
                    }

                    if (substrRegex.test(str)) {
                        // the typeahead jQuery plugin expects suggestions to a
                        // JavaScript object, refer to typeahead docs for more info
                        var match = {};

                        match[options.displayKey] = str;

                        if (strKeys.length && typeof strKeys[i] != 'undefined') {
                            match['id'] = strKeys[i];
                        }
                        matches.push(match);
                    }
                });

                cb(matches);
            };
        };

        var lookupOptions = (options.dataOptions) ? options.dataOptions : mQuery(el).data('options');
        var lookupKeys = (options.dataOptionKeys) ? options.dataOptionKeys : [];
        if (!lookupOptions) {
            return;
        }
    }

    var noRrecordMessage = (options.noRrecordMessage) ? options.noRrecordMessage : mQuery(el).data('no-record-message');
    var theName = el.replace(/[^a-z0-9\s]/gi, '').replace(/[-\s]/g, '_');
    var dataset = {
        name: theName,
        displayKey: options.displayKey,
        source: (typeof theBloodhound != 'undefined') ? theBloodhound.ttAdapter() : substringMatcher(lookupOptions, lookupKeys)
    };

    if (noRrecordMessage) {
        dataset.templates = {
            empty: "<p>" + noRrecordMessage + "<p>"
        }
    }

    var theTypeahead = mQuery(el).typeahead(
        {
            hint: true,
            highlight: true,
            minLength: options.minLength,
            multiple: options.multiple
        },
        dataset
    ).on('keypress', function (event) {
        if ((event.keyCode || event.which) == 13) {
            mQuery(el).typeahead('close');
        }
    }).on('focus', function() {
        if(mQuery(el).typeahead('val') === '' && !options.minLength) {
            mQuery(el).data('ttTypeahead').input.trigger('queryChanged', '');
        }
    });

    return theTypeahead;
};

/**
 * Activate sortable
 *
 * @param el
 */
Mautic.activateSortable = function(el) {
    var prefix = mQuery(el).attr('data-prefix');
    if (mQuery('#' + prefix + '_additem').length) {
        mQuery('#' + prefix + '_additem').click(function () {
            var count = mQuery('#' + prefix + '_itemcount').val();
            var prototype = mQuery('#' + prefix + '_additem').attr('data-prototype');
            prototype = prototype.replace(/__name__/g, count);
            mQuery(prototype).appendTo(mQuery('#' + prefix + '_list div.list-sortable'));
            mQuery('#' + prefix + '_list_' + count).focus();
            count++;
            mQuery('#' + prefix + '_itemcount').val(count);
            return false;
        });
    }

    mQuery('#' + prefix + '_list div.list-sortable').sortable({
        items: 'div.sortable',
        handle: 'span.postaddon',
        axis: 'y',
        containment: '#' + prefix + '_list',
        stop: function (i) {
            var order = 0;
            mQuery('#' + prefix + '_list div.list-sortable div.input-group input').each(function () {
                var name = mQuery(this).attr('name');
                if (mQuery(this).hasClass('sortable-label')) {
                    name = name.replace(/(\[list\]\[[0-9]+\]\[label\])$/g, '') + '[list][' + order + '][label]';
                } else if (mQuery(this).hasClass('sortable-value')) {
                    name = name.replace(/(\[list\]\[[0-9]+\]\[value\])$/g, '') + '[list][' + order + '][value]';
                    order++;
                } else {
                    name = name.replace(/(\[list\]\[[0-9]+\])$/g, '') + '[list][' + order + ']';
                    order++;
                }
                mQuery(this).attr('name', name);
            });
        }
    });
};

/**
 * Download a link via iframe
 *
 * @param link
 */
Mautic.initiateFileDownload = function (link) {
    if (mauticContactExportInBackground === 1 && link.indexOf('filetype=csv') >= 0) {
        Mautic.processCsvContactExport(link);
        return;
    }

    //initialize download links
    mQuery("<iframe/>").attr({
        src: link,
        style: "visibility:hidden;display:none"
    }).appendTo(mQuery('body'));
};

Mautic.processCsvContactExport = function (route) {
    mQuery.ajax({
        showLoadingBar: true,
        url: route,
        type: "POST",
        dataType: "json",
        success: function (response) {
            Mautic.processPageContent(response);

            if (typeof callback == 'function') {
                callback(response);
            }
        },
        error: function (request, textStatus, errorThrown) {
            Mautic.processAjaxError(request, textStatus, errorThrown);
        }
    });
};

/**
 * Quick Filters
 * This module handles the quick filtering functionality in Mautic's list views.
 * Includes initialization, toggling, applying, and resetting.
 * @namespace Mautic
 */

/**
 * Initializes the filter commands by collecting them from the DOM.
 */
Mautic.initFilterCommands = function () {
    const filterElements = document.querySelectorAll('.label[data-filter]');
    Mautic.filterCommands = Array.from(filterElements).map(function (el) {
        return el.dataset.filter;
    });

    // Include select field options as filter commands
    const selectFields = document.querySelectorAll('.popover-content select');

    selectFields.forEach(function (selectElement) {
        const options = Array.from(selectElement.options).map(option => option.value);
        Mautic.filterCommands.push(...options);
    });
};

/**
 * Toggles the active state of a filter label and manages conflicting filters.
 *
 * @param {HTMLElement} element
 */
Mautic.toggleFilter = function (element) {
    const filterValue = element.dataset.filter;
    const conflictGroup = element.dataset.conflictGroup || null;

    // If the filter is in a conflict group, deactivate other filters in the same group
    if (conflictGroup) {
        // Get all filters in the same conflict group
        const filtersInGroup = document.querySelectorAll(`.label[data-conflict-group="${conflictGroup}"]`);
        filtersInGroup.forEach(function (filterElement) {
            if (filterElement !== element) {
                filterElement.classList.remove('active');
            }
        });
    }

    // Toggle active class on the clicked element
    element.classList.toggle('active');
};

/**
 * Applies the selected filters when the "Apply filters" button is clicked.
 */
Mautic.applyFilters = function () {
    const searchInput = document.getElementById('list-search');
    let currentSearchValue = searchInput.value || '';
    currentSearchValue = Mautic.removeFilterCommands(currentSearchValue);

    const activeFilters = document.querySelectorAll('.label.active');
    let filterCommands = Array.from(activeFilters).map(function (filterElement) {
        return filterElement.dataset.filter;
    });

    const selectFields = document.querySelectorAll('.popover-content select');
    selectFields.forEach(function (selectElement) {
        const selectedOptions = Array.from(selectElement.selectedOptions).map(option => option.value);
        filterCommands.push(...selectedOptions);
    });

    const newSearchValue = (currentSearchValue + ' ' + filterCommands.join(' ')).trim();
    searchInput.value = newSearchValue;

    // Properly destroy and reinitialize popover
    const popoverTrigger = mQuery('[data-toggle="popover"]');
    popoverTrigger.popover('destroy');
    popoverTrigger.popover({
        html: true,
        container: 'body'
    });

    const enterKeyEvent = new KeyboardEvent('keyup', {
        key: 'Enter',
        keyCode: 13,
        which: 13,
        bubbles: true
    });
    searchInput.dispatchEvent(enterKeyEvent);
};

/**
 * Resets the filters when the "Reset filters" button is clicked.
 */
Mautic.resetFilters = function () {
    const searchInput = document.getElementById('list-search');
    let currentSearchValue = searchInput.value || '';
    currentSearchValue = Mautic.removeFilterCommands(currentSearchValue);
    searchInput.value = currentSearchValue.trim();

    const activeFilters = document.querySelectorAll('.label.active');
    activeFilters.forEach(function (filterElement) {
        filterElement.classList.remove('active');
    });

    const selectFields = document.querySelectorAll('.popover-content select');
    selectFields.forEach(function (selectElement) {
        selectElement.value = null;
        if (typeof mQuery !== 'undefined') {
            mQuery(selectElement).trigger('chosen:updated');
        }
    });

    // Properly destroy the popover instead of hiding it
    const popoverTrigger = mQuery('[data-toggle="popover"]');
    popoverTrigger.popover('destroy');

    const enterKeyEvent = new KeyboardEvent('keyup', {
        key: 'Enter',
        keyCode: 13,
        which: 13,
        bubbles: true
    });
    searchInput.dispatchEvent(enterKeyEvent);
};

/**
 * Removes existing filter commands from the search input value.
 *
 * @param {string} searchValue
 * @returns {string}
 */
Mautic.removeFilterCommands = function (searchValue) {
    if (!Mautic.filterCommands || Mautic.filterCommands.length === 0) {
        Mautic.initFilterCommands();
    }

    // Escape special characters in filter commands for regex
    const escapedCommands = Mautic.filterCommands.map(cmd => cmd.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));

    // Create a regex to match filter commands as whole words
    const regex = new RegExp('\\b(' + escapedCommands.join('|') + ')\\b', 'g');

    // Remove filter commands and extra spaces
    return searchValue.replace(regex, '').replace(/\s{2,}/g, ' ').trim();
};

/**
 * Parses the search input value and returns an array of active filter commands.
 *
 * @returns {Array<string>}
 */
Mautic.getActiveFilterCommands = function () {
    const searchInput = document.getElementById('list-search');
    if (!searchInput) {
        return []; // Return an empty array if there's no search input
    }
    const searchValue = searchInput.value || '';

    if (!Mautic.filterCommands || Mautic.filterCommands.length === 0) {
        Mautic.initFilterCommands();
    }

    // Escape special characters in filter commands for regex
    const escapedCommands = Mautic.filterCommands.map(cmd => cmd.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));

    // Create a regex to match filter commands as whole words
    const regex = new RegExp('\\b(' + escapedCommands.join('|') + ')\\b', 'g');

    const matches = searchValue.match(regex);
    return matches ? matches : [];
};

/**
 * Initializes the active states of filter labels based on the current search input.
 *
 * @param {HTMLElement} popoverElement
 */
Mautic.initializePopoverFilters = function (popoverElement) {
    const activeFilterCommands = Mautic.getActiveFilterCommands();

    // Handle regular filter labels
    activeFilterCommands.forEach(function (filterCommand) {
        const label = popoverElement.querySelector(`.label[data-filter="${filterCommand}"]`);
        if (label) {
            label.classList.add('active');
        }

        // Handle select fields
        const selectFields = popoverElement.querySelectorAll('select');
        selectFields.forEach(function (selectElement) {
            // Check if the value matches either the raw value or a category format
            Array.from(selectElement.options).forEach(function (option) {
                const isSelected = activeFilterCommands.some(cmd =>
                    cmd === option.value ||
                    cmd === `category:${option.value}`
                );
                option.selected = isSelected;
            });

            // Initialize chosen
            mQuery(selectElement).chosen({
                width: '100%',
                allow_single_deselect: true
            });

            // Update the UI
            mQuery(selectElement).trigger('chosen:updated');
        });
    });
};

/**
 * Handles the insertion of the popover and initializes active filter labels.
 */
Mautic.handlePopoverInsertion = function () {
    mQuery(document).on('inserted.bs.popover', '[data-toggle="popover"]', function () {
        const popoverId = mQuery(this).attr('aria-describedby');
        if (!popoverId) return;

        const popoverElement = document.getElementById(popoverId);
        if (!popoverElement) return;

        Mautic.initializePopoverFilters(popoverElement);

        // Initialize Chosen after popover content is inserted
        mQuery('.popover-content select').chosen({
            width: '100%',
            allow_single_deselect: true
        });
    });
};

// Initialize filter commands on page load
document.addEventListener('DOMContentLoaded', function () {
    Mautic.initFilterCommands();
    Mautic.handlePopoverInsertion();
});
