define(['lodash', 'i18n', 'moment', 'ui/component', 'lib/flatpickr/flatpickr', 'lib/flatpickr/l10n/index', 'handlebars', 'lib/handlebars/helpers', 'css!lib/flatpickr/flatpickr.css', 'css!ui/datetime/css/picker.css'], function (_, __, moment, component, flatpickr, flatpickrLocalization, Handlebars, Helpers0, flatpickr_css, picker_css) { 'use strict';

    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    moment = moment && Object.prototype.hasOwnProperty.call(moment, 'default') ? moment['default'] : moment;
    component = component && Object.prototype.hasOwnProperty.call(component, 'default') ? component['default'] : component;
    flatpickr = flatpickr && Object.prototype.hasOwnProperty.call(flatpickr, 'default') ? flatpickr['default'] : flatpickr;
    flatpickrLocalization = flatpickrLocalization && Object.prototype.hasOwnProperty.call(flatpickrLocalization, 'default') ? flatpickrLocalization['default'] : flatpickrLocalization;
    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, helperMissing=helpers.helperMissing, self=this;

    function program1(depth0,data) {
      
      
      return "control-buttons";
      }

    function program3(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "id=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.id)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program5(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "name=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program7(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "value=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.value)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program9(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "placeholder=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.placeholder)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program11(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "pattern=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.pattern)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program13(depth0,data) {
      
      var buffer = "", stack1;
      buffer += "aria-label=\""
        + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.label)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
        + "\"";
      return buffer;
      }

    function program15(depth0,data) {
      
      var buffer = "", helper, options;
      buffer += "\n    <button type=\"button\" disabled class=\"picker-toggle\" title=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Toggle the date time picker", options) : helperMissing.call(depth0, "__", "Toggle the date time picker", options)))
        + "\" aria-label=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Toggle the date time picker", options) : helperMissing.call(depth0, "__", "Toggle the date time picker", options)))
        + "\" >\n        <span class=\"icon-calendar\" role=\"img\"></span>\n    </button>\n    <button type=\"button\" disabled class=\"picker-clear\" title=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Clear the date time picker", options) : helperMissing.call(depth0, "__", "Clear the date time picker", options)))
        + "\" aria-label=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Clear the date time picker", options) : helperMissing.call(depth0, "__", "Clear the date time picker", options)))
        + "\">\n        <span class=\"icon-reset\" role=\"img\"></span>\n    </button>\n    ";
      return buffer;
      }

      buffer += "<div class=\"datetime-picker ";
      if (helper = helpers.setup) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.setup); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + " ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.controlButtons), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\">\n    <input type=\"text\" disabled\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.id), {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, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.name), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.value), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.placeholder), {hash:{},inverse:self.noop,fn:self.program(9, program9, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.pattern), {hash:{},inverse:self.noop,fn:self.program(11, program11, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        ";
      stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.field)),stack1 == null || stack1 === false ? stack1 : stack1.label), {hash:{},inverse:self.noop,fn:self.program(13, program13, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    />\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.controlButtons), {hash:{},inverse:self.noop,fn:self.program(15, program15, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n</div>\n";
      return buffer;
      });
    function dateTimePickerTpl(data, options, asString) {
      var html = Template(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 (original work) Open Assessment Technologies SA
     *
     */

    /**
     * The supported formats
     */
    var formats = {
      date: {
        default: 'YYYY-MM-DD',
        localized: 'L'
      },
      time: {
        default: 'HH:mm',
        localized: 'LT'
      },
      datetime: {
        default: 'YYYY-MM-DD HH:mm',
        localized: 'L LT'
      }
    };

    /**
     * Possible setups for the picker
     */
    var setups = {
      'date-range': {
        mode: 'range',
        label: __('date range'),
        format: formats.date
      },
      'datetime-range': {
        mode: 'range',
        label: __('date time range'),
        enableTime: true,
        format: formats.datetime
      },
      date: {
        mode: 'single',
        format: formats.date
      },
      time: {
        mode: 'single',
        enableTime: true,
        label: __('time'),
        noCalendar: true,
        format: formats.time
      },
      datetime: {
        mode: 'single',
        enableTime: true,
        label: __('date time'),
        format: formats.datetime
      }
    };

    /**
     * List of supported constraints
     */
    var supportedConstraints = ['minDate', 'maxDate', 'enable', 'disable'];

    /**
     * Checks translation existing for given locale
     * @param {String} locale
     * @returns {Boolean}
     */
    const hasTranslationsForLocale = locale => _.isObject(flatpickrLocalization[locale]);

    /**
     * Detects document language
     * @returns {String | undefined}
     */
    const getDefaultLocale = () => {
      const documentLang = window.document.documentElement.getAttribute('lang');
      const documentLocale = documentLang && documentLang.split('-')[0];
      if (documentLocale && hasTranslationsForLocale(documentLocale)) {
        return documentLocale;
      }
    };

    /**
     * The default configuration
     * @see dateTimePickerFactory
     */
    var defaultConfig = {
      setup: 'date',
      controlButtons: false,
      locale: getDefaultLocale(),
      useLocalizedFormat: false,
      constraints: {}
    };

    /**
     * Get the long date/time format from the localized format (LT to 'DD/MM/YYYY HH:mm')
     * @param {String} locale - 2 digits locale code (en, fr, de, etc.)
     * @param {String} localizedFormat - see moment's localized format (L, LT, LLLL, ...)
     * @returns {String|boolean} the long date/time format
     */
    var getLongLocalizedFormat = function getLongLocalizedFormat(locale, localizedFormat) {
      if (/[LT]+/.test(localizedFormat) && locale) {
        return localizedFormat.split(' ').map(function (format) {
          return moment(new Date()).locale(locale).localeData().longDateFormat(format);
        }).join(' ');
      }
      return false;
    };

    /**
     * Does the given date/time format uses the am/pm pattern ?
     * @param {String} format - moment format
     * @returns {Boolean} true if the contains am/pm
     */
    var isFormatAmPm = function isFormatAmPm(format) {
      return format && /a$/i.test(format);
    };

    /**
     * Does the given date/time format contains seconds ?
     * @param {String} format - moment format
     * @returns {Boolean} true if the format contains seconds
     */
    var isFormatInSeconds = function isFormatInSeconds(format) {
      return format && /(:ss)+/i.test(format);
    };

    /**
     * The component factory
     *
     * @param {HTMLElement|jQuery} container - where to append the component
     * @param {Object} [config]
     * @param {String} [config.setup = date] - the picker setup in date-range, datetime-range, date, time and datetime
     * @param {String} [config.locale] - the picker local
     * @param {String} [config.useLocalzedFormat = false] - does the locale is used to define the format
     * @param {String} [config.format] - define your own date/time format for the instance
     * @param {Boolean} [config.controlButtons = false] - does the field have controls to trigger opening and reset
     * @param {Object} [config.constraints] - date time selection constraints
     * @param {Object} [config.constraints] - date time selection constraints
     * @param {Array<String|Date>} [config.constraints.disable] - list of dates to disable
     * @param {Array<String|Date>} [config.constraints.enable] - list of dates to enable (if some are disabled)
     * @param {String|Date} [config.constraints.minDate] - minimum date to start picking from
     * @param {String|Date} [config.constraints.maxDate] - maximum date to start picking from
     *
     * @param {HTMLInputElement} [config.replaceField] - an input field to replace. The field attr are taken instead of config.field
     * @param {Object} [config.field] - the input field configuration
     * @param {String} [config.field.name] - the input field name
     * @param {String} [config.field.id] - the input field id
     * @param {String} [config.field.placeholder] - the input field placeholder
     * @param {String} [config.field.pattern] - the input field pattern mask
     * @param {String} [config.field.value] - the input field value
     * @param {String} [config.field.label] - label the field for a11y
     *
     * @returns {dateTimePickerComponent} the component instance
     */
    function dateTimePickerFactory(container, options) {
      var format = '';
      var value = '';

      /**
       * @typedef {Object} dateTimePicker
       */
      var dateTimePicker = component({
        /**
         * Get the current value
         * @returns {String} the field value, null if none
         */
        getValue: function getValue() {
          if (this.is('rendered')) {
            return this.controls.input.value;
          }
          return null;
        },
        /**
         * Set the current value
         * @param {String|Date} newValue - the new value matching the format
         */
        setValue: function setValue(newValue) {
          if (this.is('ready')) {
            value = newValue;
            if (_.isString(value)) {
              this.controls.input.value = value;
            }
            this.picker.setDate(value, true);
          }
          return null;
        },
        /**
         * Get the dates currently selected.
         * Usefull for the range mode to get the date list
         * @returns {Array<[Date|String]>|boolean} the selected dates
         */
        getSelectedDates: function getSelectedDates() {
          var selection;
          if (this.is('ready') && this.picker) {
            selection = this.picker.selectedDates;
            if (selection && !_.isArray(selection)) {
              return [selection];
            }
            return selection;
          }
          return false;
        },
        /**
         * Get the date/time format description, ie. 'YYYY-MM-DD'
         * @returns {String} the format
         */
        getFormat: function getFormat() {
          return format;
        },
        /**
         * Open the picker
         * @returns {dateTimePicker} chains
         * @fires dateTimePicker#open
         */
        open: function open() {
          if (this.is('ready')) {
            this.picker.open();
          }
          return this;
        },
        /**
         * Close the picker
         * @returns {dateTimePicker} chains
         * @fires dateTimePicker#close
         */
        close: function close() {
          if (this.is('ready')) {
            this.picker.close();
          }
          return this;
        },
        /**
         * Clear the field content and close the picker
         * @returns {dateTimePicker} chains
         * @fires dateTimePicker#close
         * @fires dateTimePicker#clear
         */
        clear: function clear() {
          if (this.is('ready')) {
            value = '';
            this.picker.close();
            this.picker.clear();

            /**
             * The picker get cleared
             * @event dateTimePicker#clear
             */
            this.trigger('clear');
          }
          return this;
        },
        /**
         * Clear the field content and close the picker
         * @returns {dateTimePicker} chains
         * @fires dateTimePicker#open
         * @fires dateTimePicker#close
         */
        toggle: function toogle() {
          if (this.is('ready')) {
            this.picker.toggle();
          }
          return this;
        },
        /**
         * Update constraints on a running instance
         * @param {String} constraint - the constraint name in minDate, maxDate, enable, disable
         * @param {*} constraintValue - the constraint value to update
         * @returns {dateTimePicker} chains
         * @fires dateTimePicker#open
         * @fires dateTimePicker#close
         */
        updateConstraints: function updateConstraints(constraint, constraintValue) {
          if (this.is('ready')) {
            if (_.includes(supportedConstraints, constraint)) {
              this.picker.set(constraint, constraintValue);
            }
          }
          return this;
        }
      }, defaultConfig);
      dateTimePicker.setTemplate(dateTimePickerTpl).on('init', function () {
        var self = this;
        var locale;
        var setup = setups[this.config.setup] || setups.datetime;

        //map the locale from the options to the picker locale
        if (this.config.locale && hasTranslationsForLocale(this.config.locale)) {
          locale = this.config.locale;
        }

        //date/time format
        if (locale && this.config.useLocalizedFormat) {
          //get the format from the locale
          format = getLongLocalizedFormat(locale, setup.format.localized);
        } else {
          //get the format from the config
          format = this.config.format || setup.format.default;
        }

        //input field configuration
        this.config.field = this.config.field || {};

        // replace a field by the date picker input field
        //TODO consider replacing data-attr and classes
        if (this.config.replaceField && this.config.replaceField instanceof HTMLInputElement) {
          this.config.field.id = this.config.replaceField.id;
          this.config.field.name = this.config.replaceField.name;
          this.config.field.placeholder = this.config.replaceField.placeholder;
          this.config.field.value = this.config.replaceField.value;
          this.config.field.pattern = this.config.replaceField.pattern;
          this.config.replaceField.parentNode.removeChild(this.config.replaceField);
        }
        if (!this.config.field.placeholder && format && setup.mode === 'single') {
          this.config.field.placeholder = format.toLowerCase();
        }
        if (!this.config.field.label) {
          this.config.field.label = setup.label;
        }

        /**
         * Build the configuration of the picker
         * @see https://flatpickr.js.org/options/
         */
        this.pickerConfig = {
          mode: setup.mode,
          enableTime: !!setup.enableTime,
          noCalendar: !!setup.noCalendar,
          time_24hr: !isFormatAmPm(format),
          enableSeconds: setup.enableTime && isFormatInSeconds(format),
          allowInput: true,
          clickOpens: !this.config.controlButtons,
          disableMobile: true,
          /**
           * How flatpickr will format the given date
           * @param {Date} date
           * @returns {String} the formatted date
           */
          formatDate: function formatDate(date) {
            return moment(date).format(format);
          },
          /**
           * How flatpickr parse the given input
           * @param {String} dateString
           * @returns {Date}
           */
          parseDate: function parseDate(dateString) {
            return moment(dateString, format).toDate();
          },
          /**
           * When the picker is opened
           * @fires dateTimePicker#open
           */
          onOpen: function onOpen() {
            /**
             * The picker get opened
             * @event dateTimePicker#open
             */
            self.trigger('open');
          },
          /**
           * When the picker is opened
           * @fires dateTimePicker#close
           */
          onClose: function onClose() {
            /**
             * The picker get closed
             * @event dateTimePicker#close
             */
            self.trigger('close');
          },
          /**
           * Hook flatpickr error handler
           * @param {Error} err - the thrown error
           */
          errorHandler: function errorHandler(err) {
            if (err instanceof Error) {
              //if an invalid date is provided
              //add a visual feedback indicating why the field get emptied
              if (/^Invalid date/.test(err.message)) {
                self.controls.input.classList.add('error');
                _.delay(function () {
                  self.controls.input.classList.remove('error');
                }, 1000);
              } else {
                /**
                 * Unexpected error
                 * @event dateTimePicker#error
                 * @param {Error} err
                 */
                self.trigger('error', err);
              }
            }
          }
        };
        //locale should be defined only if set...
        if (locale) {
          this.pickerConfig.locale = locale;
        }
        _.forEach(this.config.constraints, function (constraintValue, constraint) {
          if (_.includes(supportedConstraints, constraint) && constraintValue) {
            self.pickerConfig[constraint] = constraintValue;
          }
        });

        //render into the container
        if (container) {
          setTimeout(function () {
            self.render(container);
          }, 0);
        }
      }).on('render', function () {
        var self = this;
        var element = this.getElement()[0];
        this.controls = {
          input: element.querySelector('input')
        };

        //always scope the picker to the component container
        //in order to scope and style each instance
        this.pickerConfig.appendTo = element;

        //behavior of the right buttons if configured
        if (this.config.controlButtons) {
          this.controls.toggleButton = element.querySelector('.picker-toggle');
          this.controls.clearButton = element.querySelector('.picker-clear');
          this.controls.toggleButton.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            self.toggle();
          });
          this.controls.clearButton.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            self.clear();
          });
        }
        this.controls.input.addEventListener('change', function () {
          var newValue = self.controls.input.value;
          if (value && _.isString(newValue) && _.isEmpty(newValue)) {
            //if someone remove the value from the field
            //it's considered a property clean (resets everything)
            self.clear();
          } else if (value !== newValue) {
            value = newValue;

            /**
             * A value get changed
             * @event dateTimePicker#change
             * @param {String} value - the date/time value
             */
            self.trigger('change', value);
          }
        });
        value = this.controls.input.value;

        //instantiate the picker
        _.defer(function () {
          self.picker = flatpickr(self.controls.input, self.pickerConfig);
          self.enable().setState('ready', true).trigger('ready');
        });
      }).on('enable', function () {
        if (this.controls) {
          this.controls.input.disabled = false;
          if (this.config.controlButtons) {
            this.controls.toggleButton.disabled = false;
            this.controls.clearButton.disabled = false;
          }
        }
      }).on('disable', function () {
        if (this.controls) {
          this.controls.input.disabled = true;
          if (this.config.controlButtons) {
            this.controls.toggleButton.disabled = true;
            this.controls.clearButton.disabled = true;
          }
        }
      });

      //defered init to catch the event
      setTimeout(function () {
        dateTimePicker.init(options);
      }, 0);
      return dateTimePicker;
    }

    return dateTimePickerFactory;

});
