/* global $, document */
const moment = require('moment-timezone');
const _ = require('underscore');
const gutil = require('app/common/gutil');
const commands = require('../components/commands');
const dispose = require('../lib/dispose');
const dom = require('../lib/dom');
const kd = require('../lib/koDom');
const TextEditor = require('./TextEditor');
const { parseDate, TWO_DIGIT_YEAR_THRESHOLD } = require('app/common/parseDate');

// DatePicker unfortunately requires an <input> (not <textarea>). But textarea is better for us,
// because sometimes it's taller than a line, and an <input> looks worse. The following
// unconsionable hack tricks Datepicker into thinking anything it's attached to is an input.
// It's more reasonable to just modify boostrap-datepicker, but that has its own downside (with
// upgrading and minification). This hack, however, is simpler than other workarounds.
var Datepicker = $.fn.datepicker.Constructor;
// datepicker.isInput can now be set to anything, but when read, always returns true. Tricksy.
Object.defineProperty(Datepicker.prototype, 'isInput', {
  get: function() { return true; },
  set: function(v) {},
});

/**
 * DateEditor - Editor for Date type. Includes a dropdown datepicker.
 *  See reference: http://bootstrap-datepicker.readthedocs.org/en/latest/index.html
 *
 * @param {String} options.timezone: Optional timezone to use instead of UTC.
 */
function DateEditor(options) {
  // A string that is always `UTC` in the DateEditor, eases DateTimeEditor inheritance.
  this.timezone = options.timezone || 'UTC';

  this.dateFormat = options.field.widgetOptionsJson.peek().dateFormat;
  this.locale = options.field.documentSettings.peek().locale;

  // Update moment format string to represent a date unambiguously.
  this.safeFormat = makeFullMomentFormat(this.dateFormat);

  // Use the default local timezone to format the placeholder date.
  const defaultTimezone = moment.tz.guess();
  let placeholder = moment.tz(defaultTimezone).format(this.safeFormat);
  if (options.readonly) {
    // clear placeholder for readonly mode
    placeholder = null;
  }
  TextEditor.call(this, _.defaults(options, { placeholder: placeholder }));

  const cellValue = this.formatValue(options.cellValue, this.safeFormat, true);

  // Set the edited value, if not explicitly given, to the formatted version of cellValue.
  this.textInput.value = gutil.undef(options.state, options.editValue, cellValue);

  if (!options.readonly) {
    // Indicates whether keyboard navigation is active for the datepicker.
    this._keyboardNav = false;

    // Attach the datepicker.
    this._datePickerWidget = $(this.textInput).datepicker({
      keyboardNavigation: false,
      forceParse: false,
      todayHighlight: true,
      todayBtn: 'linked',
      assumeNearbyYear: TWO_DIGIT_YEAR_THRESHOLD,
      // Datepicker supports most of the languages. They just need to be included in the bundle
      // or by script tag, i.e.
      // <script src="bootstrap-datepicker/dist/locales/bootstrap-datepicker.pl.min.js"></script>
      language : this.getLanguage(),
      // Use the stripped format converted to one suitable for the datepicker.
      format: {
        toDisplay: (date, format, language) => moment.utc(date).format(this.safeFormat),
        toValue: (date, format, language) => {
          const timestampSec = parseDate(date, {
            dateFormat: this.safeFormat,
            // datepicker reads date in utc (ie: using date.getUTCDate()).
            timezone: 'UTC',
          });
          return (timestampSec === null) ? null : new Date(timestampSec * 1000);
        },
      },
    });
    this.autoDisposeCallback(() => this._datePickerWidget.datepicker('destroy'));

    // NOTE: Datepicker interferes with normal enter and escape functionality. Add an event handler
    // to the DatePicker to prevent interference with normal behavior.
    this._datePickerWidget.on('keydown', e => {
      // If enter or escape is pressed, destroy the datepicker and re-dispatch the event.
      if (e.keyCode === 13 || e.keyCode === 27) {
        this._datePickerWidget.datepicker('destroy');
        // The current target of the event will be the textarea.
        setTimeout(() => e.currentTarget.dispatchEvent(e.originalEvent), 0);
      }
    });

    // When the up/down arrow is pressed, modify the datepicker options to take control of
    // the arrow keys for date selection.
    let datepickerCommands = Object.assign({}, options.commands, {
      datepickerFocus: () => { this._allowKeyboardNav(true); }
    });
    this._datepickerCommands = this.autoDispose(commands.createGroup(datepickerCommands, this, true));

    this._datePickerWidget.on('show', () => {
      // A workaround to allow clicking in the datepicker without losing focus.
      dom(document.querySelector('.datepicker'),
        kd.attr('tabIndex', 0),                   // allows datepicker to gain focus
        kd.toggleClass('clipboard_focus', true)   // tells clipboard to not steal focus from us
      );
      // Attach command group to the input to allow switching keyboard focus to the datepicker.
      dom(this.textInput,
        // If the user inputs text into the textbox, take keyboard focus from the datepicker.
        dom.on('input', () => { this._allowKeyboardNav(false); }),
        this._datepickerCommands.attach()
      );
    });
  }
}

dispose.makeDisposable(DateEditor);
_.extend(DateEditor.prototype, TextEditor.prototype);

/** @inheritdoc */
DateEditor.prototype.getCellValue = function() {
  let timestamp = parseDate(this.textInput.value, {
    dateFormat: this.safeFormat,
    timezone: this.timezone
  });
  return timestamp !== null ? timestamp : this.textInput.value;
};

// Helper to allow/disallow keyboard navigation within the datepicker.
DateEditor.prototype._allowKeyboardNav = function(bool) {
  if (this._keyboardNav !== bool) {
    this._keyboardNav = bool;
    $(this.textInput).data().datepicker.o.keyboardNavigation = bool;
    // Force parse must be turned on with keyboard navigation, since it forces the highlighted date
    // to be used when enter is pressed. Otherwise, keyboard date selection will have no effect.
    $(this.textInput).data().datepicker.o.forceParse = bool;
  }
};

// Moment value formatting helper.
DateEditor.prototype.formatValue = function(value, formatString, shouldFallBackToValue) {
  if (_.isNumber(value) && formatString) {
    return moment.tz(value*1000, this.timezone).format(formatString);
  } else {
    // If value is AltText, return it unchanged. This way we can see it and edit in the editor.
    return (shouldFallBackToValue && typeof value === 'string') ? value : "";
  }
};

// Gets the language based on the current locale.
DateEditor.prototype.getLanguage = function() {
  // this requires a polyfill, i.e. https://www.npmjs.com/package/@formatjs/intl-locale
  // more info about ts: https://github.com/microsoft/TypeScript/issues/37326
  // return new Intl.Locale(locale).language;
  return this.locale.substr(0, this.locale.indexOf("-"));
}

// Updates the given Moment format to specify a complete date, so that the datepicker sees an
// unambiguous date in the textbox input. If the format is incomplete, fall back to YYYY-MM-DD.
function makeFullMomentFormat(mFormat) {
  let safeFormat = mFormat;
  if (!safeFormat.includes('Y')) {
    safeFormat += " YYYY";
  }
  if (!safeFormat.includes('D') || !safeFormat.includes('M')) {
    safeFormat = 'YYYY-MM-DD';
  }
  return safeFormat;
}

module.exports = DateEditor;