<template>
  <transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
    <div
      v-show="visible"
      class="el-time-range-picker el-picker-panel el-popper"
      :class="popperClass"
    >
      <div class="el-time-range-picker__content">
        <div class="el-time-range-picker__cell">
          <div class="el-time-range-picker__header">
            {{ t("el.datepicker.startTime") }}
          </div>
          <div
            :class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
            class="el-time-range-picker__body el-time-panel__content"
          >
            <time-spinner
              ref="minSpinner"
              :show-seconds="showSeconds"
              :am-pm-mode="amPmMode"
              @change="handleMinChange"
              :arrow-control="arrowControl"
              @select-range="setMinSelectionRange"
              :date="minDate"
            >
            </time-spinner>
          </div>
        </div>
        <div class="el-time-range-picker__cell">
          <div class="el-time-range-picker__header">
            {{ t("el.datepicker.endTime") }}
          </div>
          <div
            :class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
            class="el-time-range-picker__body el-time-panel__content"
          >
            <time-spinner
              ref="maxSpinner"
              :show-seconds="showSeconds"
              :am-pm-mode="amPmMode"
              @change="handleMaxChange"
              :arrow-control="arrowControl"
              @select-range="setMaxSelectionRange"
              :date="maxDate"
            >
            </time-spinner>
          </div>
        </div>
      </div>
      <div class="el-time-panel__footer">
        <button
          type="button"
          class="el-time-panel__btn cancel"
          @click="handleCancel()"
        >
          {{ t("el.datepicker.cancel") }}
        </button>
        <button
          type="button"
          class="el-time-panel__btn confirm"
          @click="handleConfirm()"
          :disabled="btnDisabled"
        >
          {{ t("el.datepicker.confirm") }}
        </button>
      </div>
    </div>
  </transition>
</template>

<script type="text/babel">
/* eslint-disable */
import {
  parseDate,
  limitTimeRange,
  modifyDate,
  clearMilliseconds,
  timeWithinRange,
} from "element-ui/src/utils/date-util";
import Locale from "element-ui/src/mixins/locale";
import TimeSpinner from "../basic/time-spinner";

const MIN_TIME = parseDate("00:00:00", "HH:mm:ss");
const MAX_TIME = parseDate("23:59:59", "HH:mm:ss");

const minTimeOfDay = function (date) {
  return modifyDate(
    MIN_TIME,
    date.getFullYear(),
    date.getMonth(),
    date.getDate()
  );
};

const maxTimeOfDay = function (date) {
  return modifyDate(
    MAX_TIME,
    date.getFullYear(),
    date.getMonth(),
    date.getDate()
  );
};

// increase time by amount of milliseconds, but within the range of day
const advanceTime = function (date, amount) {
  return new Date(
    Math.min(date.getTime() + amount, maxTimeOfDay(date).getTime())
  );
};

