<template>
  <div
    :class="themeClasses"
    class="mm-datepicker">
    <single-selector
      ref="selector"
      :value="displayValue"
      :disabled="disabled"
      :readonly="readonly"
      :visible.sync="visible"
      :clearable="clearable"
      :on-clear="clearHandler"
      :show-icon="showIcon"
      v-bind="$attrs">
      <ui-icon
        slot="suffix-icon"
        :name="icon"
        size="12px"></ui-icon>
    </single-selector>

    <ui-popper
      ref="popper"
      :visible.sync="visible"
      :placement="placement"
      :popper-options="popperOptions"
      class="mm-datepicker-popper"
      target="selector"
      trigger="click"
      @after-enter="$emit('after-enter')"
      @after-leave="$emit('after-leave')">
      <div
        class="mm-datepicker-container"
        tabindex="0"
        style="outline: 0"
        @focusin="dropdownFocusHandler">
        <ul
          v-if="localShortcuts.length"
          class="mm-datepicker-shortcuts">
          <li
            v-for="(item, index) of localShortcuts"
            :key="index">
            <a
              :title="item.text"
              @click="handleShortcutClick(item)">
              {{ item.text }}
            </a>
          </li>
        </ul>
        <component
          :is="panel"
          v-model="date"
          :disabled-date="disabledDate"
          :disabled-time="disabledTime"
          :type="type"
          :visible.sync="visible"
          :format="format"
          @picked-start="handlePickedStart"
          @picked-end="handlePickedEnd"
          @switch-panel="handleSwitchPanel"></component>
      </div>
    </ui-popper>
  </div>
</template>

<script>
import { t } from "../../locale"
import { formatDate, parseDate, getDateInstance, RANGE_SHORTCUTS } from "../../utils/date"
import input from "../../mixins/input"
import themeSelect from "../../mixins/theme-select"
import DatePanel from "./panel/date"
import DateRangePanel from "./panel/date-range"
import Popper from "../popper"
import SingleSelector from "../selector/single"
import Icon from "../icon"

import config from "../../managers/config"

config.defaults({
  "DatePicker.valueType": "string",
  "DatePicker.type": "date",
  "DatePicker.mode": "single",
  "DatePicker.rangeSeparator": () => t("ui.datepicker.separator"),
  "DatePicker.shortcuts": "",
  "DatePicker.placement": "bottom-start",
  "DatePicker.icon": "event",
  "DatePicker.clearable": true,
  "DatePicker.showIcon": true,
  "DatePicker.popperOptions": () => (
    {
      modifiers: {
        flip: {
          boundariesElement: "viewport",
        },
        preventOverflow: {
          boundariesElement: "viewport",
          escapeWithReference: false,
        },
      },
    }
  ),
})

const DEFAULT_FORMAT_MAP = {
  year: "ui.datepicker.format.year",
  month: "ui.datepicker.format.month",
  date: "ui.datepicker.format.date",
  "date-time": "ui.datepicker.format.dateTime",
}

const DEFAULT_VALUE_FORMAT_MAP = {
  year: "yyyy",
  month: "yyyy-MM",
  date: "yyyy-MM-dd",
  "date-time": "yyyy-MM-dd HH:mm:ss",
}

const PANEL_MAP = {
  single: "ui-date-panel",
  range: "ui-date-range-panel",
  dateTime: "ui-date-time-panel",
}

