define(['lodash', 'i18n', 'handlebars', 'core/providerRegistry', 'ui/component', 'ui/form/validator/validator', 'ui/form/validator/renderer', 'ui/form/widget/providers/default', 'lib/handlebars/helpers', 'css!ui/form/widget/css/widget.css'], function (_, __, Handlebars, providerRegistry, componentFactory, validatorFactory, validatorRendererFactory, defaultProvider, Helpers0, widget_css) { 'use strict';

    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    providerRegistry = providerRegistry && Object.prototype.hasOwnProperty.call(providerRegistry, 'default') ? providerRegistry['default'] : providerRegistry;
    componentFactory = componentFactory && Object.prototype.hasOwnProperty.call(componentFactory, 'default') ? componentFactory['default'] : componentFactory;
    validatorFactory = validatorFactory && Object.prototype.hasOwnProperty.call(validatorFactory, 'default') ? validatorFactory['default'] : validatorFactory;
    validatorRendererFactory = validatorRendererFactory && Object.prototype.hasOwnProperty.call(validatorRendererFactory, 'default') ? validatorRendererFactory['default'] : validatorRendererFactory;
    defaultProvider = defaultProvider && Object.prototype.hasOwnProperty.call(defaultProvider, 'default') ? defaultProvider['default'] : defaultProvider;
    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); partials = this.merge(partials, Handlebars.partials); data = data || {};
      var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression, self=this;

    function program1(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "type=\"";
      if (helper = helpers.type) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.type); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" ";
      return buffer;
      }

      buffer += "<div class=\"form-widget ";
      if (helper = helpers.widgetType) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.widgetType); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">\n    <div class=\"widget-label\">\n        ";
      stack1 = self.invokePartial(partials['ui-form-widget-label'], 'ui-form-widget-label', depth0, helpers, partials, data);
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    </div>\n    <div class=\"widget-field\">\n        <input class=\"widget-input-inline\" ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.type), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "name=\"";
      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)
        + "\" value=\"";
      if (helper = helpers.value) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.value); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" />\n    </div>\n</div>\n";
      return buffer;
      });
    function widgetTpl(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, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;

    function program1(depth0,data) {
      
      var buffer = "", helper, options;
      buffer += "\n    <abbr title=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "This field is required", options) : helperMissing.call(depth0, "__", "This field is required", options)))
        + "\">*</abbr>\n    ";
      return buffer;
      }

      buffer += "<label for=\"";
      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)
        + "\">\n    ";
      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)
        + "\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.required), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n</label>\n";
      return buffer;
      });
    function labelTpl(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) 2019 Open Assessment Technologies SA ;
     */

    /**
     * @typedef {Object} widgetConfig Defines the config entries available to setup a form widget
     * @property {String} widget - The type of widget.
     *                             It will be used by the main factory to retrieve
     *                             the implementation from the internal registry.
     * @property {String} widgetType - The internal type of widget
     * @property {String} uri - The identifier of the widget
     * @property {String} [label] - The label of the widget
     * @property {String|String[]} [value] - The value of the widget. Depending on the widget's type,
     *                                       it can be a single or a multiple value
     * @property {widgetRangeValue[]} [range] - Array of values used in multi-elements widgets (like combo or checkbox)
     * @property {Boolean} [required] - Tells if the value is required
     * @property {validationRule|validationRule[]|validator} [validation] - Optional validators to add
     */

    /**
     * @typedef {Object} widgetRangeValue Defines a value to use in a widget range
     * @property {String} uri - The identifier of the value
     * @property {String} label - The label for the value
     */

    /**
     * Some default config
     * @type {widgetConfig}
     */
    const defaults = {
      widgetType: 'input-box',
      required: false,
      label: __('Label'),
      value: '',
      range: []
    };

    /**
     * Gets the provider with respect to the provided config
     * @param {widgetConfig} config
     * @returns {Object}
     * @throws {TypeError} if the config is not valid or if the provider does not exist
     */
    function getWidgetProvider(config) {
      if (!_.isPlainObject(config)) {
        throw new TypeError('The config must be an object');
      }
      if (!_.isString(config.uri) || !config.uri) {
        throw new TypeError('The config must contain an uri');
      }
      if (!_.isString(config.widget) || !config.widget) {
        throw new TypeError('The config must declare a type of widget');
      }
      return widgetFactory.getProvider(config.widget);
    }

    /**
     * Makes sure a value is an array
     * @param {*} value
     * @returns {Array}
     */
    function forceArray(value) {
      if (value && !_.isArray(value)) {
        value = [value];
      } else {
        value = value || [];
      }
      return value;
    }

    /**
     * Factory that builds a form element based on its config.
     *
     * * @example
     *  const container = $('.my-container', $container);
     *
     *  widgetFactory.registerProvider('textinput', widgetTextInputProvider);
     *
     *  const config = {
     *          widget: 'textinput'
     *          uri: 'text',
     *          label: 'Text',
     *          required: true
     *  };
     *
     *  const widget = widgetFactory(container, config)
     *      .on('change', function(value) {
     *          // ...
     *      });
     *
     * @param {HTMLElement|String} container
     * @param {widgetConfig} config
     * @param {String} config.widget - The type of widget.
     *                                 It will be used by the main factory to retrieve
     *                                 the implementation from the internal registry.
     * @param {String} config.uri - The identifier of the widget
     * @param {String} [config.label] - The label of the widget
     * @param {String|String[]} [config.value] - The value of the widget. Depending on the widget's type,
     *                                           it can be a single or a multiple value
     * @param {widgetRangeValue[]} [config.range] - Array of values used in multi-elements widgets (like combo or checkbox)
     * @param {Boolean} [config.required] - Tells if the value is required
     * @param {validationRule|validationRule[]|validator} [config.validation] - Optional validators to add
     * @returns {widgetForm}
     * @fires ready - When the component is ready to work
     */
    function widgetFactory(container, config) {
      let widget;
      let validator;
      let validatorRenderer;
      const provider = getWidgetProvider(config);

      /**
       * Reflects the invalid state to the component
       * @param {Boolean} invalid
       * @param {String[]} [messages]
       */
      const setInvalidState = (invalid, messages) => {
        widget.setState('invalid', invalid);
        if (validatorRenderer) {
          if (invalid) {
            validatorRenderer.display(messages);
          } else {
            validatorRenderer.clear();
          }
        }
      };

      /**
       * Delegate a call to the provider, or fallback to the default implementation
       * @param {String} method - The name of the method to call.
       * @param {...} args - Extra parameters
       * @returns {*}
       */
      const delegate = function (method) {
        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          args[_key - 1] = arguments[_key];
        }
        if (_.isFunction(provider[method])) {
          return provider[method].apply(widget, args);
        }
        return defaultProvider[method].apply(widget, args);
      };

      /**
       * Triggers the change event
       * @returns {widgetForm}
       * @fires change
       */
      const notifyChange = () => {
        /**
         * @event change
         * @param {String|String[]} value
         * @param {String} uri
         */
        widget.trigger('change', widget.getValue(), widget.getUri());
      };

      /**
       * @typedef {component} widgetForm
       */
      const widgetApi = {
        /**
         * Gets the widget's URI
         * @returns {String}
         */
        getUri() {
          return this.getConfig().uri;
        },
        /**
         * Gets the value of the widget
         * @returns {String|String[]}
         */
        getValue() {
          return delegate('getValue');
        },
        /**
         * Gets the raw value of the widget
         * @returns {*}
         */
        getRawValue() {
          return delegate('getRawValue');
        },
        /**
         * Sets the value of the widget
         * @param {String|String[]} value
         * @returns {widgetForm}
         * @fires change after the value has been changed
         */
        setValue(value) {
          this.getConfig().value = value;
          delegate('setValue', value);
          notifyChange();
          return this;
        },
        /**
         * Gets access to the validation engine
         * @returns {validator}
         */
        getValidator() {
          return validator;
        },
        /**
         * Sets the validation engine
         * @param {validationRule|validationRule[]|validator} validation
         * @returns {widgetForm}
         */
        setValidator(validation) {
          if (validation && _.isFunction(validation.validate)) {
            validator = validation;
          } else {
            if (validation && !validation.validations) {
              if (!_.isArray(validation)) {
                validation = [validation];
              }
              validation = {
                validations: validation
              };
            }
            validator = validatorFactory(validation);
          }
          return this;
        },
        /**
         * Resets the widget to the default validators
         * @returns {widgetForm}
         */
        setDefaultValidators() {
          // restore factory default validators
          this.setValidator(this.getConfig().validator);

          // then apply provider default validators
          delegate('setDefaultValidators');
          return this;
        },
        /**
         * Resets the widget to its default value
         * @returns {widgetForm}
         */
        reset() {
          delegate('reset');
          setInvalidState(false);
          return this;
        },
        /**
         * Validates the widget
         * @returns {Promise}
         */
        validate() {
          return this.getValidator().validate(this.getRawValue()).then(res => {
            setInvalidState(false);
            return res;
          }).catch(err => {
            setInvalidState(true, err);
            return Promise.reject(err);
          });
        },
        /**
         * Gets access to the actual form element
         * @returns {jQuery|null}
         */
        getWidgetElement() {
          if (this.is('rendered')) {
            return delegate('getWidgetElement');
          }
          return null;
        }
      };
      widget = componentFactory(widgetApi, defaults).setTemplate(provider.template || widgetTpl).on('init', function onWidgetInit() {
        this.setDefaultValidators();

        // auto render on init (defer the call to give a chance to the init event to be completed before)
        _.defer(() => this.render(container));
      }).on('render', function onWidgetRender() {
        // reflect the type of widget
        this.setState(this.getConfig().widgetType, true);

        // react to data change
        this.getWidgetElement().on('change blur', () => {
          const value = this.getValue();
          if (value !== this.getConfig().value) {
            this.getConfig().value = value;
            notifyChange();
          }
        });

        /**
         * @event ready
         */
        validatorRenderer = validatorRendererFactory(this.getElement()).spread(this, 'error ready');
      }).on('disable', function onWidgetDisable() {
        if (this.is('rendered')) {
          this.getWidgetElement().prop('disabled', true);
        }
      }).on('enable', function onWidgetEnable() {
        if (this.is('rendered')) {
          this.getWidgetElement().prop('disabled', false);
        }
      }).on('destroy', function onWidgetDestroy() {
        if (validatorRenderer) {
          validatorRenderer.destroy();
          validatorRenderer = null;
        }
      });
      if (config) {
        // the range must be an array
        config.range = forceArray(config.range);
      }
      _.defer(() => widget.init(provider.init.call(widget, config || {}) || config));
      return widget;
    }

    // expose a partial that can be used by every form widget to inject the label markup
    Handlebars.registerPartial('ui-form-widget-label', labelTpl);

    // the widgetFactory is also a providers registry
    providerRegistry(widgetFactory);

    return widgetFactory;

});
