<template>
  <div
    :class="_class"
    :style="_style"
    v-clickoutside="closePopup"
  >
    <div
      class="hive-datepicker-input-wrapper"
      @click="showPopup"
    >
      <input
        :class="inputClass"
        :name="inputName"
        v-bind="inputAttr"
        ref="input"
        type="text"
        autocomplete="off"
        :disabled="disabled"
        :readonly="readonly"
        :value="text"
        :placeholder="innerPlaceholder"
        @keydown="handleKeydown"
        @focus="handleFocus"
        @blur="handleBlur"
        @input="handleInput"
        @change="handleChange"
      >
      <span
        v-if="showClearIcon"
        class="hive-datepicker-input-append hive-datepicker-clear-wrapper"
        @click.stop="clearDate"
      >
        <slot name="hive-datepicker-clear-icon">
          <i class="hive-datepicker-input-icon hive-datepicker-clear-icon" />
        </slot>
      </span>
      <span class="hive-datepicker-input-append">
        <slot name="calendar-icon">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            version="1.1"
            viewBox="0 0 200 200"
            class="hive-datepicker-calendar-icon"
          >
            <rect
              x="13"
              y="29"
              rx="14"
              ry="14"
              width="174"
              height="158"
              fill="transparent"
            />
            <line
              x1="46"
              x2="46"
              y1="8"
              y2="50"
            />
            <line
              x1="154"
              x2="154"
              y1="8"
              y2="50"
            />
            <line
              x1="13"
              x2="187"
              y1="70"
              y2="70"
            />
            <text
              x="50%"
              y="135"
              font-size="90"
              stroke-width="1"
              text-anchor="middle"
              dominant-baseline="middle"
            >{{ new Date().getDate() }}</text>
          </svg>
        </slot>
      </span>
    </div>
    <div
      class="hive-datepicker-popup"
      :style="innerPopupStyle"
      v-show="popupVisible"
      @click.stop.prevent
      ref="calendar"
    >
      <slot name="header">
        <div
          class="hive-datepicker-shortcuts-wrapper"
          v-if="range && innerShortcuts.length"
        >
          <button
            type="button"
            class="hive-datepicker-shortcuts"
            v-for="(r, index) in innerShortcuts"
            :key="index"
            @click="selectRange(r)"
          >
            {{ r.text }}
          </button>
        </div>
      </slot>
      <CalendarPanel
        v-if="!range"
        v-bind="$attrs"
        ref="calendarPanel"
        :index="-1"
        :type="innerType"
        :date-format="innerDateFormat"
        :value="currentValue"
        :visible="popupVisible"
        @select-date="selectDate"
        @select-time="selectTime"
      />
      <div
        class="hive-datepicker-range-wrapper"
        v-else
      >
        <CalendarPanel
          style="box-shadow:1px 0 rgba(0, 0, 0, .1)"
          v-bind="$attrs"
          ref="calendarPanel"
          :index="0"
          :type="innerType"
          :date-format="innerDateFormat"
          :value="currentValue[0]"
          :end-at="currentValue[1]"
          :start-at="null"
          :visible="popupVisible"
          @select-date="selectStartDate"
          @select-time="selectStartTime"
        />
        <CalendarPanel
          v-bind="$attrs"
          :index="1"
          :type="innerType"
          :date-format="innerDateFormat"
          :value="currentValue[1]"
          :start-at="currentValue[0]"
          :end-at="null"
          :visible="popupVisible"
          @select-date="selectEndDate"
          @select-time="selectEndTime"
        />
      </div>
      <slot
        name="footer"
        :confirm="confirmDate"
      >
        <div
          class="hive-datepicker-footer"
          v-if="confirm"
        >
          <button
            type="button"
            class="hive-datepicker-btn hive-datepicker-btn-confirm"
            @click="confirmDate"
          >
            {{ confirmText }}
          </button>
        </div>
      </slot>
    </div>
  </div>
</template>

<script>
import clickoutside from 'utils-lib/dist/clickoutside';
import { isValidDate, isValidRangeDate, isDateObejct, isPlainObject, formatDate, parseDate, throttle } from './utils/index';
import { transformDate } from './utils/transform';
import CalendarPanel from './calendar.vue';
import locale from './mixins/locale';
import Languages from './locale/languages';
import FormSize from '../scripts/mixins/form-size';

