<template>
  <div :class="ns.b()">
    <div :class="ns.e('header')">
      <slot name="header" :date="i18nDate">
        <div :class="ns.e('title')">{{ i18nDate }}</div>
        <div v-if="validatedRange.length === 0" :class="ns.e('button-group')">
          <el-button-group>
            <el-button size="small" @click="selectDate('prev-month')"> 上个月 </el-button>
            <el-button size="small" @click="selectDate('today')"> 今天 </el-button>
            <el-button size="small" @click="selectDate('next-month')"> 下个月 </el-button>
          </el-button-group>
        </div>
      </slot>
    </div>
    <div v-if="validatedRange.length === 0" :class="ns.e('body')">
      <date-table :date="date" :selected-day="realSelectedDay" @pick="pickDay">
        <template v-if="$slots.dateCell" #dateCell="data">
          <slot name="dateCell" v-bind="data"></slot>
        </template>
      </date-table>
    </div>
    <div v-else :class="ns.e('body')">
      <date-table
        v-for="(range_, index) in validatedRange"
        :key="index"
        :date="range_[0]"
        :selected-day="realSelectedDay"
        :range="range_"
        :hide-header="index !== 0"
        @pick="pickDay"
      >
        <template v-if="$slots.dateCell" #dateCell="data">
          <slot name="dateCell" v-bind="data"></slot>
        </template>
      </date-table>
    </div>
  </div>
</template>

<script lang="ts">
import { ref, computed, defineComponent } from 'vue'
import dayjs from 'dayjs'
import { ElButton, ElButtonGroup } from '@element-ultra/components/button'
import { useNamespace } from '@element-ultra/hooks'
import { debugWarn } from '@element-ultra/utils'
import DateTable from './date-table.vue'
import { calendarProps, calendarEmits } from './calendar'

import type { ComputedRef } from 'vue'
import type { Dayjs } from 'dayjs'

type DateType = 'prev-month' | 'next-month' | 'prev-year' | 'next-year' | 'today'

