import React, { forwardRef, KeyboardEvent, ReactNode, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import { CButton } from '../button/CButton'

import {
  convertToDateObject,
  createGroupsInArray,
  getCalendarDate,
  getDateBySelectionType,
  getMonthDetails,
  getMonthsNames,
  getYears,
  isDateDisabled,
  isDateInRange,
  isDateSelected,
  isDisableDateInRange,
  isSameDateAs,
  isToday,
} from './utils'

import { useForkedRef, useStateWithCallback } from '../../hooks'

export type DisabledDate = ((date: Date) => boolean) | Date | Date[]

export interface CCalendarProps {
  /**
   * A string that provides an accessible label for the button that navigates to the next month in the calendar. This label is read by screen readers to describe the action associated with the button.
   *
   * @since 5.5.0
   */
  ariaNavNextMonthLabel?: string
  /**
   * A string that provides an accessible label for the button that navigates to the next year in the calendar. This label is intended for screen readers to help users understand the button's functionality.
   *
   * @since 5.5.0
   */
  ariaNavNextYearLabel?: string
  /**
   * A string that provides an accessible label for the button that navigates to the previous month in the calendar. Screen readers will use this label to explain the purpose of the button.
   *
   * @since 5.5.0
   */
  ariaNavPrevMonthLabel?: string
  /**
   * A string that provides an accessible label for the button that navigates to the previous year in the calendar. This label helps screen reader users understand the button's function.
   *
   * @since 5.5.0
   */
  ariaNavPrevYearLabel?: string
  /**
   * Default date of the component
   */
  calendarDate?: Date | string | null
  /**
   * The number of calendars that render on desktop devices.
   */
  calendars?: number
  /**
   * A string of all className you want applied to the component.
   */
  className?: string
  /**
   * Set the format of day name.
   *
   * @default 'numeric'
   * @since 4.3.0
   */
  dayFormat?: 'numeric' | '2-digit' | ((date: Date) => string | number)
  /**
   * Specify the list of dates that cannot be selected.
   */
  disabledDates?: DisabledDate | DisabledDate[]
  /**
   * Initial selected to date (range).
   */
  endDate?: Date | string | null
  /**
   * Sets the day of start week.
   * - 0 - Sunday,
   * - 1 - Monday,
   * - 2 - Tuesday,
   * - 3 - Wednesday,
   * - 4 - Thursday,
   * - 5 - Friday,
   * - 6 - Saturday,
   *
   * @default 1
   */
  firstDayOfWeek?: number
  /**
   * Sets the default locale for components. If not set, it is inherited from the browser.
   *
   * @default 'default'
   */
  locale?: string
  /**
   * Max selectable date.
   */
  maxDate?: Date | string | null
  /**
   * Min selectable date.
   */
  minDate?: Date | string | null
  /**
   * Show arrows navigation.
   */
  navigation?: boolean
  /**
   * The custom next icon.
   */
  navNextIcon?: ReactNode
  /**
   * The custom next double icon.
   */
  navNextDoubleIcon?: ReactNode
  /**
   * The custom prev icon.
   */
  navPrevIcon?: ReactNode
  /**
   * The custom prev double icon.
   */
  navPrevDoubleIcon?: ReactNode
  /**
   * Reorder year-month navigation, and render year first.
   *
   * @since 4.3.0
   */
  navYearFirst?: boolean
  /**
   * Allow range selection.
   */
  range?: boolean
  /**
   * Set whether days in adjacent months shown before or after the current month are selectable. This only applies if the `showAdjacementDays` option is set to true.
   *
   * @since 4.11.0
   */
  selectAdjacementDays?: boolean
  /**
   * Set whether to display dates in adjacent months (non-selectable) at the start and end of the current month.
   *
   * @since 4.11.0
   */
  showAdjacementDays?: boolean
  /**
   * Set whether to display week numbers in the calendar.
   *
   * @since 5.0.0
   */
  showWeekNumber?: boolean
  /**
   * Toggle select mode between start and end date.
   */
  selectEndDate?: boolean
  /**
   * Specify the type of date selection as day, week, month, or year.
   *
   * @since 5.0.0
   */
  selectionType?: 'day' | 'week' | 'month' | 'year'
  /**
   * Initial selected date.
   */
  startDate?: Date | string | null
  /**
   * Set length or format of day name.
   *
   * @default 2
   */
  weekdayFormat?: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
  /**
   * Label displayed over week numbers in the calendar.
   *
   * @since 5.0.0
   */
  weekNumbersLabel?: string
  /**
   * Callback fired when the calendar date changed.
   */
  onCalendarDateChange?: (date: Date | string) => void
  /**
   * Callback fired when the user hovers over the calendar cell.
   */
  onDateHover?: (date: Date | string | null) => void
  /**
   * Callback fired when the start date changed.
   */
  onStartDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void // TODO: remove formatedDate?
  /**
   * Callback fired when the end date changed.
   */
  onEndDateChange?: (date: Date | string | null, formatedDate?: string | undefined) => void // TODO: remove formatedDate?
  /**
   * Callback fired when the selection type changed.
   */
  onSelectEndChange?: (value: boolean) => void
  /**
   * Callback fired when the view type of calendar changed.
   */
  onViewChanged?: (view: string) => void
}

const Calendar = (props: {
  calendarDate: Date
  dayFormat?: 'numeric' | '2-digit' | ((date: Date) => string | number)
  disabledDates?: DisabledDate | DisabledDate[]
  endDate: Date | null
  firstDayOfWeek: number
  hoverDate: Date | null
  locale: string
  maxDate: Date | null
  minDate: Date | null
  onCalendarClick: (date: Date) => void
  onCalendarKeyDown: (event: KeyboardEvent<HTMLDivElement>, date: Date) => void
  onCalendarMouseEnter: (date: Date) => void
  onCalendarMouseLeave: () => void
  order: number
  selectAdjacementDays: boolean
  selectEndDate: boolean | undefined
  selectionType: 'day' | 'week' | 'month' | 'year'
  showAdjacementDays: boolean
  showWeekNumber: boolean
  startDate: Date | null
  view: 'days' | 'months' | 'years'
  weekdayFormat: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
  weekNumbersLabel: string | undefined
}) => {
  const {
    calendarDate,
    dayFormat,
    disabledDates,
    endDate,
    firstDayOfWeek,
    hoverDate,
    locale,
    maxDate,
    minDate,
    onCalendarClick,
    onCalendarKeyDown,
    onCalendarMouseEnter,
    onCalendarMouseLeave,
    selectAdjacementDays,
    selectEndDate,
    selectionType,
    showWeekNumber,
    showAdjacementDays,
    startDate,
    view,
    weekdayFormat,
    weekNumbersLabel,
  } = props
  const [listOfMonths, setListOfMonths] = useState<string[][]>([])

  useEffect(() => {
    setListOfMonths(createGroupsInArray(getMonthsNames(locale), 4))
  }, [])

  const monthDetails = getMonthDetails(
    calendarDate.getFullYear(),
    calendarDate.getMonth(),
    firstDayOfWeek,
  )
  const listOfYears = createGroupsInArray(getYears(calendarDate.getFullYear()), 4)
  const weekDays = monthDetails[0].days

  return (
    <table>
      {view === 'days' && (
        <thead>
          <tr>
            {showWeekNumber && (
              <th className="calendar-cell">
                <div className="calendar-header-cell-inner">{weekNumbersLabel}</div>
              </th>
            )}
            {weekDays.map(({ date }: { date: Date }, idx: number) => (
              <th
                className="calendar-cell"
                abbr={date.toLocaleDateString(locale, { weekday: 'long' })}
                key={idx}
              >
                <div className="calendar-header-cell-inner">
                  {typeof weekdayFormat === 'function'
                    ? weekdayFormat(date)
                    : typeof weekdayFormat === 'string'
                      ? date.toLocaleDateString(locale, { weekday: weekdayFormat })
                      : date
                          .toLocaleDateString(locale, { weekday: 'long' })
                          .slice(0, weekdayFormat)}
                </div>
              </th>
            ))}
          </tr>
        </thead>
      )}
      <tbody>
        {view === 'days' &&
          monthDetails.map((week, index) => {
            const date = convertToDateObject(
              week.weekNumber === 0
                ? `${calendarDate.getFullYear()}W53`
                : `${calendarDate.getFullYear()}W${week.weekNumber}`,
              selectionType,
            )
            const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
            const isSelected = isDateSelected(date, startDate, endDate)
            const current = week.days.some((day) => day.month === 'current')
            return (
              <tr
                className={classNames('calendar-row', {
                  disabled: isDisabled,
                  range: selectionType === 'week' && isDateInRange(date, startDate, endDate),
                  'range-hover':
                    selectionType === 'week' && hoverDate && selectEndDate
                      ? isDateInRange(date, startDate, hoverDate)
                      : isDateInRange(date, hoverDate, endDate),
                  selected: isSelected,
                })}
                key={index}
                tabIndex={selectionType === 'week' && current && !isDisabled ? 0 : -1}
                {...(isSelected && { 'aria-selected': true })}
                {...(selectionType === 'week' &&
                  !isDisabled && {
                    onBlur: () => onCalendarMouseLeave(),
                    onClick: () => onCalendarClick(date),
                    onFocus: () => onCalendarMouseEnter(date),
                    onKeyDown: (event) => onCalendarKeyDown(event, date),
                    onMouseEnter: () => onCalendarMouseEnter(date),
                    onMouseLeave: () => onCalendarMouseLeave(),
                  })}
              >
                {showWeekNumber && (
                  <th className="calendar-cell-week-number">
                    {week.weekNumber === 0 ? 53 : week.weekNumber}
                  </th>
                )}
                {week.days.map(({ date, month }: { date: Date; month: string }, idx: number) => {
                  const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
                  const isSelected = isDateSelected(date, startDate, endDate)
                  return month === 'current' || showAdjacementDays ? (
                    <td
                      className={classNames('calendar-cell', {
                        ...(selectionType === 'day' && {
                          clickable: month !== 'current' && selectAdjacementDays,
                          disabled: isDisabled,
                          'range-hover':
                            month === 'current' &&
                            (hoverDate && selectEndDate
                              ? isDateInRange(date, startDate, hoverDate)
                              : isDateInRange(date, hoverDate, endDate)),
                          range: month === 'current' && isDateInRange(date, startDate, endDate),
                          selected: isSelected,
                        }),
                        [month]: true,
                        today: month === 'current' && isToday(date),
                      })}
                      key={idx}
                      tabIndex={
                        selectionType === 'day' &&
                        (month === 'current' || selectAdjacementDays) &&
                        !isDisabled
                          ? 0
                          : -1
                      }
                      title={date.toLocaleDateString(locale)}
                      {...(isSelected && { 'aria-selected': true })}
                      {...(selectionType === 'day' &&
                        (month === 'current' || selectAdjacementDays) && {
                          onBlur: () => onCalendarMouseLeave(),
                          onClick: () => onCalendarClick(date),
                          onFocus: () => onCalendarMouseEnter(date),
                          onKeyDown: (event) => onCalendarKeyDown(event, date),
                          onMouseEnter: () => onCalendarMouseEnter(date),
                          onMouseLeave: () => onCalendarMouseLeave(),
                        })}
                      {...(month !== 'current' &&
                        !selectAdjacementDays && {
                          onMouseEnter: () => onCalendarMouseLeave(),
                        })}
                    >
                      <div className="calendar-cell-inner">
                        {typeof dayFormat === 'function'
                          ? dayFormat(date)
                          : date.toLocaleDateString(locale, { day: dayFormat })}
                      </div>
                    </td>
                  ) : (
                    <td key={idx}></td>
                  )
                })}
              </tr>
            )
          })}
        {view === 'months' &&
          listOfMonths.map((row, index) => {
            return (
              <tr key={index}>
                {row.map((month, idx) => {
                  const monthNumber = index * 3 + idx
                  const date = new Date(calendarDate.getFullYear(), monthNumber, 1)
                  const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
                  const isSelected = isDateSelected(date, startDate, endDate)
                  return (
                    <td
                      className={classNames('calendar-cell', {
                        disabled: isDisabled,
                        selected: isSelected,
                        'range-hover':
                          selectionType === 'month' &&
                          (hoverDate && selectEndDate
                            ? isDateInRange(date, startDate, hoverDate)
                            : isDateInRange(date, hoverDate, endDate)),
                        range: isDateInRange(date, startDate, endDate),
                      })}
                      key={idx}
                      tabIndex={isDisabled ? -1 : 0}
                      {...(isSelected && { 'aria-selected': true })}
                      {...(!isDisabled && {
                        onBlur: () => onCalendarMouseLeave(),
                        onClick: () => onCalendarClick(date),
                        onFocus: () => onCalendarMouseEnter(date),
                        onKeyDown: (event) => onCalendarKeyDown(event, date),
                        onMouseEnter: () => onCalendarMouseEnter(date),
                        onMouseLeave: () => onCalendarMouseLeave(),
                      })}
                    >
                      <div className="calendar-cell-inner">{month}</div>
                    </td>
                  )
                })}
              </tr>
            )
          })}
        {view === 'years' &&
          listOfYears.map((row, index) => {
            return (
              <tr key={index}>
                {row.map((year, idx) => {
                  const date = new Date(year, 0, 1)
                  const isDisabled = isDateDisabled(date, minDate, maxDate, disabledDates)
                  const isSelected = isDateSelected(date, startDate, endDate)
                  return (
                    <td
                      className={classNames('calendar-cell year', {
                        disabled: isDisabled,
                        selected: isSelected,
                        'range-hover':
                          selectionType === 'year' &&
                          (hoverDate && selectEndDate
                            ? isDateInRange(date, startDate, hoverDate)
                            : isDateInRange(date, hoverDate, endDate)),
                        range: isDateInRange(date, startDate, endDate),
                      })}
                      key={idx}
                      tabIndex={isDisabled ? -1 : 0}
                      {...(isSelected && { 'aria-selected': true })}
                      {...(!isDisabled && {
                        onBlur: () => onCalendarMouseLeave(),
                        onClick: () => onCalendarClick(date),
                        onFocus: () => onCalendarMouseEnter(date),
                        onKeyDown: (event) => onCalendarKeyDown(event, date),
                        onMouseEnter: () => onCalendarMouseEnter(date),
                        onMouseLeave: () => onCalendarMouseLeave(),
                      })}
                    >
                      <div className="calendar-cell-inner">
                        {new Date(year, 0, 1).toLocaleDateString(locale, { year: 'numeric' })}
                      </div>
                    </td>
                  )
                })}
              </tr>
            )
          })}
      </tbody>
    </table>
  )
}

const Navigation = (props: {
  ariaNavNextMonthLabel: string
  ariaNavNextYearLabel: string
  ariaNavPrevMonthLabel: string
  ariaNavPrevYearLabel: string
  calendarDate: Date
  locale: string
  navigation: boolean
  navNextDoubleIcon: ReactNode
  navNextIcon: ReactNode
  navPrevDoubleIcon: ReactNode
  navPrevIcon: ReactNode
  navYearFirst: boolean | undefined
  onMonthClick: () => void
  onNavigationClick: (direction: string, double?: boolean) => void
  onYearClick: () => void
  view: 'days' | 'months' | 'years'
}) => {
  const {
    ariaNavNextMonthLabel,
    ariaNavNextYearLabel,
    ariaNavPrevMonthLabel,
    ariaNavPrevYearLabel,
    calendarDate,
    locale,
    navigation,
    navNextDoubleIcon,
    navNextIcon,
    navPrevDoubleIcon,
    navPrevIcon,
    navYearFirst,
    onMonthClick,
    onNavigationClick,
    onYearClick,
    view,
  } = props

  return (
    <div className="calendar-nav">
      {navigation && (
        <div className="calendar-nav-prev">
          <CButton
            color="transparent"
            size="sm"
            aria-label={ariaNavPrevYearLabel}
            onClick={() => onNavigationClick('prev', true)}
          >
            {navPrevDoubleIcon ?? (
              <span className="calendar-nav-icon calendar-nav-icon-double-prev" />
            )}
          </CButton>
          {view === 'days' && (
            <CButton
              color="transparent"
              size="sm"
              aria-label={ariaNavPrevMonthLabel}
              onClick={() => onNavigationClick('prev')}
            >
              {navPrevIcon ?? <span className="calendar-nav-icon calendar-nav-icon-prev" />}
            </CButton>
          )}
        </div>
      )}
      <div
        className="calendar-nav-date"
        aria-live="polite"
        {...(navYearFirst && { style: { display: 'flex', justifyContent: 'center' } })}
      >
        {view === 'days' && (
          <CButton color="transparent" size="sm" onClick={() => navigation && onMonthClick()}>
            {calendarDate && calendarDate.toLocaleDateString(locale, { month: 'long' })}
          </CButton>
        )}
        <CButton
          color="transparent"
          size="sm"
          onClick={() => navigation && onYearClick()}
          {...(navYearFirst && { style: { order: '-1' } })}
        >
          {calendarDate && calendarDate.toLocaleDateString(locale, { year: 'numeric' })}
        </CButton>
      </div>
      {navigation && (
        <div className="calendar-nav-next">
          {view === 'days' && (
            <CButton
              color="transparent"
              size="sm"
              aria-label={ariaNavNextMonthLabel}
              onClick={() => onNavigationClick('next')}
            >
              {navNextIcon ?? <span className="calendar-nav-icon calendar-nav-icon-next" />}
            </CButton>
          )}
          <CButton
            color="transparent"
            size="sm"
            aria-label={ariaNavNextYearLabel}
            onClick={() => onNavigationClick('next', true)}
          >
            {navNextDoubleIcon ?? (
              <span className="calendar-nav-icon calendar-nav-icon-double-next" />
            )}
          </CButton>
        </div>
      )}
    </div>
  )
}

export const CCalendar = forwardRef<HTMLDivElement, CCalendarProps>(
  (
    {
      ariaNavNextMonthLabel = 'Next month',
      ariaNavNextYearLabel = 'Next year',
      ariaNavPrevMonthLabel = 'Previous month',
      ariaNavPrevYearLabel = 'Previous year',
      startDate,
      endDate,
      calendarDate = startDate || endDate || null,
      calendars = 1,
      className,
      dayFormat = 'numeric',
      disabledDates,
      firstDayOfWeek = 1,
      locale = 'default',
      maxDate,
      minDate,
      navigation = true,
      navNextIcon,
      navNextDoubleIcon,
      navPrevIcon,
      navPrevDoubleIcon,
      navYearFirst,
      range,
      selectAdjacementDays = false,
      selectEndDate,
      selectionType = 'day',
      showAdjacementDays = true,
      showWeekNumber = false,
      weekdayFormat = 2,
      weekNumbersLabel,
      onCalendarDateChange,
      onDateHover,
      onEndDateChange,
      onStartDateChange,
      onSelectEndChange,
      onViewChanged,
    },
    ref,
  ) => {
    const calendarRef = useRef<HTMLDivElement>(null)
    const forkedRef = useForkedRef(ref, calendarRef)
    const isInitialMount = useRef(true)
    const [_calendarDate, setCalendarDate] = useState<Date | null>(null)

    useEffect(() => {
      if (selectionType === 'day' || selectionType === 'week') {
        setView('days')
        return
      }

      if (selectionType === 'month') {
        setView('months')
        return
      }

      if (selectionType === 'year') {
        setView('years')
        return
      }
    }, [selectionType])

    useEffect(() => {
      if (calendarDate === null) {
        setCalendarDate(new Date())
        return
      }

      if (calendarDate) {
        const date = convertToDateObject(calendarDate, selectionType)
        !isSameDateAs(_calendarDate, date) && setCalendarDate(date)
      }
    }, [calendarDate])

    const [_startDate, setStartDate] = useStateWithCallback<Date | null>(
      startDate ? convertToDateObject(startDate, selectionType) : null,
      () =>
        onStartDateChange && onStartDateChange(getDateBySelectionType(_startDate, selectionType)),
      !isInitialMount.current,
    )
    useEffect(() => {
      const date = startDate ? convertToDateObject(startDate, selectionType) : null
      if (!isSameDateAs(date, _startDate)) {
        setStartDate(date)
      }
    }, [startDate])

    const [_endDate, setEndDate] = useStateWithCallback<Date | null>(
      endDate ? convertToDateObject(endDate, selectionType) : null,
      () => onEndDateChange && onEndDateChange(getDateBySelectionType(_endDate, selectionType)),
      !isInitialMount.current,
    )
    useEffect(() => {
      const date = endDate ? convertToDateObject(endDate, selectionType) : null
      if (!isSameDateAs(date, _endDate)) {
        setEndDate(date)
      }
    }, [endDate])

    const [_hoverDate, setHoverDate] = useState<Date | null>(null)

    const [_maxDate, setMaxDate] = useState<Date | null>(
      maxDate ? convertToDateObject(maxDate, selectionType) : null,
    )
    useEffect(() => {
      maxDate && setMaxDate(convertToDateObject(maxDate, selectionType))
    }, [maxDate])

    const [_minDate, setMinDate] = useState<Date | null>(
      minDate ? convertToDateObject(minDate, selectionType) : null,
    )
    useEffect(() => {
      minDate && setMinDate(convertToDateObject(minDate, selectionType))
    }, [minDate])

    const [_selectEndDate, setSelectEndDate] = useStateWithCallback(
      selectEndDate,
      onSelectEndChange,
    )
    useEffect(() => {
      setSelectEndDate(selectEndDate)
    }, [selectEndDate])

    useEffect(() => {
      !isInitialMount.current &&
        typeof _selectEndDate === 'boolean' &&
        onSelectEndChange &&
        onSelectEndChange(_selectEndDate)
    }, [_selectEndDate])

    const [view, setView] = useStateWithCallback<'days' | 'months' | 'years'>('days', onViewChanged)

    useEffect(() => {
      isInitialMount.current = false
    }, [])

    const setCalendarPage = (years: number, months = 0, setMonth?: number) => {
      if (_calendarDate === null) {
        return
      }

      const year = _calendarDate.getFullYear()
      const month = _calendarDate.getMonth()
      const d = new Date(year, month, 1)

      years && d.setFullYear(d.getFullYear() + years)
      months && d.setMonth(d.getMonth() + months)
      typeof setMonth === 'number' && d.setMonth(setMonth)

      setCalendarDate(d)
      onCalendarDateChange && onCalendarDateChange(d)
    }

    const handleCalendarClick = (date: Date, index?: number) => {
      if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
        return
      }

      const _date = new Date(date)

      if (view === 'days') {
        setCalendarDate(index ? new Date(_date.setMonth(_date.getMonth() - index)) : _date)
      }

      if (view === 'months' && selectionType !== 'month') {
        setCalendarDate(index ? new Date(_date.setMonth(_date.getMonth() - index)) : _date)
        setView('days')
        return
      }

      if (view === 'years' && selectionType !== 'year') {
        setCalendarDate(index ? new Date(_date.setFullYear(_date.getFullYear() - index)) : _date)
        setView('months')
        return
      }

      if (range) {
        if (_selectEndDate) {
          setSelectEndDate(false)

          if (_startDate && _startDate > date) {
            setStartDate(null)
            setEndDate(null)
            return
          }

          if (isDisableDateInRange(_startDate, date, disabledDates)) {
            setStartDate(null)
            setEndDate(null)
            return
          }

          setEndDate(date)
          return
        }

        if (_endDate && _endDate < date) {
          setStartDate(null)
          setEndDate(null)
          return
        }

        if (isDisableDateInRange(date, _endDate, disabledDates)) {
          setStartDate(null)
          setEndDate(null)
          return
        }

        setSelectEndDate(true)
        setStartDate(date)
        return
      }

      setStartDate(date)
    }

    const handleCalendarKeyDown = (
      event: React.KeyboardEvent<HTMLDivElement>,
      date: Date,
      index?: number,
    ) => {
      if (event.code === 'Space' || event.key === 'Enter') {
        event.preventDefault()
        handleCalendarClick(date, index)
      }

      if (
        event.key === 'ArrowRight' ||
        event.key === 'ArrowLeft' ||
        event.key === 'ArrowUp' ||
        event.key === 'ArrowDown'
      ) {
        event.preventDefault()

        if (
          maxDate &&
          date >= convertToDateObject(maxDate, selectionType) &&
          (event.key === 'ArrowRight' || event.key === 'ArrowDown')
        ) {
          return
        }

        if (
          minDate &&
          date <= convertToDateObject(minDate, selectionType) &&
          (event.key === 'ArrowLeft' || event.key === 'ArrowUp')
        ) {
          return
        }

        let element = event.target as HTMLElement

        if (selectionType === 'week' && element.tabIndex === -1) {
          element = element.closest('tr[tabindex="0"]') as HTMLElement
        }

        const list: HTMLElement[] = [].concat(
          ...Element.prototype.querySelectorAll.call(
            calendarRef.current,
            selectionType === 'week' ? 'tr[tabindex="0"]' : 'td[tabindex="0"]',
          ),
        )

        const index = list.indexOf(element)
        const first = index === 0
        const last = index === list.length - 1

        const toBoundary = {
          start: index,
          end: list.length - (index + 1),
        }

        const gap = {
          ArrowRight: 1,
          ArrowLeft: -1,
          ArrowUp: selectionType === 'week' && view === 'days' ? -1 : view === 'days' ? -7 : -3,
          ArrowDown: selectionType === 'week' && view === 'days' ? 1 : view === 'days' ? 7 : 3,
        }

        if (
          (event.key === 'ArrowRight' && last) ||
          (event.key === 'ArrowDown' && toBoundary['end'] < gap['ArrowDown']) ||
          (event.key === 'ArrowLeft' && first) ||
          (event.key === 'ArrowUp' && toBoundary['start'] < Math.abs(gap['ArrowUp']))
        ) {
          if (view === 'days') {
            setCalendarPage(0, event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1)
          }

          if (view === 'months') {
            setCalendarPage(event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : -1)
          }

          if (view === 'years') {
            setCalendarPage(event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 10 : -10)
          }

          setTimeout(() => {
            const _list: HTMLElement[] = [].concat(
              ...Element.prototype.querySelectorAll.call(
                element.parentNode?.parentNode,
                'td[tabindex="0"], tr[tabindex="0"]',
              ),
            )

            if (_list.length > 0 && event.key === 'ArrowRight') {
              _list[0].focus()
            }

            if (_list.length > 0 && event.key === 'ArrowLeft') {
              _list[_list.length - 1].focus()
            }

            if (_list.length > 0 && event.key === 'ArrowDown') {
              _list[gap['ArrowDown'] - (list.length - index)].focus()
            }

            if (_list.length > 0 && event.key === 'ArrowUp') {
              _list[_list.length - (Math.abs(gap['ArrowUp']) + 1 - (index + 1))].focus()
            }
          }, 1)

          return
        }

        if (list[index + gap[event.key]].tabIndex === 0) {
          list[index + gap[event.key]].focus()
          return
        }

        for (
          let i = index;
          i < list.length;
          event.key === 'ArrowRight' || event.key === 'ArrowDown' ? i++ : i--
        ) {
          if (list[i + gap[event.key]].tabIndex === 0) {
            list[i + gap[event.key]].focus()
            break
          }
        }
      }
    }

    const handleCalendarMouseEnter = (date: Date) => {
      if (isDateDisabled(date, _minDate, _maxDate, disabledDates)) {
        return
      }

      setHoverDate(date)

      date && onDateHover && onDateHover(getDateBySelectionType(date, selectionType))
    }

    const handleCalendarMouseLeave = () => {
      setHoverDate(null)
      onDateHover && onDateHover(null)
    }

    const handleNavigationOnClick = (direction: string, double = false) => {
      if (direction === 'prev') {
        if (double) {
          setCalendarPage(view === 'years' ? -10 : -1)
          return
        }

        if (view !== 'days') {
          setCalendarPage(-1)
          return
        }

        setCalendarPage(0, -1)
        return
      }

      if (direction === 'next') {
        if (double) {
          setCalendarPage(view === 'years' ? 10 : 1)
          return
        }

        if (view !== 'days') {
          setCalendarPage(1)
          return
        }

        setCalendarPage(0, 1)
        return
      }
    }

    return (
      <div
        className={classNames(
          'calendars',
          {
            [`select-${selectionType}`]: selectionType && view === 'days',
            'show-week-numbers': showWeekNumber,
          },
          className,
        )}
        ref={forkedRef}
      >
        {_calendarDate &&
          Array.from({ length: calendars }, (_, index) => {
            const calendarDate = getCalendarDate(_calendarDate, index, view)
            return (
              <div className={classNames('calendar', view)} key={index}>
                <Navigation
                  ariaNavNextMonthLabel={ariaNavNextMonthLabel}
                  ariaNavNextYearLabel={ariaNavNextYearLabel}
                  ariaNavPrevMonthLabel={ariaNavPrevMonthLabel}
                  ariaNavPrevYearLabel={ariaNavPrevYearLabel}
                  calendarDate={calendarDate}
                  locale={locale}
                  navigation={navigation}
                  navNextDoubleIcon={navNextDoubleIcon}
                  navNextIcon={navNextIcon}
                  navPrevDoubleIcon={navPrevDoubleIcon}
                  navPrevIcon={navPrevIcon}
                  navYearFirst={navYearFirst}
                  onMonthClick={() => setView('months')}
                  onNavigationClick={handleNavigationOnClick}
                  onYearClick={() => setView('years')}
                  view={view}
                />
                <Calendar
                  calendarDate={calendarDate}
                  dayFormat={dayFormat}
                  disabledDates={disabledDates}
                  endDate={_endDate}
                  firstDayOfWeek={firstDayOfWeek}
                  hoverDate={_hoverDate}
                  locale={locale}
                  maxDate={_maxDate}
                  minDate={_minDate}
                  onCalendarClick={(date) => handleCalendarClick(date, index)}
                  onCalendarKeyDown={(event, date) => handleCalendarKeyDown(event, date, index)}
                  onCalendarMouseEnter={handleCalendarMouseEnter}
                  onCalendarMouseLeave={handleCalendarMouseLeave}
                  order={index}
                  selectAdjacementDays={selectAdjacementDays}
                  selectEndDate={_selectEndDate}
                  selectionType={selectionType}
                  showAdjacementDays={showAdjacementDays}
                  showWeekNumber={showWeekNumber}
                  startDate={_startDate}
                  view={view}
                  weekdayFormat={weekdayFormat}
                  weekNumbersLabel={weekNumbersLabel}
                />
              </div>
            )
          })}
      </div>
    )
  },
)

