define(['jquery', 'lodash', 'handlebars', 'lib/handlebars/helpers', 'css!ui/searchModal/css/advancedSearch.css', 'ui/component', 'ui/modal', 'ui/datatable', 'select2', 'core/dataProvider/request'], function ($$1, _, Handlebars, Helpers0, advancedSearch_css, component, modal, datatable, select2, request) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
    component = component && Object.prototype.hasOwnProperty.call(component, 'default') ? component['default'] : component;
    request = request && Object.prototype.hasOwnProperty.call(request, 'default') ? request['default'] : request;

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;


      buffer += "<div class=\"advanced-search-container\">\n    <div class=\"advanced-criteria-container\"></div>\n    <div class=\"add-criteria-container\">\n        <a class=\"toggle-modal-button\"><span class=\"icon-add\"></span>"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "add criteria", options) : helperMissing.call(depth0, "__", "add criteria", options)))
        + "</a>\n        <select name=\"criteria-select\">\n            <option></option>\n        </select>\n    </div>\n</div>\n";
      return buffer;
      });
    function advancedSearchTpl(data, options, asString) {
      var html = Template(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$1 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

    function program1(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            ";
      return buffer;
      }
    function program2(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"criteria-alias\">("
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + ")</span>";
      return buffer;
      }

    function program4(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"class-path\">/ "
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "</span>";
      return buffer;
      }

      buffer += "<div class=\"filter-container "
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-filter\" data-criteria=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\" data-type=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.type)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n    <button class=\"icon-result-nok\" aria-label=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Remove criteria", options) : helperMissing.call(depth0, "__", "Remove criteria", options)))
        + "\"></button>\n    <label>\n        <span class=\"filter-label-text\">"
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n            ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.isDuplicated), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </span>\n        <input type=\"text\">\n    </label>\n</div>";
      return buffer;
      });
    function textCriterionTpl(data, options, asString) {
      var html = Template$1(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$2 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;

    function program1(depth0,data) {
      
      var buffer = "";
      buffer += "\n        <li>"
        + escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
        + "</li>\n    ";
      return buffer;
      }

      buffer += "<div class=\"filter-container invalid-criteria-warning-container\">\n<span class=\"select2-search-choice-close\"></span>\n    <p>\n        "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "The following criteria are not applicable to the selected class and have been removed from the search", options) : helperMissing.call(depth0, "__", "The following criteria are not applicable to the selected class and have been removed from the search", options)))
        + ":\n    </p>\n    <ul>\n    ";
      stack1 = helpers.each.call(depth0, (depth0 && depth0.invalidCriteria), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </ul>\n</div>";
      return buffer;
      });
    function invalidCriteriaWarningTpl(data, options, asString) {
      var html = Template$2(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$3 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

    function program1(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            ";
      return buffer;
      }
    function program2(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"criteria-alias\">("
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + ")</span>";
      return buffer;
      }

    function program4(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"class-path\">/ "
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "</span>";
      return buffer;
      }

    function program6(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n            <div>\n                <input type=\"checkbox\" value=\""
        + escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
        + "\" id=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-"
        + escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
        + "\">\n                <label class=\"filter-label-text\" for=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-"
        + escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
        + "\">"
        + escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
        + "</label>\n            </div>\n        ";
      return buffer;
      }

      buffer += "<div class=\"filter-container "
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-filter\" data-criteria=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"\n    data-type=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.type)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n    <button class=\"icon-result-nok\" aria-label=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, " Remove criteria", options) : helperMissing.call(depth0, "__", " Remove criteria", options)))
        + "\"></button>\n    <fieldset class=\"filter-bool-group\">\n        <legend>"
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n            ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.isDuplicated), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </legend>\n        ";
      stack1 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.values), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </fieldset>\n</div>";
      return buffer;
      });
    function listCheckboxCriterionTpl(data, options, asString) {
      var html = Template$3(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$4 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;

    function program1(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n                ";
      stack1 = helpers['if'].call(depth0, ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            ";
      return buffer;
      }
    function program2(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"criteria-alias\">("
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.alias)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + ")</span>";
      return buffer;
      }

    function program4(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "<span class=\"class-path\">/ "
        + escapeExpression(((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1['class'])),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "</span>";
      return buffer;
      }

      buffer += "<div class=\"filter-container "
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-filter\" data-criteria=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\" data-type=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.type)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\">\n    <button class=\"icon-result-nok\" aria-label=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Remove criteria", options) : helperMissing.call(depth0, "__", "Remove criteria", options)))
        + "\"></button>\n    <div class=\"criterion-container\">\n        <span class=\"filter-label-text\">"
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\n            ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.isDuplicated), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </span>\n        <div class=\"logic-radio-group\">\n            <label class=\"logic-label\">\n                <input type=\"radio\" name=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-logic\" value=\"LOGIC_AND\"><span class=\"icon-radio\"></span> "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Include all", options) : helperMissing.call(depth0, "__", "Include all", options)))
        + "\n            </label>\n            <label class=\"logic-label\">\n                <input type=\"radio\" name=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-logic\" value=\"LOGIC_OR\"><span class=\"icon-radio\"></span> "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Include any", options) : helperMissing.call(depth0, "__", "Include any", options)))
        + "\n            </label>\n            <label class=\"logic-label\">\n                <input type=\"radio\" name=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-logic\" value=\"LOGIC_NOT\"><span class=\"icon-radio\"></span> "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Include none", options) : helperMissing.call(depth0, "__", "Include none", options)))
        + "\n            </label>\n        </div>\n        <input type='text' name=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.criterion)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "-select\">\n    </div>\n</div>\n";
      return buffer;
      });
    function listSelectCriterionTpl(data, options, asString) {
      var html = Template$4(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$5 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression;


      buffer += "<b>";
      if (helper = helpers.text) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.text); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</b>";
      return buffer;
      });
    function highlightedTextTpl(data, options, asString) {
      var html = Template$5(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$6 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing;


      buffer += "<span class=\"class-path\"> / ";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.text), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.text), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</span>";
      return buffer;
      });
    function classLabelTpl(data, options, asString) {
      var html = Template$6(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$7 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing;


      buffer += "<span class=\"criteria-alias\"> (";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.text), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.text), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ")</span>";
      return buffer;
      });
    function aliasTpl(data, options, asString) {
      var html = Template$7(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$8 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing;


      buffer += "<span class=\"criteria-label\">";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.text), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.text), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</span>";
      return buffer;
      });
    function labelTpl(data, options, asString) {
      var html = Template$8(data, options);
      return (asString || true) ? html : $(html);
    }

    /**
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; under version 2
     * of the License (non-upgradable).
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     *
     * Copyright (c) 2021 (original work) Open Assessment Technologies SA ;
     */

    /**
     * Sort an array by a particular property.
     * @param {Array} iter - The array to sort.
     * @param {string} prop - The name of the sorting property.
     * @returns {Array} - Returns a sorted copy of the array.
     * @private
     */
    function sortBy(iter, prop) {
      return Array.from(iter).sort((a, b) => {
        const textA = a && a[prop] || '';
        const textB = b && b[prop] || '';
        return textA.localeCompare(textB);
      });
    }

    /**
     * Creates advanced search component
     *
     * @param {object} config
     * @param {object} config.renderTo - DOM element where component will be rendered to
     * @param {string} config.advancedCriteria - advanced criteria to be set on component creation
     * @param {bool} config.hideCriteria - if the criteria must be hidden
     * @param {string} config.rootClassUri - rootClassUri to check for whitelist sections
     * @param {string} config.statusUrl - the URL to the status API (usually '/tao/AdvancedSearch/status')
     * @returns {advancedSearch}
     */
    function advancedSearchFactory(config) {
      // Private properties to be easily accessible by instance methods
      let $container = null;
      let $addCriteria = null;
      let $addCriteriaInput = null;
      let $criteriaSelect = null;
      let $advancedCriteriaContainer = null;
      let criteriaState = null;
      let criteriaMapping = {};
      const criteriaTypes = {
        text: 'text',
        list: 'list'
      };
      const criteriaLogic = {
        and: 'LOGIC_AND',
        or: 'LOGIC_OR',
        not: 'LOGIC_NOT'
      };
      let isAdvancedSearchStatusEnabled;
      let isCriteriaListUpdated = false;

      // Creates new component
      const instance = component({
        /**
         * Tells if the advanced search is enabled.
         * @returns {boolean}
         */
        isEnabled() {
          return !!isAdvancedSearchStatusEnabled;
        },
        /**
         * Request metadata (criteria) for the given uri
         * @param {string} classUri - url to make the reques to
         * @returns {Promise} - Request promise
         */
        updateCriteria(route) {
          if (!isAdvancedSearchStatusEnabled) {
            return Promise.resolve();
          }
          const $criteriaIcon = $$1('.add-criteria-container a span').eq(0);
          $criteriaIcon.toggleClass('icon-add').toggleClass('icon-loop');
          return request(route).then(response => {
            criteriaMapping = response.criteriaMapping || {};
            const classTree = response.classDefinition ? response.classDefinition : response;
            const criteria = formatCriteria(classTree);
            updateCriteria(criteria);
            isCriteriaListUpdated = true;
            $criteriaIcon.toggleClass('icon-add').toggleClass('icon-loop');
          }).catch(e => instance.trigger('error', e));
        },
        /**
         * Access to component state
         * @returns {Object} - criteria state
         */
        getState() {
          return criteriaState;
        },
        /**
         * Removes every rendered criterion, updates criteria state accordingly
         * and removes classes applied to scrollable list of criteria
         */
        clear() {
          $advancedCriteriaContainer.removeClass(['scrollable', 'scroll-separator-top', 'scroll-separator-bottom']);
          $advancedCriteriaContainer.empty();
          _.forEach(criteriaState, criterion => {
            criterion.rendered = false;
            criterion.value = null;
            criterion.logic = null;
          });
        },
        /**
         * Builds substring of search query with the advanced criteria conditions
         */
        getAdvancedCriteriaQuery(hasSearchInput) {
          const advancedSearchCriteria = _.filter(criteriaState, criterion => criterion.rendered === true);
          let query = '';
          advancedSearchCriteria.forEach(renderedCriterion => {
            const queryParam = renderedCriterion.propertyUri;
            if ((hasSearchInput || query.trim().length !== 0) && renderedCriterion.value) {
              query += ' AND ';
            }
            if (renderedCriterion.type === criteriaTypes.text) {
              if (renderedCriterion.value && renderedCriterion.value.trim() !== '') {
                query += `${queryParam}:${renderedCriterion.value.trim()}`;
              }
            } else if (renderedCriterion.type === criteriaTypes.list) {
              if (renderedCriterion.value && renderedCriterion.value.length > 0) {
                if (renderedCriterion.value.length === 1 && renderedCriterion.logic === criteriaLogic.not) {
                  //we have to pass NOT logic anyways, so add empty member to have NOT logic modifier in the query
                  renderedCriterion.value.push('');
                }
                query += renderedCriterion.value.map(value => `${queryParam}:${value}`).join(` ${renderedCriterion.logic} `);

                //we need to remove empty member from renderedCriterion.value because renderedCriterion can be reused after the query is done
                renderedCriterion.value = renderedCriterion.value.filter(value => value !== '');
              }
            }
          });
          return query;
        }
      }).setTemplate(advancedSearchTpl).on('render', () => {
        initUiSelectors();
        initAddCriteriaSelector().then(() => {
          initCriteriaState();
          instance.trigger('ready');
        }).catch(e => instance.trigger('error', e));
      });

      /**
       * Inits template selectors and scroll animation
       */
      function initUiSelectors() {
        $container = instance.getElement();
        $addCriteria = $$1('.add-criteria-container', $container);
        $addCriteria.addClass('disabled');
        $addCriteriaInput = $$1('.add-criteria-container a', $container);
        $criteriaSelect = $$1('.add-criteria-container select', $container);
        $advancedCriteriaContainer = $$1('.advanced-criteria-container', $container);
        $advancedCriteriaContainer.on('scroll', _.throttle(animateScroll, 100));
      }

      /**
       * Lookup for characters in text to highlight
       * @param {String} text - text to lookup
       * @param {String} searchString - match to be applied in the text
       * @returns {String} - highlighted text
       */
      function highlightCharacter(text, searchString) {
        if (!searchString) {
          return text;
        }
        const reg = new RegExp(searchString, 'gi');
        return text.replace(reg, str => highlightedTextTpl({
          text: str
        }));
      }

      /**
       * Inits select2 on criteria select and its UX logic
       */
      function initAddCriteriaSelector() {
        return request(instance.config.statusUrl).then(function (response) {
          if (config.hideCriteria || !response.enabled || response.whitelist && response.whitelist.includes(config.rootClassUri)) {
            isAdvancedSearchStatusEnabled = false;
            return;
          }
          isAdvancedSearchStatusEnabled = true;
          $addCriteria.removeClass('disabled');
          $criteriaSelect.select2({
            containerCssClass: 'criteria-select2',
            dropdownCssClass: 'criteria-dropdown-select2',
            sortResults: results => sortBy(results, 'text'),
            escapeMarkup: function (markup) {
              return markup;
            },
            formatResult: function formatResult(result, container, query) {
              const label = result.element[0].getAttribute('label');
              const alias = result.element[0].getAttribute('alias');
              const classLabel = result.element[0].getAttribute('class-label');
              let html = labelTpl({
                text: highlightCharacter(label, query.term)
              });
              if (alias) {
                html += aliasTpl({
                  text: alias
                });
              }
              if (classLabel) {
                html += classLabelTpl({
                  text: classLabel
                });
              }
              return html;
            }
          });

          // open dropdown when user clicks on add criteria input
          $addCriteriaInput.on('click', () => {
            if (isCriteriaListUpdated) {
              $criteriaSelect.select2('open');
              // if dropdown is opened above addCriteria input, top property is slightly decreased to avoid overlapping with addCriteria icon
              if ($$1('.criteria-dropdown-select2').hasClass('select2-drop-above')) {
                $$1('.criteria-dropdown-select2').css('top', $$1('.criteria-dropdown-select2').css('top').split('px')[0] - 10 + 'px');
              }
            }
          });

          // when a criterion is selected add it to criteria container, remove it from dropdown options and reset select
          $criteriaSelect.on('change', () => {
            const criterionToAdd = $criteriaSelect.children('option:selected').val();
            addNewCriterion(criterionToAdd);
            $criteriaSelect.children('option:selected').remove();
            $criteriaSelect.select2('val', '');
          });
        }).catch(function (e) {
          return instance.trigger('error', e);
        });
      }

      /**
       * inits criteriaState loading it from the store (if present) or empty object.
       * If there is a stored criteriaState, those criteria that were rendered
       * but with null value are updated to not being rendered
       */
      function initCriteriaState() {
        if (instance.config.advancedCriteria) {
          _.forEach(instance.config.advancedCriteria, criterion => {
            if (criterion.rendered === true && criterion.value === null) {
              criterion.rendered = false;
            }
          });
        }
        criteriaState = instance.config.advancedCriteria || {};
      }

      /**
       * Styles scrolling on $advancedCriteriaContainer
       */
      function animateScroll() {
        const scrollPercentage = $advancedCriteriaContainer.get(0).scrollTop / ($advancedCriteriaContainer.get(0).scrollHeight - $advancedCriteriaContainer.get(0).clientHeight);
        if (scrollPercentage > 0.1) {
          $advancedCriteriaContainer.addClass('scroll-separator-top');
        } else {
          $advancedCriteriaContainer.removeClass('scroll-separator-top');
        }
        if (scrollPercentage < 0.9) {
          $advancedCriteriaContainer.addClass('scroll-separator-bottom');
        } else {
          $advancedCriteriaContainer.removeClass('scroll-separator-bottom');
        }
      }

      /**
       * Renders new criterion to criteria container so it can be used on advanced search filtering
       * @param {string} criterionToAdd - new criterion to be added
       */
      function addNewCriterion(criterionToAdd) {
        // remove deprecated warning message
        $$1('.invalid-criteria-warning-container').remove();

        // render new criterion
        const criterion = criteriaState[criterionToAdd];
        const $criterionContainer = renderCriterion(criterion);

        // set logic to remove criterion
        $$1('.icon-result-nok', $criterionContainer).on('click', {
          criterion
        }, removeCriterion);

        // set initial value and manage value changes
        bindCriterionValue(criterion, $criterionContainer);

        // update styles if scroll is enabled
        if ($advancedCriteriaContainer.get(0).scrollHeight > $advancedCriteriaContainer.outerHeight()) {
          $advancedCriteriaContainer.addClass('scrollable');
        }
        criterion.rendered = true;
      }

      /**
       * Renders the new criterion selecting the appropiate handlebars template and prepending to advanced criteria container.
       * If criterion is of type list with a uri endpoint to request the options, select2 is also init
       * @param {object} criterion - criterion to render
       * @returns - the rendered container
       */
      function renderCriterion(criterion) {
        let templateToUse = null;
        if (criterion.type === criteriaTypes.text) {
          templateToUse = textCriterionTpl;
        } else if (criterion.type === criteriaTypes.list && criterion.uri) {
          templateToUse = listSelectCriterionTpl;
        } else {
          templateToUse = listCheckboxCriterionTpl;
        }
        $advancedCriteriaContainer.append(templateToUse({
          criterion
        }));
        const $criterionContainer = $$1(`.${criterion.id}-filter`, $container);
        const valueMapping = criteriaMapping[criterion.type];

        /**
         * On criterion of type list with a uri endpoint to retrieve options, template includes a select
         * that is managed with select2, so we init it here
         */
        if (criterion.type === criteriaTypes.list && criterion.uri) {
          $$1(`input[name=${criterion.id}-select]`, $criterionContainer).select2({
            multiple: true,
            ajax: {
              url: criterion.uri,
              dataType: 'json',
              data: function (term) {
                return {
                  subject: term
                };
              },
              results: response => ({
                results: response.data.map(option => ({
                  id: valueMapping === 'uri' ? option.uri : option.label,
                  text: option.label
                }))
              })
            },
            initSelection: function (element, callback) {
              const data = [];
              $$1(element.val().split(',')).each(function () {
                data.push({
                  id: this,
                  text: this
                });
              });
              callback(data);
            }
          });
        }
        return $criterionContainer;
      }

      /**
       * Fetches initial criterion label from api in case the value mapping is uri
       * @param {object} criterion - criterion to be managed
       */
      function getInitialCriterionLabel(criterion) {
        const valueMapping = criteriaMapping[criterion.type];
        if (valueMapping !== 'uri' || !criterion.value) {
          return Promise.resolve({
            id: criterion.value,
            text: criterion.value
          });
        }
        return $$1.ajax({
          type: 'GET',
          url: criterion.uri,
          dataType: 'json'
        }).then(_ref => {
          let {
            data
          } = _ref;
          if (Array.isArray(criterion.value)) {
            return criterion.value.map(v => ({
              id: v,
              text: (data.find(d => d.uri === v) || {}).label
            }));
          }
          let c = data.find(d => d.uri === criterion.value) || {};
          return {
            text: c.label,
            id: criterion.value
          };
        });
      }

      /**
       * Sets initial value for rendered criterion and sets binding between view and state
       * @param {object} criterion - criterion to be managed
       * @param {object} $criterionContainer - rendered criterion
       */
      function bindCriterionValue(criterion, $criterionContainer) {
        getInitialCriterionLabel(criterion).then(initialCriterion => {
          if (criterion.type === criteriaTypes.text) {
            // set initial value
            $$1('input', $criterionContainer).val(criterion.value);
            // set event to bind input value to critariaState
            $$1('input', $criterionContainer).on('change', function () {
              criterion.value = $$1(this).val() || null;
            });
          } else if (criterion.type === criteriaTypes.list && criterion.uri) {
            // set initial value
            if (criterion.value) {
              $$1(`input[name=${criterion.id}-select]`, $criterionContainer).select2('data', initialCriterion);
            }
            criterion.logic = criterion.logic || criteriaLogic.and;
            $$1(`input[name="${criterion.id}-logic"][value="${criterion.logic}"]`, $criterionContainer).prop('checked', true);
            // set event to bind input value to critariaState
            $$1(`input[name=${criterion.id}-select]`, $criterionContainer).on('change', event => {
              criterion.value = event.val;
            });
            // set event to bind logic selector to critariaState
            $$1(`input[name="${criterion.id}-logic"]`, $criterionContainer).on('change', event => {
              criterion.logic = event.target.value;
            });
          } else {
            // set initial value
            if (criterion.value) {
              criterion.value.forEach(selectedValue => {
                $$1(`input[value=${selectedValue}]`, $criterionContainer).prop('checked', true);
              });
            }
            // set event to bind input value to critariaState
            $$1('input[type="checkbox"]', $criterionContainer).on('change', function () {
              criterion.value = $$1(this).closest('.filter-container').find('input[type=checkbox]:checked').get().map(element => element.value);
            });
          }
        });
      }

      /**
       * Removes a criterion from advanced criteria container when user clicks on the criterion close icon.
       * It also adds the option element to criteria select so removed criterion can be rendered again
       * @param {object} event - click event triggered on closing icon
       */
      function removeCriterion(event) {
        const criterion = event.data.criterion;
        const newOption = createCriteriaOption(criterion);
        const criterionKey = getCriterionStateId(criterion);

        // remove criterion and append new criterion to select options
        $$1(this).parent().remove();
        $criteriaSelect.append(newOption);

        // reset criterion values on criteriaState
        criteriaState[criterionKey].rendered = false;
        criteriaState[criterionKey].value = null;

        // check if advanced criteria container is no longer scrollable
        if ($advancedCriteriaContainer.get(0).scrollHeight <= $advancedCriteriaContainer.outerHeight()) {
          $advancedCriteriaContainer.removeClass('scrollable');
        }
      }

      /**
       * Parses received criteria from BE to the data structure required for criteria selector. To do so,
       * appends every criterion into criteria array and then returns a duplicate-free version of it
       * considering label property as uniqueness criterion
       * @param {Array} classes - array of classes with the metadata (aka criteria) for each one of them
       * @returns {Array} - criteria array
       */
      function formatCriteria(classTree) {
        let criteria = [];
        _.forEach(classTree, classInstance => {
          criteria.push(...classInstance.metadata);
        });

        // extends each criterion with an id that can be use as a valid css class
        _.forEach(criteria, criterion => {
          criterion.label = getCriterionLabel(criterion);
          criterion.id = criterion.propertyUri.replace(/^[^a-zA-Z]*|[^a-zA-Z0-9]*/g, '');
        });
        return criteria;
      }

      /**
       * Manages the new set of available criteria. To do so, removes warning container and every
       * selectable criterion. Then removes from criteriaState and view every deprecated criterion,
       * updates criteriaState with the new available criteria set, and renders new warning
       * message if required
       * @param {array} criteria - array of class properties
       */
      function updateCriteria(criteria) {
        $$1('.invalid-criteria-warning-container').remove();
        $criteriaSelect.find('option:not(:first-child)').remove();
        const invalidCriteria = deleteDeprecatedCriteria(criteria);
        extendCriteria(criteria);
        renderWarningMessage(invalidCriteria);
      }

      /**
       * If there is any invalid criterion, renders an explanatory warning message
       * @param {array} invalidCriteria - array of string containing the label of every invalid criterion (those that were rendered but are no longer available)
       */
      function renderWarningMessage(invalidCriteria) {
        if (invalidCriteria.length > 0) {
          const invalidCriteriaWarning = invalidCriteriaWarningTpl({
            invalidCriteria
          });
          $advancedCriteriaContainer.prepend(invalidCriteriaWarning);
          $$1('.invalid-criteria-warning-container .select2-search-choice-close', $advancedCriteriaContainer).on('click', function () {
            $$1(this).parent().remove();
          });
        }
      }

      /**
       * Loops through current criteriaState so every criterion that is no longer available in the new
       * available criteria set is removed from criteriaState and from view, in case it had been
       * rendered. In that case it is also pushed into invalidCriteria array to be latter
       * included in the warning message
       * @param {array} criteria - array containing new set of criteria for current class
       * @returns {array} - array of strings with each deprecated criterion that was being displayed
       */
      function deleteDeprecatedCriteria(criteria) {
        const invalidCriteria = [];
        _.forEach(criteriaState, oldCriterion => {
          const deprecatedCriterion = !criteria.find(newCriterion => newCriterion.label === oldCriterion.label);
          const oldCriterionKey = getCriterionStateId(oldCriterion);
          if (deprecatedCriterion) {
            if (criteriaState[oldCriterionKey].rendered) {
              $advancedCriteriaContainer.find(`.${oldCriterion.id}-filter`).remove();
              invalidCriteria.push(oldCriterion.label);
            }
            delete criteriaState[oldCriterionKey];
          }
        });
        return invalidCriteria;
      }

      /**
       * Loops through new criteria set and checks if each new criterion was already present or not on criteriaState
       * and updates view and selectable criteria list accordingly
       * @param {array} criteria - array containing new set of criteria for current class
       */
      function extendCriteria(criteria) {
        criteria.forEach(criterion => {
          let createOption = true;
          const criteriaStateId = getCriterionStateId(criterion);

          // if new criterion was already on criteriaState and had to be rendered, we avoid creating an option for it and render it if it was not
          if (criteriaState[criteriaStateId] && criteriaState[criteriaStateId].rendered === true) {
            createOption = false;
            if ($advancedCriteriaContainer.find(`.${criterion.id}-filter`).length === 0) {
              addNewCriterion(criteriaStateId);
            }
          } else {
            // if new criterion was not on criteriaState we add it
            criteriaState[criteriaStateId] = criterion;
            criteriaState[criteriaStateId].rendered = false;
            criteriaState[criteriaStateId].value = null;
          }

          // create new option element to criteria select
          if (createOption) {
            $criteriaSelect.append(createCriteriaOption(criterion));
          }
        });
      }

      /**
       * Creates a new option element
       * with attributes to use in select2 markup
       * @param {Object} criterion
       * @returns {HTMLOptionElement} Single option criteria
       */
      function createCriteriaOption(criterion) {
        const label = criterion.label;
        let classLabel = '';
        let alias = '';
        let option;
        if (criterion.isDuplicated) {
          classLabel = criterion.class.label || '';
          alias = criterion.alias || '';
        }
        option = new Option(label, getCriterionStateId(criterion), false, false);
        option.setAttribute('label', label);
        option.setAttribute('alias', alias);
        option.setAttribute('class-label', classLabel);
        return option;
      }

      /**
       * @param {Object} criterion
       * @returns String
       */
      function getCriterionStateId(criterion) {
        return criterion.propertyUri;
      }

      /**
       * @param {Object} criterion
       * @returns String
       */
      function getCriterionLabel(criterion) {
        return criterion.label;
      }

      // return initialized instance of searchModal
      return instance.init(config);
    }

    return advancedSearchFactory;

});
