import moment from 'moment'
import * as React from 'react'
import { DayPicker, useDayPicker } from 'react-day-picker'

import styles from './_calendar.module.scss'
import Button from '../Button/Button'
import Icon from '../Icons/Icon'
import { c } from '../../translations/LibraryTranslationService'
import { type CalendarProps } from './DatePicker.models'

function Calendar({
  className,
  showOutsideDays = false,
  showYearSwitcher = true,
  yearRange = 12,
  numberOfMonths,
  ...props
}: CalendarProps) {
  const [navView, setNavView] = React.useState<'days' | 'months' | 'years'>(
    'days',
  )

  const [selectedYear, setSelectedYear] = React.useState(
    new Date().getFullYear(),
  )
  const [displayYears, setDisplayYears] = React.useState<{
    from: number
    to: number
  }>(
    React.useMemo(() => {
      return {
        from: selectedYear - Math.floor(yearRange / 2 - 1),
        to: selectedYear + Math.ceil(yearRange / 2),
      }
    }, [selectedYear, yearRange]),
  )
  const {
    onNextClick,
    onPrevClick,
    startMonth,
    endMonth,
    mode,
    selected,
    dateRestrictions,
  } = props
  const columnsDisplayed =
    navView === 'days' && mode === 'range' ? numberOfMonths : 1

  const [isTodaySelected, setIsTodaySelected] = React.useState(false)
  const [isOnlyStartSelected, setIsOnlyStartSelected] = React.useState(false)
  React.useEffect(() => {
    if (mode === 'range' && selected) {
      const range = selected as { from: Date; to: Date | undefined }
      const onlyStartSelected = Boolean(
        range.from && range.to && range.from.getTime() === range.to.getTime(),
      )
      setIsOnlyStartSelected(onlyStartSelected)
    } else {
      setIsOnlyStartSelected(false)
    }
    const today = new Date()
    let isToday = false
    if (mode === 'single' && selected) {
      isToday =
        selected.getDate() === today.getDate() &&
        selected.getMonth() === today.getMonth() &&
        selected.getFullYear() === today.getFullYear()
    }
    setIsTodaySelected(isToday)
  }, [mode, selected])

  function isYearSelected(year: number): string | false {
    if (mode === 'single' && selected) {
      return selected.getFullYear() === year ? 'selectedYear' : false
    } else if (mode === 'range' && selected) {
      const range = selected as { from: Date; to: Date | undefined }
      if (range.from && range.to) {
        const startYear = range.from.getFullYear()
        const endYear = range.to.getFullYear()
        if (startYear === endYear && year === startYear) {
          return 'sameMonthYear'
        }
        if (year === startYear) return 'rangeStartYear'
        if (year === endYear) return 'rangeEndYear'
        if (year > startYear && year < endYear) return 'rangeMiddleYear'
      } else if (range.from) {
        return range.from.getFullYear() === year ? 'rangeStartYear' : false
      }
    }
    return false
  }

  function isMonthSelected(month: number, year: number): string | false {
    if (mode === 'single' && selected) {
      return selected.getMonth() === month && selected.getFullYear() === year
        ? 'selectedMonth'
        : false
    } else if (mode === 'range' && selected) {
      const range = selected as { from: Date; to: Date | undefined }
      if (range.from && range.to) {
        const startDate = range.from
        const endDate = range.to
        const currentDate = new Date(year, month, 1)

        // Check if both dates are in the same month and year
        if (
          startDate.getFullYear() === endDate.getFullYear() &&
          startDate.getMonth() === endDate.getMonth() &&
          year === startDate.getFullYear() &&
          month === startDate.getMonth()
        ) {
          return 'sameMonthMonth'
        }

        if (
          year === startDate.getFullYear() &&
          month === startDate.getMonth()
        ) {
          return 'rangeStartMonth'
        }
        if (year === endDate.getFullYear() && month === endDate.getMonth()) {
          return 'rangeEndMonth'
        }
        if (currentDate > startDate && currentDate < endDate) {
          return 'rangeMiddleMonth'
        }
      } else if (range.from) {
        return range.from.getMonth() === month &&
          range.from.getFullYear() === year
          ? 'rangeStartMonth'
          : false
      }
    }
    return false
  }

  function isMonthRestricted(month: number, year: number) {
    const monthEnd = new Date(year, month + 1, 0)

    // Check if all dates in the month are restricted
    const allDatesRestricted = Array.from(
      { length: monthEnd.getDate() },
      (_, i) => {
        const date = new Date(year, month, i + 1)
        const isBeforeRestricted =
          dateRestrictions?.beforeDate && date < dateRestrictions.beforeDate
        const isAfterRestricted =
          dateRestrictions?.afterDate && date > dateRestrictions.afterDate
        return isBeforeRestricted || isAfterRestricted
      },
    ).every((isRestricted) => isRestricted)

    return allDatesRestricted
  }

  function isYearRestricted(year: number) {
    // Check if all months in the year are restricted
    const allMonthsRestricted = Array.from({ length: 12 }, (_, month) => {
      return isMonthRestricted(month, year)
    }).every((isRestricted) => isRestricted)

    return allMonthsRestricted
  }

  const disabled = React.useMemo(
    () => [
      ...(dateRestrictions?.beforeDate
        ? [{ before: dateRestrictions.beforeDate }]
        : []),
      ...(dateRestrictions?.afterDate
        ? [{ after: dateRestrictions.afterDate }]
        : []),
    ],
    [dateRestrictions?.beforeDate, dateRestrictions?.afterDate],
  )

  const formatters = React.useMemo(
    () => ({
      formatWeekdayName: (weekday: Date) => {
        const shortName = weekday
          .toLocaleDateString('en', { weekday: 'short' })
          .toLowerCase()
          .slice(0, 3)
        return c(shortName)
      },
      formatMonthCaption: (month: Date) => {
        // Always use full month name for the main calendar header
        const monthName = month
          .toLocaleDateString('en', { month: 'long' })
          .toLowerCase()

        return `${c(monthName)} ${month.getFullYear()}`
      },
    }),
    [],
  )

  /**
   * Generate dynamic CSS for selected, disabled single date
   *
   * Why we use dangerouslySetInnerHTML instead of pure CSS:
   * - CSS cannot dynamically match data attribute values (e.g., data-day="X" where X is from JS state)
   * - CSS is declarative/static and cannot read JavaScript state about which date is selected
   * - This approach is performant (one small style tag) and works with CSS modules
   *
   * Safety: The HTML content is completely controlled by us (no user input), making it safe to use.
   */
  const selectedDisabledStyle = React.useMemo(() => {
    if (mode === 'single' && selected) {
      const singleDate = selected as Date

      // Check if the selected date is disabled by date restrictions
      const isDisabled = disabled.some((disabledRule) => {
        if (disabledRule.before && singleDate < disabledRule.before) return true
        if (disabledRule.after && singleDate > disabledRule.after) return true
        return false
      })

      if (isDisabled) {
        // Format date as YYYY-MM-DD to match react-day-picker's data-day attribute
        const year = singleDate.getFullYear()
        const month = String(singleDate.getMonth() + 1).padStart(2, '0')
        const day = String(singleDate.getDate()).padStart(2, '0')
        const dataDay = `${year}-${month}-${day}`

        // Generate CSS that targets the specific disabled date and applies selected styling
        return {
          __html: `
            .${styles.calendar} [data-day="${dataDay}"] .${styles.dayButton} {
              background-color: var(--dark-blue) !important;
              color: var(--white) !important;
              cursor: default;
            }
          `,
        }
      }
    }
    return null
  }, [selected, mode, disabled])

  return (
    <>
      {/*
        Inject dynamic CSS for selected disabled dates
        This ensures users can see that their date selection was registered
        even when the date is outside the allowed restrictions
      */}
      {selectedDisabledStyle && (
        <style dangerouslySetInnerHTML={selectedDisabledStyle} />
      )}
      <DayPicker
        disabled={disabled}
        showOutsideDays={showOutsideDays}
        className={`${styles.calendar} ${mode === 'range' ? styles.rangeCalendar : ''} ${className || ''}`}
        formatters={formatters}
        classNames={{
          months: `${styles.months} ${mode === 'range' && navView === 'days' ? styles.twoMonthsGap : ''}`,
          month_caption: styles.monthCaption,
          weekdays: styles.weekdays,
          weekday: styles.weekday,
          month: styles.month,
          caption_label: styles.captionLabel,
          button_next: `${styles.buttonNav} ${styles.buttonNext}`,
          button_previous: `${styles.buttonNav} ${styles.buttonPrevious}`,
          month_grid: `${styles.monthGrid} ${mode === 'range' ? styles.rangeMonthGrid : ''}`,
          week: styles.week,
          day: styles.day,
          day_button: styles.dayButton,
          selected: styles.selected,
          today: `${isTodaySelected ? styles.todaySelected : styles.today}`,
          outside: styles.outside,
          disabled: styles.disabled,
          // Styles for range mode
          range_start: styles.rangeStart,
          range_middle: styles.rangeMiddle,
          range_end: `${isOnlyStartSelected ? styles.onlyStartSelected : styles.rangeEnd}`,
          footer: `${navView !== 'days' ? styles.hideFooter : ''} `,
        }}
        components={{
          Nav: () => {
            const { nextMonth, previousMonth, goToMonth } = useDayPicker()

            const isPreviousDisabled = (() => {
              if (navView === 'years') {
                return (
                  (startMonth &&
                    moment(new Date(displayYears.from - 1, 0, 1)).isBefore(
                      moment(startMonth),
                      'day',
                    )) ||
                  (endMonth &&
                    moment(new Date(displayYears.from - 1, 0, 1)).isAfter(
                      moment(endMonth),
                      'day',
                    ))
                )
              }
              return !previousMonth
            })()

            const isNextDisabled = (() => {
              if (navView === 'years') {
                return (
                  (startMonth &&
                    moment(new Date(displayYears.to + 1, 0, 1)).isBefore(
                      moment(startMonth),
                      'day',
                    )) ||
                  (endMonth &&
                    moment(new Date(displayYears.to + 1, 0, 1)).isAfter(
                      moment(endMonth),
                      'day',
                    ))
                )
              }
              return !nextMonth
            })()
            const handlePreviousClick = React.useCallback(() => {
              if (navView === 'years') {
                setDisplayYears((prev) => ({
                  from: prev.from - (prev.to - prev.from + 1),
                  to: prev.to - (prev.to - prev.from + 1),
                }))
                onPrevClick?.(
                  new Date(
                    displayYears.from - (displayYears.to - displayYears.from),
                    0,
                    1,
                  ),
                )
                return
              }

              if (navView === 'months') {
                setSelectedYear((prevYear) => prevYear - 1)
                return
              }

              if (!previousMonth) return
              goToMonth(previousMonth)
              onPrevClick?.(previousMonth)
            }, [previousMonth, goToMonth])

            const handleNextClick = React.useCallback(() => {
              if (navView === 'years') {
                setDisplayYears((prev) => ({
                  from: prev.from + (prev.to - prev.from + 1),
                  to: prev.to + (prev.to - prev.from + 1),
                }))
                onNextClick?.(
                  new Date(
                    displayYears.from + (displayYears.to - displayYears.from),
                    0,
                    1,
                  ),
                )
                return
              }

              if (navView === 'months') {
                setSelectedYear((prevYear) => prevYear + 1)
                return
              }

              if (!nextMonth) return
              goToMonth(nextMonth)
              onNextClick?.(nextMonth)
            }, [goToMonth, nextMonth])
            return (
              <nav>
                <Button
                  className={`${styles.buttonPrevious} ${mode === 'range' ? styles.buttonPreviousRange : ''} ${styles.buttonNav}`}
                  tabIndex={isPreviousDisabled ? undefined : -1}
                  disabled={isPreviousDisabled}
                  onClick={handlePreviousClick}
                >
                  <Icon icon='caretLeft'></Icon>
                </Button>
                <Button
                  className={`${styles.buttonNext} ${styles.buttonNav} `}
                  tabIndex={isNextDisabled ? undefined : -1}
                  disabled={isNextDisabled}
                  onClick={handleNextClick}
                >
                  <Icon icon='caretRight'></Icon>
                </Button>
              </nav>
            )
          },
          CaptionLabel: ({ children, ...props }) => {
            if (!showYearSwitcher) return <span {...props}>{children}</span>
            return (
              <Button
                className={styles.captionLabel}
                onClick={() =>
                  setNavView((prev) =>
                    prev === 'days'
                      ? 'months'
                      : prev === 'months'
                        ? 'years'
                        : 'days',
                  )
                }
              >
                {navView === 'days'
                  ? children
                  : navView === 'months'
                    ? selectedYear
                    : displayYears.from + ' - ' + displayYears.to}
              </Button>
            )
          },
          MonthGrid: ({ className, children, ...props }) => {
            const { goToMonth } = useDayPicker()
            if (navView === 'years') {
              return (
                <div
                  className={`${styles.monthGrid} ${className || ''}`}
                  {...props}
                >
                  {Array.from(
                    { length: displayYears.to - displayYears.from + 1 },
                    (_, i) => {
                      const year = displayYears.from + i
                      const isToday = year === new Date().getFullYear()
                      const selectedClass = isYearSelected(year)
                      const isDisabled =
                        (startMonth &&
                          moment(new Date(year, 11, 31)).isBefore(
                            moment(startMonth),
                            'day',
                          )) ||
                        (endMonth &&
                          moment(new Date(year, 0, 1)).isAfter(
                            moment(endMonth),
                            'day',
                          ))
                      return (
                        <Button
                          key={i}
                          className={`${isToday ? styles.today : ''} ${
                            selectedClass ? styles[selectedClass] : ''
                          } ${isYearRestricted(year) ? styles.restrictedYear : ''}`}
                          onClick={() => {
                            if (!isYearRestricted(year)) {
                              setSelectedYear(year)
                              setNavView('months')
                            }
                          }}
                          disabled={
                            navView === 'years' ? isDisabled : undefined
                          }
                        >
                          {year}
                        </Button>
                      )
                    },
                  )}
                </div>
              )
            } else if (navView === 'months') {
              return (
                <div
                  className={`${styles.monthGrid} ${className || ''}`}
                  {...props}
                >
                  {Array.from({ length: 12 }, (_, i) => {
                    const isToday =
                      i === new Date().getMonth() &&
                      selectedYear === new Date().getFullYear()
                    const selectedClass = isMonthSelected(i, selectedYear)
                    return (
                      <Button
                        key={i}
                        className={`${isToday ? styles.today : ''} ${
                          selectedClass ? styles[selectedClass] : ''
                        } ${isMonthRestricted(i, selectedYear) ? styles.restrictedMonth : ''}`}
                        onClick={() => {
                          if (!isMonthRestricted(i, selectedYear)) {
                            setNavView('days')
                            goToMonth(new Date(selectedYear, i))
                          }
                        }}
                      >
                        {c(
                          new Date(0, i)
                            .toLocaleString('default', { month: 'short' })
                            .toLowerCase(),
                        )}
                      </Button>
                    )
                  })}
                </div>
              )
            }
            return <table {...props}>{children}</table>
          },
        }}
        numberOfMonths={columnsDisplayed}
        {...props}
      />
    </>
  )
}
Calendar.displayName = 'Calendar'

export { Calendar }
