<template>
  <transition :name="transitionName">
    <div
      v-show="showDatepicker"
      :id="wrapperId"
      v-click-outside="handleClickOutside"
      class="asd__wrapper"
      :class="wrapperClasses"
      :style="showFullscreen ? undefined : wrapperStyles"
      @keydown.esc="closeDatepicker"
    >
      <div v-if="showFullscreen" class="asd__mobile-header asd__mobile-only">
        <button class="asd__mobile-close" type="button" @click="closeDatepicker">
          <slot v-if="$slots['close-icon']" name="close-icon"></slot>
          <div v-else class="asd__mobile-close-icon">X</div>
        </button>
        <h3>{{ mobileHeader || mobileHeaderFallback }}</h3>
      </div>
      <div class="asd__datepicker-header">
        <button
          aria-label="previous month"
          class="asd__change-month-button asd__change-month-button--previous"
          type="button"
          @click="previousMonth"
        >
          <cp-icon type="chevron-left" />
        </button>
        <button
          aria-label="next month"
          class="asd__change-month-button asd__change-month-button--next"
          type="button"
          @click="nextMonth"
        >
          <cp-icon type="chevron-right" />
        </button>
        <div
          v-for="(month, index) in showMonths"
          :key="month"
          class="asd__days-legend"
          :style="[monthWidthStyles, { left: `${width * index}px` }]"
        >
          <div v-for="(day, dayIndex) in daysShort" :key="dayIndex" class="asd__day-title">{{ day }}</div>
        </div>
      </div>
      <div class="asd__inner-wrapper" :style="innerStyles">
        <transition-group name="asd__list-complete" tag="div">
          <div
            v-for="(month, monthIndex) in months"
            :key="month.firstDateOfMonth"
            class="asd__month"
            :class="{ 'asd__month--hidden': monthIndex === 0 || monthIndex > showMonths }"
            :style="monthWidthStyles"
          >
            <div class="asd__month-name">
              <select
                v-if="showMonthYearSelect"
                v-model="month.monthName"
                v-resize-select
                class="asd__month-year-select"
                :tabindex="monthIndex === 0 || monthIndex > showMonths ? -1 : 0"
                @change="updateMonth(monthIndex, month.year, $event)"
              >
                <option
                  v-for="(monthName, idx) in monthNames"
                  :key="`month-${monthIndex}-${monthName}`"
                  :disabled="isMonthDisabled(month.year, idx)"
                  :value="monthName"
                >
                  {{ monthName }}
                </option>
              </select>
              <span v-else>{{ month.monthName }}</span>
              <select
                v-if="showMonthYearSelect"
                v-model="month.year"
                class="asd__month-year-select"
                :tabindex="monthIndex === 0 || monthIndex > showMonths ? -1 : 0"
                @change="updateYear(monthIndex, month.monthNumber - 1, $event)"
              >
                <option
                  v-if="years.indexOf(month.year) === -1"
                  :key="`month-${monthIndex}-${month.year}`"
                  :disabled="true"
                  :value="month.year"
                >
                  {{ month.year }}
                </option>
                <option v-for="year in years" :key="`month-${monthIndex}-${year}`" :value="year">
                  {{ year }}
                </option>
              </select>
              <span v-else>{{ month.year }}</span>
            </div>
            <table class="asd__month-table" role="presentation">
              <tbody>
                <tr v-for="(week, index) in month.weeks" :key="index" class="asd__week">
                  <!-- biome-ignore lint/a11y/useKeyWithMouseEvents: hover preview is mouse-only; keyboard uses day buttons -->
                  <td
                    v-for="({ dayDate, dayNumber }, dayIndex) in week"
                    :key="`${dayIndex}_${dayNumber}`"
                    :ref="`date-${dayDate}`"
                    class="asd__day"
                    :class="getDayClasses(dayNumber, dayDate)"
                    :data-date="dayDate"
                    @mouseover="setHoverDate(dayDate)"
                  >
                    <button
                      v-if="dayNumber"
                      class="asd__day-button"
                      :date="dayDate"
                      :disabled="isDisabled(dayDate)"
                      tabindex="-1"
                      type="button"
                      @click="selectDate(dayDate)"
                    >
                      <span class="asd__day-number">{{ dayNumber }}</span>
                    </button>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </transition-group>
      </div>
      <slot />
    </div>
  </transition>
</template>

<script lang="ts">
// @ts-nocheck — legacy Options API bundle; excluded from strict type-checking until a typed rewrite.
import { DateTime, Info } from 'luxon'
import { useId } from 'vue'

import CpIcon from '@/components/CpIcon.vue'

import ResizeSelect from '@/directives/ResizeSelect'

