<template>
  <table class="mm-date-table">
    <thead>
      <tr>
        <th
          v-for="name of weekDayNames"
          :key="name">
          {{ name }}
        </th>
      </tr>
    </thead>
    <tbody
      @click="handleClick"
      @mousemove="throttleHandleMouseMove">
      <tr
        v-for="(row, rowIndex) of days"
        :key="rowIndex">
        <td
          v-for="(cell, cellIndex) of row"
          :key="cellIndex">
          <div
            :class="cell.classes"
            class="mm-day">
            <span v-if="root && root.$scopedSlots['date-cell']">
              <scope-cell :cell="cell"></scope-cell>
            </span>
            <span v-else>
              {{ cell.day }}
            </span>
          </div>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
import { t } from "../../../locale"
import { getDaysInMonth, getDateInstance, today, pad, getCellDateDisabled } from "../../../utils/date"
import { startOfDay } from "date-fns"
import chunk from "lodash/chunk"
import throttle from "lodash/throttle"

const PREV_MONTH = "prev-month"
const NEXT_MONTH = "next-month"
const WEEK_DAY_NAMES = [
  t("ui.datepicker.weeks.mon"),
  t("ui.datepicker.weeks.tue"),
  t("ui.datepicker.weeks.wed"),
  t("ui.datepicker.weeks.thu"),
  t("ui.datepicker.weeks.fri"),
  t("ui.datepicker.weeks.sat"),
  t("ui.datepicker.weeks.sun"),
]

const getStartOfDayTime = (day) => startOfDay(getDateInstance(day)).getTime()

export default {
  name: "mm-date-table",
  inject: {
    root: {
      from: "mm-date-picker",
    },
  },
  components: {
    scopeCell: {
      inject: {
        root: {
          from: "mm-date-picker",
        },
      },
      props: {
        cell: null,
      },
      render() {
        return this.root.$scopedSlots["date-cell"]({
          cell: this.cell,
        })
      },
    },
  },
  props: {
    today: {
      type: Date,
      default() {
        return today()
      },
    },
    weekStart: {
      type: Number,
      default: 7,
      validator(val) {
        return val >= 1 && val <= 7
      },
    },
    month: {
      type: Number,
      validator(value) {
        return value >= 0 && value <= 11
      },
    },
    year: Number,
    startDate: [Date, String],
    endDate: [Date, String],
    selecting: {
      type: Boolean,
      default: false,
    },
    range: Array,
    pickedDate: [Date, String],
    onPick: Function,
    onHover: Function,
    disabledDate: {
      type: Function,
      default: () => false,
    },
  },
  data() {
    return {
      throttleHandleMouseMove: null,
    }
  },
  computed: {
    weekDayNames() {
      const names = [...WEEK_DAY_NAMES]
      return names.splice(this.weekStart - 1).concat(names)
    },
    days() {
      const year = this.year
      const month = this.month
      const firstDayInMonth = new Date(year, month)
      const offset = (firstDayInMonth.getDay() + 7 - this.weekStart) % 7
      const daysInMonth = getDaysInMonth(year, month + 1)
      const daysInPreviousMonth = getDaysInMonth(year, month)
      const weekCount = Math.ceil((offset + daysInMonth) / 7)
      const today = getStartOfDayTime(this.today)

      const days = Array.from({ length: weekCount * 7 }).map((_, index) => {
        let cell = {
          type: "normal",
          day: "",
          month: "",
          year: "",
          classes: [],
          isToday: false,
          isStart: false,
          isEnd: false,
          isInRange: false,
          isSelected: false,
        }

        if (index < offset) {
          cell.type = PREV_MONTH
          cell.day = daysInPreviousMonth + index + 1 - offset
          cell.month = (month + 11) % 12
          cell.year = month === 0 ? year - 1 : year
          cell.classes.push(PREV_MONTH)
        } else if (index - offset < daysInMonth) {
          cell.day = index + 1 - offset
          cell.month = month
          cell.year = year
          if (getStartOfDayTime(`${year}/${pad(month + 1, 2)}/${pad(index + 1 - offset, 2)}`) === today) {
            cell.type = "today"
            cell.classes.push("today")
            cell.isToday = true
          }
        } else {
          cell.type = NEXT_MONTH
          cell.day = index + 1 - offset - daysInMonth
          cell.month = (month + 1) % 12
          cell.year = month === 11 ? year + 1 : year
          cell.classes.push(NEXT_MONTH)
        }

        const time = getStartOfDayTime(`${cell.year}/${pad(cell.month + 1, 2)}/${pad(cell.day, 2)}`)
        if (this.isSpecialTime("pickedDate", time)) {
          cell.classes.push("selected")
          cell.isSelected = true
        }

        if (cell.type !== PREV_MONTH && cell.type !== NEXT_MONTH) {
          if (this.isSpecialTime("startDate", time)) {
            cell.classes.push("start")
            cell.isStart = true
          }

          if (this.isSpecialTime("endDate", time)) {
            cell.classes.push("end")
            cell.isStart = true
          }

          if (this.isInRange(time)) {
            cell.classes.push("range")
            cell.isInRange = true
          }
        }

        if (this.isDisabled(cell)) {
          cell.classes.push("disabled")
        }

        return cell
      })

      return chunk(days, 7)
    },
  },
  created() {
    this.throttleHandleMouseMove = throttle(this.handleClick, 60)
  },
  methods: {
    isDisabled({ year, month, day }) {
      return getCellDateDisabled({
        mode: "date",
        cellDate: new Date(year, month, day),
        disabledDate: this.disabledDate,
      })
    },
    isSpecialTime(type, time) {
      if (!this[type]) {
        return false
      }
      return time === getStartOfDayTime(this[type])
    },
    isInRange(time) {
      if (!this.range || !this.range.length) {
        return false
      }
      return time >= getStartOfDayTime(this.range[0]) && time <= getStartOfDayTime(this.range[1])
    },
    getIndexes(e) {
      let { target } = e
      if (target.tagName === "SPAN") {
        target = target.parentNode.parentNode
      }

      if (target.tagName === "DIV") {
        target = target.parentNode
      }

      return target.tagName === "TD"
        ? {
          cellIndex: target.cellIndex,
          rowIndex: target.parentNode.rowIndex,
        }
        : null
    },
    handleClick(e) {
      const callBack = e.type === "click" ? this.onPick : this.onHover
      if (!callBack) {
        return
      }

      let { target } = e
      if (target.tagName === "SPAN") {
        target = target.parentNode.parentNode
      }

      if (target.tagName === "DIV") {
        target = target.parentNode
      }

      if (target.tagName !== "TD") {
        return
      }

      const { cellIndex } = target
      const { rowIndex } = target.parentNode
      const cell = this.days[rowIndex - 1][cellIndex]
      let year = this.year
      let month = this.month
      let date = cell.day

      if (cell.classes.includes("disabled")) {
        return
      }

      if (cell.classes.includes(PREV_MONTH)) {
        if (month === 0) {
          year = year - 1
          month = 11
        } else {
          month = month - 1
        }
      } else if (cell.classes.includes(NEXT_MONTH)) {
        if (month === 11) {
          year = year + 1
          month = 0
        } else {
          month = month + 1
        }
      }

      callBack(new Date(year, month, date))
    },
  },
}
</script>