CCalendar.propTypes = {
  ariaNavNextMonthLabel: PropTypes.string,
  ariaNavNextYearLabel: PropTypes.string,
  ariaNavPrevMonthLabel: PropTypes.string,
  ariaNavPrevYearLabel: PropTypes.string,
  className: PropTypes.string,
  calendarDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  calendars: PropTypes.number,
  dayFormat: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.oneOf<'2-digit' | 'numeric'>(['2-digit', 'numeric']),
  ]),
  disabledDates: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.array, PropTypes.func]),
  endDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  firstDayOfWeek: PropTypes.number,
  locale: PropTypes.string,
  maxDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  minDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  navigation: PropTypes.bool,
  navNextIcon: PropTypes.node,
  navNextDoubleIcon: PropTypes.node,
  navPrevIcon: PropTypes.node,
  navPrevDoubleIcon: PropTypes.node,
  navYearFirst: PropTypes.bool,
  range: PropTypes.bool,
  selectAdjacementDays: PropTypes.bool,
  selectEndDate: PropTypes.bool,
  selectionType: PropTypes.oneOf(['day', 'week', 'month', 'year']),
  showAdjacementDays: PropTypes.bool,
  showWeekNumber: PropTypes.bool,
  startDate: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
  weekdayFormat: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.number,
    PropTypes.oneOf<'long' | 'narrow' | 'short'>(['long', 'narrow', 'short']),
  ]),
  weekNumbersLabel: PropTypes.string,
  onDateHover: PropTypes.func,
  onCalendarDateChange: PropTypes.func,
  onEndDateChange: PropTypes.func,
  onSelectEndChange: PropTypes.func,
  onStartDateChange: PropTypes.func,
  onViewChanged: PropTypes.func,
}

CCalendar.displayName = 'CCalendar'