const name = 'hive-datepicker';

export default {
  name,
  components: { CalendarPanel },
  directives: {
    clickoutside
  },
  mixins: [
    locale,
    FormSize
  ],
  props: {
    value: null,
    valueType: {
      default: 'date',
      validator: function(value) {
        return ['timestamp', 'format', 'date'].indexOf(value) !== -1 || isPlainObject(value);
      }
    },
    placeholder: {
      type: String,
      default: null
    },
    lang: {
      type: [String, Object],
      default: 'zh'
    },
    format: {
      type: [String, Object],
      default: 'YYYY-MM-DD'
    },
    dateFormat: {
      type: String // format the time header and date tooltip
    },
    type: {
      type: String,
      default: 'date' // ['date', 'datetime'] zendy added 'month', 'year', mxie added "time"
    },
    range: {
      type: Boolean,
      default: false
    },
    rangeSeparator: {
      type: String,
      default: '~'
    },
    width: {
      type: [String, Number],
      default: null
    },
    confirmText: {
      type: String,
      default: 'OK'
    },
    confirm: {
      type: Boolean,
      default: false
    },
    readonly: Boolean,
    disabled: Boolean,
    clearable: {
      type: Boolean,
      default: true
    },
    shortcuts: {
      type: [Boolean, Array],
      default: true
    },
    inputName: {
      type: String,
      default: 'date'
    },
    inputClass: {
      type: [String, Array],
      default: 'hive-datepicker-input'
    },
    inputAttr: Object,
    appendToBody: {
      type: Boolean,
      default: false
    },
    popupStyle: {
      type: Object
    }
  },
  data() {
    return {
      currentValue: this.range ? [null, null] : null,
      userInput: null,
      popupVisible: false,
      position: {}
    };
  },
  computed: {
    _class() {
      return [
        name,
        {
          [`${name}-range`]: this.range,
          [`${name}-${this.curSize}`]: this.curSize,
          disabled: this.disabled
        }
      ];
    },
    _style() {
      return {
        width: this.width
      };
    },

    transform() {
      const type = this.valueType;
      if (isPlainObject(type)) {
        return { ...transformDate.date, ...type };
      }
      if (type === 'format') {
        return {
          value2date: this.parse.bind(this),
          date2value: this.stringify.bind(this)
        };
      }
      return transformDate[type] || transformDate.date;
    },
    language() {
      if (isPlainObject(this.lang)) {
        return { ...Languages.en, ...this.lang };
      }
      return Languages[this.lang] || Languages.en;
    },
    innerPlaceholder() {
      if (typeof this.placeholder === 'string') {
        return this.placeholder;
      }
      return this.range ? this.t('placeholder.dateRange') : this.t('placeholder.date');
    },
    text() {
      if (this.userInput !== null) {
        return this.userInput;
      }
      const { value2date } = this.transform;
      if (!this.range) {
        return this.isValidValue(this.value)
          ? this.stringify(value2date(this.value))
          : '';
      }
      return this.isValidRangeValue(this.value)
        ? `${this.stringify(value2date(this.value[0]))} ${this.rangeSeparator} ${this.stringify(value2date(this.value[1]))}`
        : '';
    },
    showClearIcon() {
      return !this.disabled && this.clearable && (this.range ? this.isValidRangeValue(this.value) : this.isValidValue(this.value));
    },
    innerType() {
      return String(this.type).toLowerCase();
    },
    innerShortcuts() {
      if (Array.isArray(this.shortcuts)) {
        return this.shortcuts;
      }
      if (this.shortcuts === false) {
        return [];
      }
      const pickers = this.t('pickers');
      const arr = [
        {
          text: pickers[0],
          onClick(self) {
            self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 7)];
            self.updateDate(true);
          }
        },
        {
          text: pickers[1],
          onClick(self) {
            self.currentValue = [new Date(), new Date(Date.now() + 3600 * 1000 * 24 * 30)];
            self.updateDate(true);
          }
        },
        {
          text: pickers[2],
          onClick(self) {
            self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 7), new Date()];
            self.updateDate(true);
          }
        },
        {
          text: pickers[3],
          onClick(self) {
            self.currentValue = [new Date(Date.now() - 3600 * 1000 * 24 * 30), new Date()];
            self.updateDate(true);
          }
        }
      ];
      return arr;
    },
    innerDateFormat() {
      if (this.dateFormat) {
        return this.dateFormat;
      }
      if (typeof this.format !== 'string') {
        return 'YYYY-MM-DD';
      }
      if (this.innerType === 'date') {
        return this.format;
      }
      return this.format.replace(/[Hh]+.*[msSaAZ]|\[.*?\]/g, '').trim() || 'YYYY-MM-DD';
    },
    innerPopupStyle() {
      return { ...this.position, ...this.popupStyle };
    }
  },
  watch: {
    value: {
      immediate: true,
      handler: 'handleValueChange'
    },
    popupVisible(val) {
      if (val) {
        this.initCalendar();
      } else {
        this.userInput = null;
        this.blur();
      }
    }
  },

  mounted() {
    if (this.appendToBody) {
      this.popupElm = this.$refs.calendar;
      document.body.appendChild(this.popupElm);
    }
    this._displayPopup = throttle(() => {
      if (this.popupVisible) {
        this.displayPopup();
      }
    }, 200);
    window.addEventListener('resize', this._displayPopup);
    window.addEventListener('scroll', this._displayPopup);
  },
  beforeDestroy() {
    if (this.popupElm && this.popupElm.parentNode === document.body) {
      document.body.removeChild(this.popupElm);
    }
    window.removeEventListener('resize', this._displayPopup);
    window.removeEventListener('scroll', this._displayPopup);
  },
  methods: {
    initCalendar() {
      this.handleValueChange(this.value);
      this.displayPopup();
    },
    stringify(date) {
      return (isPlainObject(this.format) && typeof this.format.stringify === 'function')
        ? this.format.stringify(date)
        : formatDate(date, this.format);
    },
    parse(value) {
      return (isPlainObject(this.format) && typeof this.format.parse === 'function')
        ? this.format.parse(value)
        : parseDate(value, this.format);
    },
    isValidValue(value) {
      const { value2date } = this.transform;
      return isValidDate(value2date(value));
    },
    isValidRangeValue(value) {
      const { value2date } = this.transform;
      return Array.isArray(value) && value.length === 2 && this.isValidValue(value[0]) &&
          this.isValidValue(value[1]) && (value2date(value[1]).getTime() >= value2date(value[0]).getTime());
    },
    dateEqual(a, b) {
      return isDateObejct(a) && isDateObejct(b) && a.getTime() === b.getTime();
    },
    rangeEqual(a, b) {
      return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => this.dateEqual(item, b[index]));
    },
    selectRange(range) {
      if (typeof range.onClick === 'function') {
        return range.onClick(this);
      }
      this.currentValue = [new Date(range.start), new Date(range.end)];
      this.updateDate(true);
    },
    clearDate() {
      const date = this.range ? [null, null] : null;
      this.currentValue = date;
      this.updateDate(true);
      this.$emit('clear');
    },
    confirmDate() {
      const valid = this.range ? isValidRangeDate(this.currentValue) : isValidDate(this.currentValue);
      if (valid) {
        this.updateDate(true);
      }
      this.emitDate('confirm');
      this.closePopup();
    },
    updateDate(confirm = false) {
      if ((this.confirm && !confirm) || this.disabled) {
        return false;
      }
      const equal = this.range ? this.rangeEqual(this.value, this.currentValue) : this.dateEqual(this.value, this.currentValue);
      if (equal) {
        return false;
      }
      this.emitDate('input');
      this.emitDate('change');
      return true;
    },
    emitDate(eventName) {
      const { date2value } = this.transform;
      const value = this.range
        ? this.currentValue.map(date2value)
        : date2value(this.currentValue);
      this.$emit(eventName, value);
    },
    handleValueChange(value) {
      const { value2date } = this.transform;
      if (this.range) {
        this.currentValue = this.isValidRangeValue(value) ? value.map(value2date) : [null, null];
      } else {
        this.currentValue = this.isValidValue(value) ? value2date(value) : null;
      }
    },
    selectDate(date) {
      this.currentValue = date;
      this.updateDate() && this.closePopup();
    },
    selectStartDate(date) {
      this.$set(this.currentValue, 0, date);
      if (this.currentValue[1]) {
        this.updateDate();
      }
    },
    selectEndDate(date) {
      this.$set(this.currentValue, 1, date);
      if (this.currentValue[0]) {
        this.updateDate();
      }
    },
    selectTime(time, close) {
      this.currentValue = time;
      this.updateDate() && close && this.closePopup();
    },
    selectStartTime(time) {
      this.selectStartDate(time);
    },
    selectEndTime(time) {
      this.selectEndDate(time);
    },
    showPopup() {
      if (this.disabled) {
        return;
      }
      this.popupVisible = true;
    },
    closePopup() {
      this.popupVisible = false;
    },
    getPopupSize(element) {
      const originalDisplay = element.style.display;
      const originalVisibility = element.style.visibility;
      element.style.display = 'block';
      element.style.visibility = 'hidden';
      const styles = window.getComputedStyle(element);
      const width = element.offsetWidth + parseInt(styles.marginLeft) + parseInt(styles.marginRight);
      const height = element.offsetHeight + parseInt(styles.marginTop) + parseInt(styles.marginBottom);
      const result = { width, height };
      element.style.display = originalDisplay;
      element.style.visibility = originalVisibility;
      return result;
    },
    displayPopup() {
      const dw = document.documentElement.clientWidth;
      const dh = document.documentElement.clientHeight;
      const InputRect = this.$el.getBoundingClientRect();
      const PopupRect = this._popupRect || (this._popupRect = this.getPopupSize(this.$refs.calendar));
      const position = {};
      let offsetRelativeToInputX = 0;
      let offsetRelativeToInputY = 0;
      if (this.appendToBody) {
        offsetRelativeToInputX = window.pageXOffset + InputRect.left;
        offsetRelativeToInputY = window.pageYOffset + InputRect.top;
      }
      if (
        dw - InputRect.left < PopupRect.width &&
          InputRect.right < PopupRect.width
      ) {
        position.left = offsetRelativeToInputX - InputRect.left + 1 + 'px';
      } else if (InputRect.left + InputRect.width / 2 <= dw / 2) {
        position.left = offsetRelativeToInputX + 'px';
      } else {
        position.left = offsetRelativeToInputX + InputRect.width - PopupRect.width + 'px';
      }
      if (
        InputRect.top <= PopupRect.height &&
          dh - InputRect.bottom <= PopupRect.height
      ) {
        position.top = offsetRelativeToInputY + dh - InputRect.top - PopupRect.height + 'px';
      } else if (InputRect.top + InputRect.height / 2 <= dh / 2) {
        position.top = offsetRelativeToInputY + InputRect.height + 'px';
      } else {
        position.top = offsetRelativeToInputY - PopupRect.height + 'px';
      }
      if (position.top !== this.position.top || position.left !== this.position.left) {
        this.position = position;
      }
    },
    blur() {
      this.$refs.input.blur();
    },
    handleBlur(event) {
      this.$emit('blur', event);
    },
    handleFocus(event) {
      if (!this.popupVisible) {
        this.showPopup();
      }
      this.$emit('focus', event);
    },
    handleKeydown(event) {
      const keyCode = event.keyCode;
      // Tab 9 or Enter 13
      if (keyCode === 9 || keyCode === 13) {
        // ie emit the watch before the change event
        event.stopPropagation();
        this.handleChange();
        this.userInput = null;
        this.closePopup();
      }
    },
    handleInput(event) {
      this.userInput = event.target.value;
    },
    handleChange() {
      if (!this.readonly && this.userInput !== null) {
        const value = this.text;
        const checkDate = this.$refs.calendarPanel.isDisabledTime;
        if (!value) {
          this.clearDate();
          return;
        }
        if (this.range) {
          const range = value.split(` ${this.rangeSeparator} `);
          if (range.length === 2) {
            const start = this.parse(range[0]);
            const end = this.parse(range[1]);
            if (start && end && !checkDate(start, null, end) && !checkDate(end, start, null)) {
              this.currentValue = [start, end];
              this.updateDate(true);
              this.closePopup();
              return;
            }
          }
        } else {
          const date = this.parse(value);
          if (date && !checkDate(date, null, null)) {
            this.currentValue = date;
            this.updateDate(true);
            this.closePopup();
            return;
          }
        }
        this.$emit('input-error', value);
      }
    }
  }
};
</script>