export default {
  RANGE_SHORTCUTS,
  name: "mm-date-picker",
  components: {
    "ui-popper": Popper,
    "ui-date-panel": DatePanel,
    "ui-date-range-panel": DateRangePanel,
    "ui-icon": Icon,
    SingleSelector,
  },
  provide() {
    return {
      "mm-date-picker": this,
    }
  },
  mixins: [input, themeSelect],
  inheritAttrs: false,
  props: {
    value: {
      type: [String, Date, Number, Array],
    },
    valueType: {
      type: String,
      default: () => config.get("DatePicker.valueType"),
      validator(value) {
        return ["string", "date", "timestamp"].includes(value)
      },
    },
    format: {
      type: String,
    },
    valueFormat: {
      type: String,
    },
    type: {
      type: String,
      default: () => config.get("DatePicker.type"),
      validator: value => {
        return ["year", "month", "date", "date-time"].includes(value)
      },
    },
    popperOptions: {
      type: Object,
      default: () => config.get("DatePicker.popperOptions").call(this),
    },
    mode: {
      type: String,
      default: () => config.get("DatePicker.mode"),
      validator(value) {
        return ["single", "range"].includes(value)
      },
    },
    rangeSeparator: {
      type: String,
      default() {
        return config.get("DatePicker.rangeSeparator").call(this)
      },
    },
    disabledDate: Function,
    disabledTime: Function,
    shortcuts: {
      type: [String, Array],
      default: () => config.get("DatePicker.shortcuts"),
    },
    placement: {
      type: String,
      default: () => config.get("DatePicker.placement"),
    },
    icon: {
      type: String,
      default: () => config.get("DatePicker.icon"),
    },
    clearable: {
      type: Boolean,
      default: () => config.get("DatePicker.clearable"),
    },
    showIcon: {
      type: Boolean,
      default: () => config.get("DatePicker.showIcon"),
    },
    onChange: {
      type: Function,
      default: () => {},
    },
  },
  data() {
    return {
      displayValue: "",
      date: this.mode === "range" ? [] : "",
      visible: false,
      searchValue: "",
    }
  },
  computed: {
    realFormat() {
      return this.format ? this.format : t(DEFAULT_FORMAT_MAP[this.type])
    },
    realValueFormat() {
      return this.valueFormat ? this.valueFormat : DEFAULT_VALUE_FORMAT_MAP[this.type]
    },
    panel() {
      return PANEL_MAP[this.mode]
    },
    localShortcuts() {
      let shortcuts = []
      if (typeof this.shortcuts === "string" && this.shortcuts.length) {
        shortcuts = this.shortcuts
          .split(",")
          .map(item => RANGE_SHORTCUTS[item])
          .filter(item => !!item)
      } else if (Array.isArray(this.shortcuts)) {
        shortcuts = this.shortcuts
      }

      return shortcuts
    },
  },
  watch: {
    value: {
      immediate: true,
      handler(newValue) {
        if (this.mode === "single") {
          this.handleSingleValueChange(newValue)
        }

        if (this.mode === "range") {
          this.handleRangeValueChange(newValue)
        }
      },
    },
    date(value) {
      if (this.mode === "single") {
        this.handleSingleDateChange(value)
      }

      if (this.mode === "range") {
        this.handleRangeDateChange(value)
      }
    },
  },
  methods: {
    setDateByStartToEnd(start, end) {
      if (start > end) {
        [start, end] = [end, start]
      }

      if (start && end) {
        const { realFormat, rangeSeparator, date } = this

        const oldValue =
          formatDate(date[0], realFormat) + rangeSeparator + formatDate(date[1], realFormat)
        const newValue =
          formatDate(start, realFormat) + rangeSeparator + formatDate(end, realFormat)

        if (oldValue !== newValue) {
          this.date = [start, end]
          this.displayValue = newValue
        }
      } else {
        this.displayValue = ""
        this.date = []
      }
    },
    handleBlur() {
      if (this.mode === "single") {
        this.handleSingleBlur()
      }

      if (this.mode === "range") {
        this.handleRangeBlur()
      }
    },
    handleSingleBlur() {
      if (this.displayValue) {
        const oldValue = formatDate(this.date, this.realFormat)
        if (this.displayValue !== oldValue) {
          this.date = parseDate(this.displayValue, this.realFormat)
        }
      } else {
        this.date = ""
      }
    },
    handleRangeBlur() {
      if (this.displayValue) {
        let [start, end] = this.displayValue.split(this.rangeSeparator)
        this.setDateByStartToEnd(parseDate(start, this.realFormat), parseDate(end, this.realFormat))
      } else {
        this.date = []
      }
    },
    handleSingleValueChange(value) {
      const date =
        typeof value === "string" ? parseDate(value, this.realValueFormat) : getDateInstance(value)
      if (!value || !date) {
        this.date = ""
        return
      }

      if (!this.date || date.getTime() !== this.date.getTime()) {
        this.date = date
        this.displayValue = formatDate(date, this.realFormat)
      }
    },
    handleRangeValueChange(value) {
      if (Array.isArray(value) && value[0] && value[1]) {
        let [start, end] = value
        this.setDateByStartToEnd(
          parseDate(start, this.realValueFormat),
          parseDate(end, this.realValueFormat)
        )
      } else if (this.date.length) {
        this.date = []
      }
    },
    handleSingleDateChange(value) {
      this.displayValue = formatDate(value, this.realFormat)
      let updateValue = null

      switch (this.valueType) {
        case "string":
          updateValue = formatDate(value, this.realValueFormat)
          break
        case "date":
          updateValue = value
          break
        case "timestamp":
          updateValue = value.getTime()
          break
      }

      if (this.value !== updateValue) {
        this.$emit("input", updateValue)
        this.$emit("change", updateValue)
        this.onChange(updateValue)
      }

      if (this.type !== "date-time") {
        this.visible = false
      }
    },
    handleRangeDateChange(value) {
      if (!value.length) {
        this.displayValue = ""
        const updateValue = []
        this.$emit("input", updateValue)
        this.$emit("change", updateValue)
        this.onChange(updateValue)
        return
      }

      const [start, end] = value
      this.displayValue =
        formatDate(start, this.realFormat) + this.rangeSeparator + formatDate(end, this.realFormat)
      let updateValue = null

      switch (this.valueType) {
        case "string":
          updateValue = [
            formatDate(start, this.realValueFormat),
            formatDate(end, this.realValueFormat),
          ]
          break
        case "date":
          updateValue = value
          break
        case "timestamp":
          updateValue = [start.getTime(), end.getTime()]
          break
      }

      this.$emit("input", updateValue)
      this.$emit("change", updateValue)
      this.onChange(updateValue)

      if (this.type !== "date-time") {
        this.visible = false
      }
    },
    handleClick() {
      if (!this.disabled && !this.readonly) {
        this.visible = !this.visible
      }
    },
    handleShortcutClick(item) {
      if (typeof item.onClick === "function") {
        item.onClick(this)
        this.visible = false
      }
    },
    handlePickedStart(value) {
      this.$emit("picked-start", value)
    },
    handlePickedEnd(value) {
      this.$emit("picked-end", value)
    },
    handleSwitchPanel() {
      this.$nextTick(() => {
        this.$refs.popper && this.$refs.popper.update()
      })
    },
    dropdownFocusHandler() {
      this.$refs.selector.focus()
    },
    clearHandler() {
      this.date = ""
      this.$emit("clear")
      this.$emit("picked-end", "")
    },
  },
}
</script>