export default {
  name: 'CoreDatepicker',
  components: {
    CpIcon,
  },
  directives: {
    resizeSelect: ResizeSelect,
  },
  props: {
    triggerElementId: {
      type: String,
      default: 'datepicker-trigger',
    },
    dateOne: {
      type: [String, Date],
      default: '',
    },
    dateTwo: {
      type: [String, Date],
      default: '',
    },
    minDate: {
      type: [String, Date],
      default: () => DateTime.local().toISODate(),
    },
    maxDate: {
      type: [String, Date],
      default: '',
    },
    mode: {
      type: String,
      default: 'single',
    },
    offsetY: {
      type: Number,
      default: 0,
    },
    offsetX: {
      type: Number,
      default: 0,
    },
    monthsToShow: {
      type: Number,
      default: 2,
    },
    startOpen: {
      type: Boolean,
    },
    fullscreenMobile: {
      type: Boolean,
      default: false,
    },
    inline: {
      type: Boolean,
      default: false,
    },
    mobileHeader: {
      type: String,
      default: '',
    },
    disabledDates: {
      type: Array,
      default: () => [],
    },
    enabledDates: {
      type: Array,
      default: () => [],
    },
    customizedDates: {
      type: Array,
      default: () => [],
    },
    showActionButtons: {
      type: Boolean,
      default: false,
    },
    showShortcutsMenuTrigger: {
      type: Boolean,
      default: false,
    },
    showMonthYearSelect: {
      type: Boolean,
      default: false,
    },
    yearsForSelect: {
      type: Number,
      default: 10,
    },
    trigger: {
      type: Boolean,
      default: false,
    },
    closeAfterSelect: {
      type: Boolean,
      default: false,
    },
    isRecurrent: {
      type: Boolean,
      default: false,
    },
    closeCalendar: {
      type: Boolean,
      default: false,
    },
    locale: {
      type: String,
      default: () => {
        return navigator.language
      },
    },
    isTest: {
      type: Boolean,
      default: () => process.env.NODE_ENV === 'test',
    },
  },
  emits: [
    'dateOneSelected',
    'dateTwoSelected',
    'isInline',
    'previousMonth',
    'nextMonth',
    'opened',
    'cancelled',
    'closed',
    'apply',
  ],
  data() {
    return {
      wrapperId: `datepicker-wrapper-${useId()}`,
      dateLabelFormat: 'dddd, MMMM D, YYYY',
      showDatepicker: false,
      showKeyboardShortcutsMenu: false,
      showMonths: 2,
      colors: {
        selected: '#fffff',
        inRange: '#66e2da',
        selectedText: '#fff',
        text: '#565a5c',
        inRangeBorder: '#33dacd',
        disabled: '#fff',
        hoveredInRange: '#67f6ee',
      },
      sundayFirst: false,
      monthNames: Info.months('long', { locale: this.locale }),
      days: Info.weekdaysFormat('long', { locale: this.locale }),
      daysShort: Info.weekdaysFormat('short', { locale: this.locale }),
      texts: {
        apply: 'Apply',
        cancel: 'Cancel',
        keyboardShortcuts: 'Keyboard Shortcuts',
      },
      startingDate: '',
      months: [],
      years: [],
      width: 300,
      selectedDate1: '',
      selectedDate2: '',
      isSelectingDate1: true,
      hoverDate: '',
      focusedDate: '',
      alignRight: false,
      triggerPosition: {},
      triggerWrapperPosition: {},
      viewportWidth: undefined,
      isMobile: undefined,
      triggerElement: undefined,
    }
  },
  computed: {
    transitionName() {
      return this.inline ? '' : 'asd__fade'
    },
    wrapperClasses() {
      return [
        this.$attrs.class,
        {
          'asd__wrapper--datepicker-open': this.showDatepicker,
          'asd__wrapper--full-screen': this.showFullscreen,
          'asd__wrapper--isInline': this.inline,
          'asd__wrapper--showOneMonth': this.monthsToShow === 1,
        },
      ]
    },
    wrapperStyles() {
      return {
        width: `${this.width * this.showMonths}px`,
        zIndex: this.inline ? '' : '100',
      }
    },
    innerStyles() {
      return {
        'margin-left': this.showFullscreen ? `-${this.viewportWidth}` : `-${this.width}px`,
      }
    },
    monthWidthStyles() {
      return {
        width: this.showFullscreen ? this.viewportWidth : `${this.width}px`,
      }
    },
    mobileHeaderFallback() {
      return this.mode === 'range' ? 'Select dates' : 'Select date'
    },
    showFullscreen() {
      return this.isMobile && this.fullscreenMobile
    },
    datesSelected() {
      return !!((this.selectedDate1 && this.selectedDate1 !== '') || (this.selectedDate2 && this.selectedDate2 !== ''))
    },
    allDatesSelected() {
      return !!(this.selectedDate1 && this.selectedDate1 !== '' && this.selectedDate2 && this.selectedDate2 !== '')
    },
    hasMinDate() {
      return !!(this.minDate && this.minDate !== '')
    },
    isRangeMode() {
      return this.mode === 'range'
    },
    isSingleMode() {
      return this.mode === 'single'
    },
    datePropsCompound() {
      // used to watch for changes in props, and update GUI accordingly
      return this.dateOne + this.dateTwo
    },
    isDateTwoBeforeDateOne() {
      if (!this.dateTwo) {
        return false
      }
      const dtDateOne = DateTime.fromISO(this.dateOne)
      const dtDateTwo = DateTime.fromISO(this.dateTwo)

      return dtDateTwo < dtDateOne
    },
    visibleMonths() {
      const firstMonthArray = this.months.filter((_m, index) => index > 0)
      const numberOfMonthsArray = []
      for (let i = 0; i < this.showMonths; i++) {
        numberOfMonthsArray.push(i)
      }
      return numberOfMonthsArray.map((_, index) => {
        if (!firstMonthArray[index]) return
        return firstMonthArray[index].firstDateOfMonth
      })
    },
  },
  watch: {
    selectedDate1(newValue) {
      const newDate = !newValue || newValue === '' ? '' : newValue
      this.$emit('dateOneSelected', newDate)
    },
    selectedDate2(newValue) {
      const newDate = !newValue || newValue === '' ? '' : newValue
      this.$emit('dateTwoSelected', newDate)
    },
    mode() {
      this.setStartDates()
    },
    minDate() {
      this.setStartDates()
      this.generateMonths()
      this.generateYears()
    },
    maxDate() {
      this.generateYears()
    },
    datePropsCompound() {
      if (this.dateOne !== this.selectedDate1) {
        this.startingDate = this.dateOne
        this.setStartDates()
        this.generateMonths()
        this.generateYears()
      }
      if (this.isDateTwoBeforeDateOne) {
        this.selectedDate2 = ''
        this.$emit('dateTwoSelected', '')
      }
    },
    trigger(newValue) {
      if (newValue) {
        setTimeout(() => {
          this.openDatepicker()
        }, 0)
      }
    },
    closeCalendar() {
      this.closeDatepicker()
    },
  },
  created() {
    this.setupDatepicker()

    if (this.sundayFirst) {
      this.setSundayToFirstDayInWeek()
    }
  },
  mounted() {
    this.handleDatepickerSizing()

    this.triggerElement = this.isTest ? document.createElement('input') : document.getElementById(this.triggerElementId)

    this.setStartDates()
    this.generateMonths()
    this.generateYears()

    if (this.startOpen || this.inline) {
      this.openDatepicker()
    }

    this.triggerElement.addEventListener('click', this._handleWindowClickEvent)
  },
  unmounted() {
    window.removeEventListener('resize', this._handleWindowResizeEvent)

    window.removeEventListener('click', this._handleWindowClickEvent)

    this.triggerElement.removeEventListener('click', this._handleWindowClickEvent)
  },
  methods: {
    handleDatepickerSizing() {
      this.viewportWidth = `${window.innerWidth}px`

      this._handleWindowResizeEvent = this.debounce(() => {
        if (this.inline) this.openDatepicker()
        else this.closeDatepicker()
        this.$emit('isInline', this.inline)
        this.positionDatepicker()
        this.setStartDates()
      }, 200)

      this._handleWindowClickEvent = (event) => {
        if (event.target.id === this.triggerElementId) {
          event.stopPropagation()
          event.preventDefault()
          this.toggleDatepicker()
        }
      }
      window.addEventListener('resize', this._handleWindowResizeEvent)
    },
    getDayClasses(dayNumber, dayDate) {
      return [
        {
          'asd__day--enabled': dayNumber !== 0,
          'asd__day--empty': dayNumber === 0,
          'asd__day--disabled': this.isDisabled(dayDate),
          'asd__day--selected':
            dayDate && (this.selectedDate1 === dayDate || this.selectedDate2 === dayDate) && !this.isRecurrent,
          'asd__day--selected-recurrent':
            dayDate && (this.selectedDate1 === dayDate || this.selectedDate2 === dayDate) && this.isRecurrent,
          'asd__day--date-one-not-selected': this.mode === 'range' && this.selectedDate1 === '',
          'asd__day--date-two-not-selected':
            this.mode === 'range' &&
            this.selectedDate1 !== '' &&
            this.selectedDate2 === '' &&
            this.isAfter(dayDate, this.selectedDate1),
          'asd__day--in-range': this.isInRange(dayDate),
          'asd__day--end-week': this.isEndWeek(dayDate),
          'asd__day--start-week': this.isStartWeek(dayDate),
          'asd__day--start-month': this.isStartMonth(dayDate),
          'asd__day--end-month': this.isEndMonth(dayDate),
          'asd__day--today': dayDate && this.isToday(dayDate),
          'asd__day--hovered': this.isHoveredInRange(dayDate),
          'asd__selected-date-one': dayDate && dayDate === this.selectedDate1 && this.mode === 'range',
          'asd__selected-date-two': dayDate && dayDate === this.selectedDate2 && this.mode === 'range',
        },
        this.customizedDateClass(dayDate),
      ]
    },
    debounce(func, wait, immediate) {
      let timeout

      return function () {
        // biome-ignore lint: use the rest of argument [...arguments]
        const args = arguments

        const later = () => {
          timeout = null
          if (!immediate) func.apply(this, args)
        }
        const callNow = immediate && !timeout
        clearTimeout(timeout)
        timeout = setTimeout(later, wait)
        if (callNow) func.apply(this, args)
      }
    },
    copyObject(obj) {
      return JSON.parse(JSON.stringify(obj))
    },
    findAncestor(element, selector) {
      if (!element) {
        return null
      }
      if (typeof element.closest === 'function') {
        return element.closest(selector) || null
      }
      while (element) {
        if (element.matches(selector)) {
          return element
        }
        element = element.parentElement
      }
      return null
    },
    handleClickOutside(event) {
      if (event.target.id === this.triggerElementId || !this.showDatepicker || this.inline || this.isRecurrent) {
        return
      }
      this.closeDatepicker()
    },
    shouldHandleInput(event, key) {
      return event.keyCode === key && (!event.shiftKey || event.keyCode === 191) && this.showDatepicker
    },
    isMonthDisabled(year, monthIndex) {
      const monthDate = DateTime.fromObject({ year, month: monthIndex + 1 }).toJSDate()
      const startOfMonth = DateTime.fromISO(this.minDate).startOf('month')

      if (this.hasMinDate && monthDate < startOfMonth) {
        return true
      }
      return this.isAfterEndDate(monthDate)
    },
    generateMonths() {
      this.months = []
      let currentMonth = this.startingDate
      for (let i = 0; i < this.showMonths + 2; i++) {
        this.months.push(this.getMonth(currentMonth))
        currentMonth = this.addMonths(currentMonth)
      }
    },
    generateYears() {
      if (!this.showMonthYearSelect) return
      this.years = []
      const currentYear = DateTime.fromISO(this.startingDate).year
      const startYear = this.minDate ? DateTime.fromISO(this.minDate).year : currentYear - this.yearsForSelect
      const endYear = this.maxDate ? DateTime.fromISO(this.maxDate).year : currentYear + this.yearsForSelect
      for (let year = startYear; year <= endYear; year++) {
        this.years.push(year.toString())
      }
    },
    setupDatepicker() {
      if (this.$options.dateLabelFormat) {
        this.dateLabelFormat = this.copyObject(this.$options.dateLabelFormat)
      }
      if (this.$options.sundayFirst) {
        this.sundayFirst = this.copyObject(this.$options.sundayFirst)
      }
      if (this.$options.colors) {
        const colors = this.copyObject(this.$options.colors)
        this.colors.selected = colors.selected || this.colors.selected
        this.colors.inRange = colors.inRange || this.colors.inRange
        this.colors.hoveredInRange = colors.hoveredInRange || this.colors.hoveredInRange
        this.colors.selectedText = colors.selectedText || this.colors.selectedText
        this.colors.text = colors.text || this.colors.text
        this.colors.inRangeBorder = colors.inRangeBorder || this.colors.inRangeBorder
        this.colors.disabled = colors.disabled || this.colors.disabled
      }
      if (this.$options.monthNames && this.$options.monthNames.length === 12) {
        this.monthNames = this.copyObject(this.$options.monthNames)
      }
      if (this.$options.days && this.$options.days.length === 7) {
        this.days = this.copyObject(this.$options.days)
      }
      if (this.$options.daysShort && this.$options.daysShort.length === 7) {
        this.daysShort = this.copyObject(this.$options.daysShort)
      }
      if (this.$options.texts) {
        const texts = this.copyObject(this.$options.texts)
        this.texts.apply = texts.apply || this.texts.apply
        this.texts.cancel = texts.cancel || this.texts.cancel
      }
    },
    setStartDates() {
      // Set dates if initialized
      if (this.mode === 'range') {
        if (this.dateOne) this.selectDate(this.dateOne)
        if (this.dateTwo) this.selectDate(this.dateTwo)
      }

      let startDate = this.dateOne ? DateTime.fromISO(this.dateOne) : DateTime.local()
      const minDate = DateTime.fromISO(this.minDate)

      if (this.hasMinDate && startDate < minDate) {
        startDate = minDate
      }
      this.startingDate = this.subtractMonths(startDate.toJSDate())
      this.selectedDate1 = this.dateOne
      this.selectedDate2 = this.dateTwo
      this.focusedDate = startDate.toJSDate()
    },
    setSundayToFirstDayInWeek() {
      const lastDay = this.days.pop()
      this.days.unshift(lastDay)
      const lastDayShort = this.daysShort.pop()
      this.daysShort.unshift(lastDayShort)
    },
    getMonth(date) {
      const dt = DateTime.fromISO(date)
      const firstDateOfMonth = dt.toFormat('yyyy-MM-01')
      const year = dt.toFormat('yyyy')
      const monthNumber = parseInt(dt.toFormat('M'))
      const monthName = this.monthNames[monthNumber - 1]

      return {
        year,
        firstDateOfMonth,
        monthName,
        monthNumber,
        weeks: this.getWeeks(firstDateOfMonth),
      }
    },
    getWeeks(date) {
      const dt = DateTime.fromISO(date)
      const weekDayNotInMonth = { dayNumber: 0 }
      const daysInMonth = dt.daysInMonth
      const year = dt.toFormat('yyyy')
      const month = dt.toFormat('MM')
      let firstDayInWeek = parseInt(dt.toFormat(this.sundayFirst ? 'd' : 'E'))
      if (this.sundayFirst) {
        firstDayInWeek++
      }
      const weeks = []
      let week = []

      // add empty days to get first day in correct position
      for (let s = 1; s < firstDayInWeek; s++) {
        week.push(weekDayNotInMonth)
      }
      for (let d = 0; d < daysInMonth; d++) {
        const isLastDayInMonth = d >= daysInMonth - 1
        const dayNumber = d + 1
        const dayNumberFull = dayNumber < 10 ? `0${dayNumber}` : dayNumber
        week.push({
          dayNumber,
          dayNumberFull,
          dayDate: `${year}-${month}-${dayNumberFull}`,
        })

        if (week.length === 7) {
          weeks.push(week)
          week = []
        } else if (isLastDayInMonth) {
          for (let i = 0; i < 7 - week.length; i++) {
            week.push(weekDayNotInMonth)
          }
          weeks.push(week)
          week = []
        }
      }
      return weeks
    },
    selectDate(date) {
      if (this.isBeforeMinDate(date) || this.isAfterEndDate(date) || this.isDateDisabled(date)) {
        return
      }

      if (this.mode === 'single') {
        this.selectedDate1 = date
        if (this.closeAfterSelect) this.closeDatepicker()
        return
      }

      const dtDate = DateTime.fromISO(date)
      const dtSelectedDate1 =
        typeof this.selectedDate1 === 'object'
          ? DateTime.fromJSDate(this.selectedDate1)
          : DateTime.fromISO(this.selectedDate1)

      if (this.isSelectingDate1 || dtDate <= dtSelectedDate1) {
        this.selectedDate1 = date
        this.isSelectingDate1 = false

        const dtSelectedDate2 =
          typeof this.selectedDate2 === 'object'
            ? DateTime.fromJSDate(this.selectedDate2)
            : DateTime.fromISO(this.selectedDate2)

        if (dtSelectedDate2 < dtDate) {
          this.selectedDate2 = ''
        }
      } else {
        this.selectedDate2 = date
        this.isSelectingDate1 = true

        if (dtSelectedDate1 > dtDate) {
          this.selectedDate1 = ''
        } else if (this.showActionButtons) {
          // if user has selected both dates, focus the apply button for accessibility
          this.$refs['apply-button'].focus()
        }

        if (this.allDatesSelected && this.closeAfterSelect) {
          this.closeDatepicker()
        }
      }
    },
    setHoverDate(date) {
      this.hoverDate = date
    },
    setFocusedDate(date) {
      this.focusedDate = date
      const dateElement = this.$refs[`date-${date}`]
      // handle .focus() on ie11 by adding a short timeout
      if (dateElement && dateElement.length) {
        setTimeout(() => {
          dateElement[0].focus()
        }, 10)
      }
    },
    resetFocusedDate(setToFirst) {
      if (this.focusedDate && !this.isDateVisible(this.focusedDate)) {
        const visibleMonthIdx = setToFirst ? 0 : this.visibleMonths.length - 1
        const targetMonth = DateTime.fromISO(this.visibleMonths[visibleMonthIdx])
        const monthIdx = targetMonth.month
        const year = targetMonth.year
        this.focusedDate = DateTime.fromISO(this.focusedDate).set({ month: monthIdx, year }).toISODate()
      }
    },
    isToday(date) {
      return DateTime.local().toISODate() === date
    },
    isSelected(date) {
      if (!date) {
        return
      }
      return this.selectedDate1 === date || this.selectedDate2 === date
    },
    isInRange(date) {
      if (!this.allDatesSelected || this.isSingleMode) {
        return false
      }

      const dtDate = DateTime.fromISO(date)
      const dtSelectedDate1 =
        typeof this.selectedDate1 === 'object'
          ? DateTime.fromJSDate(this.selectedDate1)
          : DateTime.fromISO(this.selectedDate1)
      const dtSelectedDate2 =
        typeof this.selectedDate2 === 'object'
          ? DateTime.fromJSDate(this.selectedDate2)
          : DateTime.fromISO(this.selectedDate2)
      const dtHoverDate = DateTime.fromISO(this.hoverDate)

      return (
        (dtDate > dtSelectedDate1 && dtDate < dtSelectedDate2) ||
        (dtDate > dtSelectedDate1 && dtDate < dtHoverDate && !this.allDatesSelected)
      )
    },
    isEndWeek(date) {
      if (!date) return

      return DateTime.fromISO(date).weekday === 7
    },
    isStartWeek(date) {
      if (!date) return

      return DateTime.fromISO(date).weekday === 1
    },
    isEndMonth(date) {
      if (!date) return

      const dt = DateTime.fromISO(date)

      return dt.day === dt.daysInMonth
    },
    isStartMonth(date) {
      if (!date) return

      return DateTime.fromISO(date).day === 1
    },
    isAfter(dateOne, dateTwo) {
      if (!dateOne || !dateTwo) return false

      const dtOne = DateTime.fromISO(dateOne)
      const dtTwo = DateTime.fromISO(dateTwo)

      return dtOne > dtTwo
    },
    isHoveredInRange(date) {
      if (this.isSingleMode || this.allDatesSelected) {
        return false
      }

      const dtDate = DateTime.fromISO(date)
      const dtSelectedDate1 =
        typeof this.selectedDate1 === 'object'
          ? DateTime.fromJSDate(this.selectedDate1)
          : DateTime.fromISO(this.selectedDate1)
      const dtHoverDate = DateTime.fromISO(this.hoverDate)

      return (dtDate > dtSelectedDate1 && dtDate < dtHoverDate) || (dtDate > dtHoverDate && date < dtSelectedDate1)
    },
    isBeforeMinDate(date) {
      if (!this.minDate) {
        return false
      }
      const dtDate = DateTime.fromISO(date)
      const dtMinDate = DateTime.fromISO(this.minDate)
      return dtDate < dtMinDate
    },
    isAfterEndDate(date) {
      if (!this.maxDate) {
        return false
      }
      const dtDate = DateTime.fromISO(date)
      const dtEndDate = DateTime.fromISO(this.maxDate)
      return dtDate > dtEndDate
    },
    isDateVisible(date) {
      if (!date) {
        return false
      }
      const dtDate = DateTime.fromISO(date)
      const dtStart = DateTime.fromISO(this.visibleMonths[0]).minus({ day: 1 })
      const dtEnd = DateTime.fromISO(this.visibleMonths[this.monthsToShow - 1])
        .plus({ month: 1 })
        .set({ day: 0 })
        .plus({ day: 1 })

      return dtDate > dtStart && dtDate < dtEnd
    },
    isDateDisabled(date) {
      if (this.enabledDates.length > 0) {
        return !this.enabledDates.includes(date)
      } else {
        return this.disabledDates.includes(date)
      }
    },
    customizedDateClass(date) {
      let customizedClasses = ''
      if (this.customizedDates.length > 0) {
        for (let i = 0; i < this.customizedDates.length; i++) {
          if (this.customizedDates[i].dates.includes(date))
            customizedClasses += ` asd__day--${this.customizedDates[i].cssClass}`
        }
      }
      return customizedClasses
    },
    isDisabled(date) {
      return this.isDateDisabled(date) || this.isBeforeMinDate(date) || this.isAfterEndDate(date)
    },
    previousMonth() {
      this.startingDate = this.subtractMonths(this.months[0].firstDateOfMonth)

      this.months.unshift(this.getMonth(this.startingDate))
      this.months.splice(this.months.length - 1, 1)
      this.$emit('previousMonth', this.visibleMonths)
      this.resetFocusedDate(false)
    },
    nextMonth() {
      if (!this.months[this.months.length - 1]) return
      this.startingDate = this.addMonths(this.months[this.months.length - 1].firstDateOfMonth)
      this.months.push(this.getMonth(this.startingDate))
      this.months.splice(0, 1)
      this.$emit('nextMonth', this.visibleMonths)
      this.resetFocusedDate(true)
    },
    subtractMonths(date) {
      const dt = typeof date === 'object' ? DateTime.fromJSDate(date) : DateTime.fromISO(date)
      return dt.minus({ month: 1 }).toISODate()
    },
    addMonths(date) {
      const dt = typeof date === 'object' ? DateTime.fromJSDate(date) : DateTime.fromISO(date)
      return dt.plus({ month: 1 }).toISODate()
    },
    toggleDatepicker() {
      if (this.showDatepicker) {
        this.closeDatepicker()
      } else {
        this.openDatepicker()
      }
    },
    updateMonth(_offset, year, event) {
      const newMonth = event.target.value
      const monthIdx = this.monthNames.indexOf(newMonth)
      const startingDate = DateTime.fromISO(this.startingDate)
      this.startingDate = startingDate.set({ month: monthIdx, year }).toISODate()
      this.generateMonths()
    },
    updateYear(_offset, monthIdx, event) {
      const newYear = event.target.value
      const startingDate = DateTime.fromISO(this.startingDate)
      this.startingDate = startingDate.set({ month: monthIdx, year: newYear }).toISODate()
      this.generateMonths()
    },
    openDatepicker() {
      this.positionDatepicker()
      this.setStartDates()
      this.triggerElement.classList.add('datepicker-open')
      this.showDatepicker = true
      this.initialDate1 = this.dateOne
      this.initialDate2 = this.dateTwo
      this.$emit('opened')
      this.$nextTick(() => {
        if (!this.inline) this.setFocusedDate(this.focusedDate)
      })
    },
    closeDatepickerCancel() {
      if (this.showDatepicker) {
        this.selectedDate1 = this.initialDate1
        this.selectedDate2 = this.initialDate2
        this.$emit('cancelled')
        this.closeDatepicker()
      }
    },
    closeDatepicker() {
      if (this.inline) {
        return
      }
      this.showDatepicker = false
      this.showKeyboardShortcutsMenu = false
      this.triggerElement.classList.remove('datepicker-open')
      this.$emit('closed')
    },
    apply() {
      this.$emit('apply')
      this.closeDatepicker()
    },
    positionDatepicker() {
      const triggerWrapperElement = this.findAncestor(this.triggerElement, '.datepicker-trigger')
      this.triggerPosition = this.triggerElement.getBoundingClientRect()
      if (triggerWrapperElement) {
        this.triggerWrapperPosition = triggerWrapperElement.getBoundingClientRect()
      } else {
        this.triggerWrapperPosition = { left: 0, right: 0 }
      }

      const viewportWidth = document.documentElement.clientWidth || window.innerWidth
      this.viewportWidth = `${viewportWidth}px`

      this.isMobile = viewportWidth < 768

      if (this.isMobile) {
        this.showMonths = 1
      } else {
        if (this.monthsToShow > 2) {
          this.showMonths = 2
        } else {
          this.showMonths = this.monthsToShow
        }
      }

      this.$nextTick(function () {
        const datepickerWrapper = document.getElementById(this.wrapperId)
        if (!this.triggerElement || !datepickerWrapper) {
          return
        }

        const rightPosition =
          this.triggerElement.getBoundingClientRect().left + datepickerWrapper.getBoundingClientRect().width
        this.alignRight = rightPosition > viewportWidth
      })
    },
  },
}
</script>