export default {
  mixins: [Locale],

  components: { TimeSpinner },

  computed: {
    showSeconds() {
      return (this.format || "").indexOf("ss") !== -1;
    },

    offset() {
      return this.showSeconds ? 11 : 8;
    },

    spinner() {
      return this.selectionRange[0] < this.offset
        ? this.$refs.minSpinner
        : this.$refs.maxSpinner;
    },

    btnDisabled() {
      return this.minDate.getTime() > this.maxDate.getTime();
    },
    amPmMode() {
      if ((this.format || "").indexOf("A") !== -1) return "A";
      if ((this.format || "").indexOf("a") !== -1) return "a";
      return "";
    },
  },

  data() {
    return {
      popperClass: "",
      minDate: new Date(),
      maxDate: new Date(),
      value: [],
      oldValue: [new Date(), new Date()],
      defaultValue: null,
      format: "HH:mm:ss",
      visible: false,
      selectionRange: [0, 2],
      arrowControl: false,
    };
  },

  watch: {
    value(value) {
      if (Array.isArray(value)) {
        this.minDate = new Date(value[0]);
        this.maxDate = new Date(value[1]);
      } else {
        if (Array.isArray(this.defaultValue)) {
          this.minDate = new Date(this.defaultValue[0]);
          this.maxDate = new Date(this.defaultValue[1]);
        } else if (this.defaultValue) {
          this.minDate = new Date(this.defaultValue);
          this.maxDate = advanceTime(
            new Date(this.defaultValue),
            60 * 60 * 1000
          );
        } else {
          this.minDate = new Date();
          this.maxDate = advanceTime(new Date(), 60 * 60 * 1000);
        }
      }
    },

    visible(val) {
      if (val) {
        this.oldValue = this.value;
        this.$nextTick(() => this.$refs.minSpinner.emitSelectRange("hours"));
      }
    },
  },

  methods: {
    handleClear() {
      this.$emit("pick", null);
    },

    handleCancel() {
      this.$emit("pick", this.oldValue);
    },

    handleMinChange(date) {
      this.minDate = clearMilliseconds(date);
      this.handleChange();
    },

    handleMaxChange(date) {
      this.maxDate = clearMilliseconds(date);
      this.handleChange();
    },

    handleChange() {
      if (this.isValidValue([this.minDate, this.maxDate])) {
        this.$refs.minSpinner.selectableRange = [
          [minTimeOfDay(this.minDate), this.maxDate],
        ];
        this.$refs.maxSpinner.selectableRange = [
          [this.minDate, maxTimeOfDay(this.maxDate)],
        ];
        this.$emit("pick", [this.minDate, this.maxDate], true);
      }
    },

    setMinSelectionRange(start, end) {
      this.$emit("select-range", start, end, "min");
      this.selectionRange = [start, end];
    },

    setMaxSelectionRange(start, end) {
      this.$emit("select-range", start, end, "max");
      this.selectionRange = [start + this.offset, end + this.offset];
    },

    handleConfirm(visible = false) {
      const minSelectableRange = this.$refs.minSpinner.selectableRange;
      const maxSelectableRange = this.$refs.maxSpinner.selectableRange;

      this.minDate = limitTimeRange(
        this.minDate,
        minSelectableRange,
        this.format
      );
      this.maxDate = limitTimeRange(
        this.maxDate,
        maxSelectableRange,
        this.format
      );

      this.$emit("pick", [this.minDate, this.maxDate], visible);
    },

    adjustSpinners() {
      this.$refs.minSpinner.adjustSpinners();
      this.$refs.maxSpinner.adjustSpinners();
    },

    changeSelectionRange(step) {
      const list = this.showSeconds ? [0, 3, 6, 11, 14, 17] : [0, 3, 8, 11];
      const mapping = ["hours", "minutes"].concat(
        this.showSeconds ? ["seconds"] : []
      );
      const index = list.indexOf(this.selectionRange[0]);
      const next = (index + step + list.length) % list.length;
      const half = list.length / 2;
      if (next < half) {
        this.$refs.minSpinner.emitSelectRange(mapping[next]);
      } else {
        this.$refs.maxSpinner.emitSelectRange(mapping[next - half]);
      }
    },

    isValidValue(date) {
      return (
        Array.isArray(date) &&
        timeWithinRange(this.minDate, this.$refs.minSpinner.selectableRange) &&
        timeWithinRange(this.maxDate, this.$refs.maxSpinner.selectableRange)
      );
    },

    handleKeydown(event) {
      const keyCode = event.keyCode;
      const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };

      // Left or Right
      if (keyCode === 37 || keyCode === 39) {
        const step = mapping[keyCode];
        this.changeSelectionRange(step);
        event.preventDefault();
        return;
      }

      // Up or Down
      if (keyCode === 38 || keyCode === 40) {
        const step = mapping[keyCode];
        this.spinner.scrollDown(step);
        event.preventDefault();
        return;
      }
    },
  },
};
</script>
