define(['ui/component', 'handlebars', 'lib/handlebars/helpers', 'ui/button', 'css!ui/propertySelector/css/propertySelector.css', 'jquery'], function (component, Handlebars, Helpers0, buttonFactory, propertySelector_css, $$1) { 'use strict';

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

    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);  


      return "<div class=\"property-selector-container\">\n    <input type=\"text\" class=\"search-property\"/>\n    <ol class=\"property-list-container\">\n    </ol>\n    <div class=\"control-buttons-container\">\n    </div>\n</div>";
      });
    function propertySelectorTpl(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, helperMissing=helpers.helperMissing, functionType="function", escapeExpression=this.escapeExpression, self=this;

    function program1(depth0,data) {
      
      
      return "checked";
      }

    function program3(depth0,data) {
      
      var buffer = "", stack1, helper, options;
      buffer += "<span class=\"property-description-alias\">(";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.alias), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.alias), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ")</span>";
      return buffer;
      }

    function program5(depth0,data) {
      
      var buffer = "", stack1, helper, options;
      buffer += "<span class=\"property-description-class\">/ ";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.classLabel), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.classLabel), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</span>";
      return buffer;
      }

      buffer += "<li class=\"property-description-container\">\n    <label class=\"property-description\">\n        <span class=\"property-description-checkbox\">\n            <input type=\"checkbox\" data-property-id=\"";
      if (helper = helpers.id) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.id); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.selected), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += " />\n            <span class=\"icon-checkbox\"></span>\n        </span>\n        <span class=\"property-description-label\">\n            ";
      stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.label), options) : helperMissing.call(depth0, "dompurify", (depth0 && depth0.label), options));
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.alias), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.classLabel), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </span>\n    </label>\n</li>";
      return buffer;
      });
    function propertyDescriptionTpl(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, 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$2(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) 2022 (original work) Open Assessment Technologies SA ;
     */

    /**
     * A list of fields in which we can search a term.
     * @type {string[]}
     * @private
     */
    const searchableFields = ['label', 'alias', 'classLabel'];

    /**
     * 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 a property selector with respect to given options.
     * @param {object} [config]
     * @returns {*}
     */
    function propertySelectorFactory(config) {
      //element references
      let $container;
      let $buttonsContainer;
      let $propertyListContainer;
      let $searchInput;
      let availableProperties = [];
      let selectedProperties;
      let search = '';
      let searchRedrawTimeoutId;
      const parentGap = 20;
      const searchRedrawTimeout = 500;
      const instance = component({
        /**
         * Updates the list
         */
        redrawList() {
          $propertyListContainer.empty();
          const propertiesToRender = [];
          availableProperties.forEach(property => {
            property.selected = selectedProperties.has(property.id);
            if (search === '' || includeSearch(property, search)) {
              propertiesToRender.push(createPropertyOption(property, search));
            }
          });
          $propertyListContainer.append(propertiesToRender);
          this.trigger('redraw');
        },
        /**
         * @typedef propertySelectorPosition
         * @property {number} top top position of container element from parent
         * @property {number} right right position of container element from parent
         * @property {number} bottom bottom position of container element from parent
         * @property {number} left left position of container element from parent
         */
        /**
         * @typedef propertyDescription
         * @property {string} id id of property
         * @property {string} label label of the property
         * @property {string} alias alias of the property
         * @property {string} classLabel label of the property class
         */
        /**
         * @typedef propertySelectorData
         * @property {propertySelectorPosition} position position of the property selector modal top, right, bottom, left
         * @property {Array<string>} selected array of selected property id
         * @property {Array<propertyDescription>} available array of available proprty descriptions
         */
        /**
         * Sets data
         * @param {propertySelectorData} data
         */
        setData(data) {
          if (data.available) {
            availableProperties = sortBy(data.available, 'label');
          }
          selectedProperties = new Set(data.selected);
          this.redrawList();
        },
        toggle() {
          if (this.shown) {
            this.hide();
          } else {
            this.show();
            this.redrawList();
          }
        }
      }).setTemplate(propertySelectorTpl).on('render', function () {
        //component parts reference assignments
        $container = instance.getElement();
        $propertyListContainer = $$1('.property-list-container', $container);
        $buttonsContainer = $$1('.control-buttons-container', $container);
        $propertyListContainer.on('click', e => {
          if (e.target.dataset.propertyId) {
            if (e.target.checked) {
              selectedProperties.add(e.target.dataset.propertyId);
            } else {
              selectedProperties.delete(e.target.dataset.propertyId);
            }
          }
        });
        positionContainer($container, this.config.data.position);
        addButtons($buttonsContainer);
        this.setData(this.config.data);

        //search event setup
        $searchInput = $$1('input.search-property', $container);
        $searchInput.on('input', function () {
          search = $$1(this).val();
          if (searchRedrawTimeoutId) {
            clearTimeout(searchRedrawTimeoutId);
          }
          searchRedrawTimeoutId = setTimeout(instance.redrawList, searchRedrawTimeout);
        });
        this.show();
        this.trigger('ready');
      }).on('destroy', unregisterPageClick).on('hide', function () {
        this.shown = false;
        unregisterPageClick();
      }).on('show', function () {
        registerPageClick();
        this.shown = true;
      }).on('cancel select', function () {
        this.hide();
        $searchInput.val('');
        search = '';
      });

      /**
       * Close the component when clicking outside.
       * @param event
       */
      function pageClick(event) {
        if ($$1(event.target).closest('.property-selector-container').length) {
          return;
        }
        instance.trigger('cancel');
      }

      /**
       * Listens to the clicks outside the component
       */
      function registerPageClick() {
        setTimeout(() => document.addEventListener('click', pageClick), 0);
      }

      /**
       * Stops listening to the clicks outside the component
       */
      function unregisterPageClick() {
        setTimeout(() => document.removeEventListener('click', pageClick), 0);
      }

      /**
       * Checks if a searchable field contains the searched term.
       * @param {object} property
       * @param {string} searchString
       * @returns {boolean}
       */
      function includeSearch(property, searchString) {
        const searchedTerm = searchString.toLowerCase();
        return searchableFields.some(field => 'string' === typeof property[field] && property[field].toLowerCase().includes(searchedTerm));
      }

      /**
       * 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) {
        const reg = new RegExp(searchString, 'gi');
        return text.replace(reg, str => highlightedTextTpl({
          text: str
        }));
      }

      /**
       * Creates property description list element
       * @param {Object} property
       * @returns JQuery element containing property description
       */
      function createPropertyOption(property, searchString) {
        const descriptionData = Object.assign({}, property);
        if (searchString !== '') {
          searchableFields.forEach(field => {
            if (descriptionData[field]) {
              descriptionData[field] = highlightCharacter(descriptionData[field], searchString);
            }
          });
        }
        return $$1(propertyDescriptionTpl(descriptionData));
      }

      /**
       * Adds buttons to container
       * @param {jQuery} $targetContainer
       */
      function addButtons($targetContainer) {
        const cancelButton = buttonFactory({
          id: 'cancel',
          label: 'Cancel',
          type: 'info',
          cls: 'btn-secondary'
        }).on('click', () => instance.trigger('cancel'));
        const saveButton = buttonFactory({
          id: 'save',
          label: 'Save',
          type: 'info'
        }).on('click', () => instance.trigger('select', [...selectedProperties]));
        cancelButton.render($targetContainer);
        saveButton.render($targetContainer);
      }

      /**
       * Positions element inside parent
       * @param {jQuery} $el element to apply positioning
       * @param {Object} position object  { top, left, right, bottom } top OR bottom is required
       */
      function positionContainer($el, position) {
        let {
          top,
          left,
          right,
          bottom
        } = position;
        let maxHeight;
        if (typeof top === 'undefined' && typeof bottom === 'undefined') {
          top = 0;
          bottom = 0;
          maxHeight = $el.parent().height();
        } else if (typeof bottom === 'undefined') {
          maxHeight = $el.parent().height() - top - parentGap;
        } else if (typeof top === 'undefined') {
          maxHeight = $el.parent().height() - bottom - parentGap;
        }
        $el.css({
          top,
          left,
          right,
          bottom,
          maxHeight
        });
      }
      setTimeout(() => instance.init(config), 0);
      return instance;
    }

    return propertySelectorFactory;

});
