define(['jquery', 'lodash', 'i18n', 'ui/component', 'handlebars', 'lib/handlebars/helpers', 'css!ui/class/css/selector.css'], function ($$1, _, __, component, Handlebars, Helpers0, selector_css) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    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;

    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 = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression, self=this;

    function program1(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n    <a href=\"#\" class=\"selected truncate\" data-uri=\"";
      if (helper = helpers.classUri) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.classUri); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" title=\"";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</a>\n    ";
      return buffer;
      }

    function program3(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n    <a href=\"#\" class=\"selected truncate empty\">";
      if (helper = helpers.placeholder) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.placeholder); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</a>\n    ";
      return buffer;
      }

      buffer += "<div class=\"class-selector\">\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.classUri), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    <div class=\"options folded\">\n        <ul>";
      if (helper = helpers.tree) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.tree); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "</ul>\n    </div>\n</div>\n";
      return buffer;
      });
    function selectorTpl(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, functionType="function", escapeExpression=this.escapeExpression, self=this;

    function program1(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n        <ul>\n            ";
      if (helper = helpers.childList) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.childList); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </ul>\n    ";
      return buffer;
      }

      buffer += "<li>\n    <a href=\"#\" data-uri=\"";
      if (helper = helpers.uri) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.uri); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" title=\"";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">";
      if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</a>\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.childList), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n</li>\n\n";
      return buffer;
      });
    function listItemTpl(data, options, asString) {
      var html = Template$1(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) 2017 (original work) Open Assessment Technologies SA ;
     */
    var defaultConfig = {
      classes: [],
      placeholder: __('Select a class')
    };

    /**
     * Create a brand new class selector
     * @param {jQueryElement} $container - where the component is rendered
     * @param {Object} [config] - the configuration
     * @param {String} [config.classUri] - the selected class URI (by default)
     * @param {String} [config.placeholder] - when nothing is selected
     * @param {Object[]} [config.classes] - the class tree data, a collection of objects as {uri, label, children}
     * @returns {classSelector} the component itself
     */
    function classesSelectorFactory($container, config) {
      var $selected;
      var $options;

      //create an index to manage classes easily
      var classList = {};

      /**
       * Build the Class Tree from the data
       * @param {Object[]} classes - as {uri, label, children}
       * @returns {String} the HTML tree
       */
      var buildTree = function buildTree(classes) {
        var nodeToListItem = function nodeToListItem(acc, node) {
          var item;
          if (node.uri && node.label) {
            item = _.clone(node);
            classList[item.uri] = item;
            if (node.children && node.children.length) {
              item.childList = _.reduce(node.children, nodeToListItem, '');
            }
            acc += listItemTpl(item);
          }
          return acc;
        };
        return _.reduce(classes, nodeToListItem, '');
      };
      var isOutsideOfThisContainer = function isOutsideOfThisContainer(e) {
        if (!$options[0].contains(e.target) && $selected[0] !== e.target && !$options[0].classList.contains('folded')) {
          $selected.toggleClass('open');
          $options.toggleClass('folded');
        }
      };

      /**
       * @typedef {classSelector} the component
       */
      var classSelector = component({
        /**
         * Set the selected class
         * @param {String} uri - the class URI to select
         * @returns {classSelector} chains
         * @fires classSelector#change
         */
        setValue: function setValue(uri) {
          if (this.config.classUri !== uri && !_.isUndefined(classList[uri])) {
            this.config.classUri = uri;
            if (this.is('rendered') && $selected.length) {
              $selected.text(classList[uri].label).attr({
                title: classList[uri].label,
                'data-uri': uri
              }).data('uri', uri).removeClass('empty');

              /**
               * @event classSelector#change
               * @param {String} uri - the selected class URI
               * @param {Object} class - the class node
               */
              this.trigger('change', uri, classList[uri]);
            }
          }
          return this;
        },
        /**
         * Get the selected class
         * @returns {String} the selected class URI
         */
        getValue: function getValue() {
          return this.config.classUri;
        },
        /**
         * Get the selected class node
         * @returns {Object} the node
         */
        getClassNode: function getClassNode() {
          var node = null;
          if (this.config.classUri && classList[this.config.classUri]) {
            node = classList[this.config.classUri];
          }
          return node;
        },
        /**
         * Empty the component: remove the selection, set back the placeholder
         * @returns {classSelector} chains
         * @fires classSelector#change
         */
        empty: function empty() {
          if (this.is('rendered') && $selected.length && this.config.classUri) {
            this.config = _.omit(this.config, 'classUri');
            $selected.text(this.config.placeholder).removeAttr('title').data('uri', null).removeAttr('data-uri').addClass('empty');
            this.trigger('change');
          }
          return this;
        },
        /**
         * Does the given node exists ?
         *
         * @param {Object|String} node - the node or directly the URI
         * @param {String} [node.uri]
         * @returns {Boolean}
         */
        hasNode: function hasNode(node) {
          var uri;
          if (node && classList) {
            uri = _.isString(node) ? node : node.uri;
            return _.has(classList, uri);
          }
          return false;
        },
        /**
         * Removes the given node
         *
         * @param {Object|String} node - the node or directly the URI
         * @param {String} [node.uri]
         * @returns {Boolean}
         */
        removeNode: function removeNode(node) {
          var uri;
          if (this.hasNode(node)) {
            uri = _.isString(node) ? node : node.uri;

            //if the node is selected, we remove the selection
            if (uri === this.config.classUri) {
              this.empty();
            }
            classList = _.omit(classList, uri);
            if (this.is('rendered')) {
              $$1('[data-uri="' + uri + '"]', this.getElement()).parent('li').remove();
            }
            return !this.hasNode(node);
          }
          return false;
        },
        /**
         * Add a node.
         *
         * @param {Object} node - the node to add
         * @param {String} node.uri
         * @param {String} node.label
         * @param {Object[]} node.children - let's you add a sub hierarchy
         * @param {String} [parentUri] - where to append the new node
         * @returns {classSelector} chains
         */
        addNode: function addNode(node, parentUri) {
          var subTree;
          var $parentNode;
          if (this.is('rendered') && node && !this.hasNode(node)) {
            //this will also update the classList
            subTree = buildTree([node]);
            if (parentUri) {
              $parentNode = $$1('[data-uri="' + parentUri + '"]', $options);
            }
            if (!$parentNode || !$parentNode.length) {
              $parentNode = $$1('[data-uri]:first-child', $options);
            }

            //attach the sub tree
            if ($parentNode.parent('li').children('ul').length) {
              $parentNode.parent('li').children('ul').append(subTree);
            } else {
              $parentNode.parent('li').append('<ul>' + subTree + '</ul>');
            }
          }
          return this;
        },
        /**
         * Update a node (the label for now)
         *
         * @param {Object} node - the node to update
         * @param {String} node.uri
         * @param {String} node.label
         * @returns {classSelector} chains
         */
        updateNode: function updateNode(node) {
          if (node && node.uri && this.hasNode(node) && classList[node.uri].label !== node.label) {
            classList[node.uri].label = node.label;
            if (this.is('rendered')) {
              $$1('[data-uri="' + node.uri + '"]', this.getElement()).attr('title', node.label).text(node.label);
            }
          }
          return this;
        },
        /**
         * Update multiple nodes, recursively
         * @see {classSelector#updateNode}
         *
         * @param {Object[]} node - the node to update
         * @param {String} node.uri
         * @param {String} node.label
         * @param {Object[]} node.children
         * @returns {classSelector} chains
         */
        updateNodes: function updateNodes(nodes) {
          var self = this;
          _.forEach(nodes, function (node) {
            if (node.children) {
              self.updateNodes(node.children);
            }
            self.updateNode(node);
          });
          return this;
        }
      }, defaultConfig).setTemplate(selectorTpl).on('init', function () {
        //generate the tree
        this.config.tree = buildTree(this.config.classes);
        if (this.config.classUri && classList[this.config.classUri]) {
          //set the default label
          this.config.label = classList[this.config.classUri].label;
        }
        this.render($container);
      }).on('render', function () {
        var self = this;
        var $component = this.getElement();
        $selected = $$1('.selected', $component);
        $options = $$1('.options', $component);
        window.addEventListener('click', isOutsideOfThisContainer);
        $selected.on('click', function (e) {
          e.preventDefault();
          $selected.toggleClass('open');
          $options.toggleClass('folded');
        });
        $options.on('click', 'a', function (e) {
          e.preventDefault();
          self.setValue($$1(this).data('uri'));
          $selected.toggleClass('open');
          $options.toggleClass('folded');
        });
      }).on('destroy', function () {
        classList = {};
        window.removeEventListener('click', isOutsideOfThisContainer);
      });
      _.defer(function () {
        classSelector.init(config);
      });
      return classSelector;
    }

    return classesSelectorFactory;

});
