import React, { forwardRef, KeyboardEvent, useEffect, useState } from 'react'
import classNames from 'classnames'

import {
  convertToDateObject,
  createGroupsInArray,
  getMonthDetails,
  getMonthsNames,
  getYears,
  isDateDisabled,
  isDateInRange,
  isDateSelected,
  isMonthDisabled,
  isMonthInRange,
  isMonthSelected,
  isToday,
  isYearDisabled,
  isYearInRange,
  isYearSelected,
} from './utils'
import type { DisabledDate, SelectionTypes, ViewTypes } from './types'

export interface CCalendarPanelProps {
  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: SelectionTypes
  showAdjacementDays: boolean
  showWeekNumber: boolean
  startDate: Date | null
  view: ViewTypes
  weekdayFormat: number | 'long' | 'narrow' | 'short' | ((date: Date) => string | number)
  weekNumbersLabel: string | undefined
}

export const CCalendarPanel = forwardRef<HTMLTableElement, CCalendarPanelProps>(
  (
    {
      calendarDate,
      dayFormat,
      disabledDates,
      endDate,
      firstDayOfWeek,
      hoverDate,
      locale,
      maxDate,
      minDate,
      onCalendarClick,
      onCalendarKeyDown,
      onCalendarMouseEnter,
      onCalendarMouseLeave,
      selectAdjacementDays,
      selectEndDate,
      selectionType,
      showWeekNumber,
      showAdjacementDays,
      startDate,
      view,
      weekdayFormat,
      weekNumbersLabel,
    },
    ref
  ) => {
    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 ref={ref}>
        {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 = isMonthDisabled(date, minDate, maxDate, disabledDates)
                    const isSelected = isMonthSelected(date, startDate, endDate)
                    return (
                      <td
                        className={classNames('calendar-cell', {
                          disabled: isDisabled,
                          selected: isSelected,
                          'range-hover':
                            selectionType === 'month' &&
                            (hoverDate && selectEndDate
                              ? isMonthInRange(date, startDate, hoverDate)
                              : isMonthInRange(date, hoverDate, endDate)),
                          range: isMonthInRange(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 = isYearDisabled(date, minDate, maxDate, disabledDates)
                    const isSelected = isYearSelected(date, startDate, endDate)
                    return (
                      <td
                        className={classNames('calendar-cell year', {
                          disabled: isDisabled,
                          selected: isSelected,
                          'range-hover':
                            selectionType === 'year' &&
                            (hoverDate && selectEndDate
                              ? isYearInRange(date, startDate, hoverDate)
                              : isYearInRange(date, hoverDate, endDate)),
                          range: isYearInRange(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>
    )
  }
)

CCalendarPanel.displayName = 'CCalendarPanel'