<style lang="scss">
$tablet: 768px;
$background-grey-color: #f7f7f7;
$transition-time: 0.3s;

@include mx.popover-desktop('asd__fade');

@mixin degrade {
  position: absolute;
  z-index: -1;
  width: 65px;
  height: 35px;
  background: linear-gradient(90deg, #f7f7f7 50%, rgba(241, 241, 241, 0%) 95%);
  content: '';
}

// datepicker
.asd__list-complete-enter-from,
.asd__list-complete-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

.asd__list-complete-leave-active {
  position: absolute;
  visibility: hidden;
}

.asd {
  &__wrapper {
    overflow: hidden;
    border-radius: 10px;
    background-color: var(--cp-background-white);
    text-align: center;
    white-space: nowrap;

    &:not(&--isInline) {
      box-shadow:
        0 9px 24px rgba(62, 62, 91, 20%),
        0 4px 4px rgba(62, 62, 91, 10%);
    }

    &--isInline {
      position: relative !important;
    }

    &::before {
      // Line between the two months
      @media (min-width: $tablet) {
        position: absolute;
        z-index: -1;
        width: 1px;
        margin: auto;
        background: var(--cp-border-soft);
        content: '';
        inset: 20px 0 0;
      }
    }

    &--showOneMonth {
      &::before {
        content: none;
      }
    }

    &--full-screen {
      position: fixed;
      z-index: 100;
      border: none;
      inset: 0;
    }
  }

  &__inner-wrapper {
    position: relative;
    transition: all $transition-time ease;
  }

  &__datepicker-header {
    position: relative;
  }

  &__change-month-button {
    position: absolute;
    z-index: 10;
    top: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--cp-radius-full);

    &:focus-visible {
      outline: 2px solid var(--cp-background-accent-solid);
      outline-offset: 2px;
    }

    i {
      color: var(--cp-text-primary);
    }

    svg {
      cursor: pointer;
      stroke-width: 1.2px;
    }

    @media (min-width: $tablet) {
      top: 22px;
    }

    &--previous {
      left: 16px;

      @media (min-width: $tablet) {
        left: 24px;
      }
    }

    &--next {
      right: 16px;

      @media (min-width: $tablet) {
        right: 24px;
      }
    }
  }

  &__days-legend {
    position: absolute;
    font-size: 12px;
    line-height: 15px;
    user-select: none;

    @media (min-width: $tablet) {
      top: 60px;
      padding: 0 24px;
    }

    @media (max-width: ($tablet - 1px)) {
      top: 56px;
      padding: 0 16px;
    }
  }

  &__day-title {
    display: inline-block;
    width: math.div(1, 7) * 100%;
    margin-left: -1px;
    color: var(--cp-text-secondary);
    font-size: 14px;
    line-height: 18px;
    text-align: center;
  }

  &__month-table {
    width: 100%;
    max-width: 100%;
    border-spacing: 0 6px;
  }

  &__wrapper:not(&__wrapper--isInline) &__month {
    padding-right: 16px;
    padding-left: 16px;

    @media (min-width: $tablet) {
      padding-right: 24px;
      padding-left: 24px;
    }
  }

  &__month {
    display: inline-block;
    padding: 16px 16px 8px;
    transition: all $transition-time ease;

    @media (min-width: $tablet) {
      padding: 24px 24px 12px;
    }

    &--hidden {
      height: 275px;
      visibility: hidden;
    }
  }

  &__month-name {
    margin: 0 0 36px;
    color: var(--cp-text-primary);
    font-size: 16px;
    line-height: 20px;
    text-align: center;

    @media (max-width: $tablet) {
      margin-top: 2px;
    }

    > span {
      padding-right: 3px;
    }
  }

  &__selected {
    &-date-one {
      background: $background-grey-color;
      border-bottom-left-radius: 50%;
      border-top-left-radius: 50%;
    }

    &-date-one.asd__day--end-month {
      &::before {
        @include degrade;

        border-bottom-left-radius: 50%;
        border-top-left-radius: 50%;
      }
    }

    &-date-one.asd__day--end-week:not(.asd__day--selected) {
      border-bottom-right-radius: 10px;
      border-top-right-radius: 10px;
    }

    &-date-two {
      background: $background-grey-color;
      border-bottom-right-radius: 50%;
      border-top-right-radius: 50%;
    }

    &-date-two.asd__day--start-month {
      &::before {
        @include degrade;

        right: 20px;
        width: 20px;
        transform: rotate(180deg);
      }
    }

    &-date-two.asd__day--start-week {
      border-bottom-left-radius: 5px;
      border-top-left-radius: 5px;
    }
  }

  &__day {
    position: relative;
    padding: 0;
    outline: none;

    &--in-range,
    &--hovered {
      background: $background-grey-color;
    }

    &--hovered {
      .asd__day--end-week {
        border-bottom-right-radius: 5px;
        border-top-right-radius: 5px;
      }

      .asd__day--start-week {
        border-bottom-left-radius: 5px;
        border-top-left-radius: 5px;
      }
    }

    &--in-range.asd__day--end-week {
      border-bottom-right-radius: 5px;
      border-top-right-radius: 5px;
    }

    &--in-range.asd__day--start-week {
      border-bottom-left-radius: 5px;
      border-top-left-radius: 5px;
    }

    &--hovered.asd__day--start-month,
    &--in-range.asd__day--start-month:not(.asd__selected-date-one, .asd__selected-date-two) {
      &::before {
        @include degrade;

        right: 20px;
        width: 30px;
        transform: rotate(180deg);
      }
    }

    &--hovered.asd__day--end-month,
    &--in-range.asd__day--end-month:not(.asd__selected-date-one, .asd__selected-date-two) {
      &::before {
        @include degrade;
      }
    }

    &--enabled {
      &:not(.asd__day--disabled, .asd__day--selected, .asd__selected-date-one, .asd__selected-date-two, .asd__day--in-range):hover {
        button {
          z-index: 2;
          border: 2px solid #f7f7f7;
          border-radius: 50%;
          background: var(--cp-background-white);
        }
      }

      &.asd__day--date-two-not-selected:hover {
        background: $background-grey-color;
        border-bottom-right-radius: 50%;
        border-top-right-radius: 50%;

        &.asd__day--start-month::before {
          @include degrade;

          right: 20px;
          width: 30px;
          transform: rotate(180deg);
        }
      }
    }

    &--enabled.asd__day--in-range:hover {
      background: #f1f1f3;
    }

    &--selected {
      button {
        border-radius: 50%;
        background: var(--cp-background-accent-solid);
      }

      button span {
        color: var(--cp-text-white) !important;
      }

      &.asd__selected-date-one::before {
        @include degrade;

        border-bottom-left-radius: 50%;
        border-top-left-radius: 50%;
      }

      &.asd__selected-date-two::before {
        @include degrade;

        right: 20px;
        width: 30px;
        border-bottom-right-radius: 50%;
        border-top-right-radius: 50%;
        transform: rotate(180deg);
      }
    }

    &--disabled .asd__day-number,
    &--empty .asd__day-number {
      color: var(--cp-text-disabled) !important;
    }

    &--disabled * {
      cursor: not-allowed !important;
    }

    &--today .asd__day-button .asd__day-number {
      color: var(--cp-text-accent-primary);
      font-weight: 600;
    }

    &--recurency {
      &:not(.asd__selected-date-one, .asd__selected-date-two) {
        background: $background-grey-color;
      }

      button {
        border: 2px solid $background-grey-color;
        border-radius: 50%;
        background: var(--cp-background-accent-solid) !important;
      }

      button .asd__day-number {
        color: var(--cp-text-white);
      }
    }

    &--selected-recurrent {
      button {
        z-index: 2;
        border: 2px solid $background-grey-color;
        border-radius: 50%;
        background: var(--cp-background-white);
      }

      button::before {
        top: 7px;
        left: 5px;
      }

      span {
        color: var(--cp-text-primary);
      }
    }
  }

  &__day-button {
    position: relative;
    width: 35px;
    height: 35px;
    border: none;
    background: transparent;
    font-size: 14px;
    outline: none;
    user-select: none;

    .asd__day-number {
      position: relative;
      z-index: 3;
      color: var(--cp-text-primary);
    }
  }

  &__mobile-header {
    position: relative;
    height: 50px;
    padding: 15px !important;
    border-bottom: var(--cp-border-soft);
    text-align: center;

    h3 {
      margin: 0;
      font-size: 20px;
    }
  }

  &__mobile-only {
    display: none;

    @media (max-width: $tablet) {
      display: block;
    }
  }

  &__mobile-close {
    position: absolute;
    z-index: 100;
    top: 7px;
    right: 5px;
    padding: 5px;
    border: none;
    cursor: pointer;

    &__icon {
      position: relative;
      padding: 0;
      font-size: 1.6em;
      font-weight: bold;
    }
  }
}
</style>