export default defineComponent({
  name: 'ElCalendar',

  components: {
    DateTable,
    ElButton,
    ElButtonGroup
  },

  props: calendarProps,
  emits: calendarEmits,

  setup(props, { emit }) {
    const ns = useNamespace('calendar')

    const selectedDay = ref<Dayjs>()
    const now = dayjs().locale('zh-cn')

    const prevMonthDayjs = computed(() => {
      return date.value.subtract(1, 'month').date(1)
    })
    const curMonthDatePrefix = computed(() => {
      return dayjs(date.value).locale('zh-cn').format('YYYY-MM')
    })

    const nextMonthDayjs = computed(() => {
      return date.value.add(1, 'month').date(1)
    })

    const prevYearDayjs = computed(() => {
      return date.value.subtract(1, 'year').date(1)
    })

    const nextYearDayjs = computed(() => {
      return date.value.add(1, 'year').date(1)
    })

    const i18nDate = computed(() => {
      return `${date.value.year()} 年 ${date.value.format('M')} 月`
    })

    const realSelectedDay = computed<Dayjs | undefined>({
      get() {
        if (!props.modelValue) return selectedDay.value
        return date.value
      },
      set(val) {
        if (!val) return
        selectedDay.value = val
        const result = val.toDate()

        emit('input', result)
        emit('update:modelValue', result)
      }
    })

    const date: ComputedRef<Dayjs> = computed(() => {
      if (!props.modelValue) {
        if (realSelectedDay.value) {
          return realSelectedDay.value
        } else if (validatedRange.value.length) {
          return validatedRange.value[0][0]
        }
        return now
      } else {
        return dayjs(props.modelValue).locale('zh-cn')
      }
    })

    // https://github.com/element-plus/element-plus/issues/3155
    // Calculate the validate date range according to the start and end dates
    const calculateValidatedDateRange = (startDayjs: Dayjs, endDayjs: Dayjs): [Dayjs, Dayjs][] => {
      const firstDay = startDayjs.startOf('week')
      const lastDay = endDayjs.endOf('week')
      const firstMonth = firstDay.get('month')
      const lastMonth = lastDay.get('month')

      // Current mouth
      if (firstMonth === lastMonth) {
        return [[firstDay, lastDay]]
      }
      // Two adjacent months
      else if (firstMonth + 1 === lastMonth) {
        const firstMonthLastDay = firstDay.endOf('month')
        const lastMonthFirstDay = lastDay.startOf('month')

        // Whether the last day of the first month and the first day of the last month is in the same week
        const isSameWeek = firstMonthLastDay.isSame(lastMonthFirstDay, 'week')
        const lastMonthStartDay = isSameWeek ? lastMonthFirstDay.add(1, 'week') : lastMonthFirstDay

        return [
          [firstDay, firstMonthLastDay],
          [lastMonthStartDay.startOf('week'), lastDay]
        ]
      }
      // Three consecutive months (compatible: 2021-01-30 to 2021-02-28)
      else if (firstMonth + 2 === lastMonth) {
        const firstMonthLastDay = firstDay.endOf('month')
        const secondMonthFirstDay = firstDay.add(1, 'month').startOf('month')

        // Whether the last day of the first month and the second month is in the same week
        const secondMonthStartDay = firstMonthLastDay.isSame(secondMonthFirstDay, 'week')
          ? secondMonthFirstDay.add(1, 'week')
          : secondMonthFirstDay

        const secondMonthLastDay = secondMonthStartDay.endOf('month')
        const lastMonthFirstDay = lastDay.startOf('month')

        // Whether the last day of the second month and the last day of the last month is in the same week
        const lastMonthStartDay = secondMonthLastDay.isSame(lastMonthFirstDay, 'week')
          ? lastMonthFirstDay.add(1, 'week')
          : lastMonthFirstDay

        return [
          [firstDay, firstMonthLastDay],
          [secondMonthStartDay.startOf('week'), secondMonthLastDay],
          [lastMonthStartDay.startOf('week'), lastDay]
        ]
      }
      // Other cases
      else {
        debugWarn('ElCalendar', 'start time and end time interval must not exceed two months')
        return []
      }
    }

    // if range is valid, we get a two-digit array
    const validatedRange = computed(() => {
      if (!props.range) return []
      const rangeArrDayjs = props.range.map(_ => dayjs(_).locale('zh-cn'))
      const [startDayjs, endDayjs] = rangeArrDayjs
      if (startDayjs.isAfter(endDayjs)) {
        debugWarn('ElCalendar', 'end time should be greater than start time')
        return []
      }
      if (startDayjs.isSame(endDayjs, 'month')) {
        // same month
        return calculateValidatedDateRange(startDayjs, endDayjs)
      } else {
        // two months
        if (startDayjs.add(1, 'month').month() !== endDayjs.month()) {
          debugWarn('ElCalendar', 'start time and end time interval must not exceed two months')
          return []
        }
        return calculateValidatedDateRange(startDayjs, endDayjs)
      }
    })

    const pickDay = (day: Dayjs) => {
      realSelectedDay.value = day
    }

    const selectDate = (type: DateType) => {
      let day: Dayjs
      if (type === 'prev-month') {
        day = prevMonthDayjs.value
      } else if (type === 'next-month') {
        day = nextMonthDayjs.value
      } else if (type === 'prev-year') {
        day = prevYearDayjs.value
      } else if (type === 'next-year') {
        day = nextYearDayjs.value
      } else {
        day = now
      }

      if (day.isSame(date.value, 'day')) return
      pickDay(day)
    }

    return {
      selectedDay,
      curMonthDatePrefix,
      i18nDate,
      realSelectedDay,
      date,
      validatedRange,
      pickDay,
      selectDate,

      ns
    }
  }
})
</script>
