import { useState, useCallback, useEffect, KeyboardEvent } from 'react';

import { MonthFormat } from '../common';
import { isWithinRange, moveToWithinRange, returnDateView } from '../common/dateUtils';
import DayCalendar from './dayCalendar';
import { getStartOfDay } from './getStartOfDay';
import MonthCalendar from './monthCalendar';
import YearCalendar from './yearCalendar';

export interface CalendarProps {
  value: Date | null;
  min?: Date | null;
  max?: Date | null;
  monthFormat?: `${MonthFormat}`;
  placeholder?: string;
  onChange: (date: Date | null) => void;
  onSelect?: (date: Date) => void;
}

const Calendar = ({
  value,
  min: minProp = null,
  max: maxProp = null,
  monthFormat = MonthFormat.LONG,
  placeholder,
  onChange,
  onSelect,
}: CalendarProps) => {
  const selectedDate = getStartOfDay(value);
  const min = getStartOfDay(minProp);
  const max = getStartOfDay(maxProp);

  useEffect(() => {
    if (selectedDate && !isWithinRange(selectedDate, min, max)) {
      onChange(moveToWithinRange(selectedDate, min, max));
    }
  }, [selectedDate?.getTime(), min?.getTime(), max?.getTime(), onChange]); // eslint-disable-line react-hooks/exhaustive-deps

  const dateView = returnDateView(selectedDate, min, max);
  const [viewMonth, setViewMonth] = useState(dateView.getMonth());
  const [viewYear, setViewYear] = useState(dateView.getFullYear());
  const [mode, setMode] = useState<'day' | 'month' | 'year'>('day');

  useEffect(() => {
    const view = returnDateView(selectedDate, min, max);
    // eslint-disable-next-line react-hooks/set-state-in-effect
    setViewMonth(view.getMonth());

    setViewYear(view.getFullYear());
  }, [selectedDate?.getTime(), min?.getTime(), max?.getTime()]); // eslint-disable-line react-hooks/exhaustive-deps

  const handleViewDateUpdate = useCallback(
    // eslint-disable-next-line react-hooks/preserve-manual-memoization
    ({ month, year }: { month?: number; year?: number }) => {
      if (month !== undefined) setViewMonth(month);
      if (year !== undefined) setViewYear(year);
    },
    [],
  );

  const switchToDays = useCallback(() => setMode('day'), []);
  const switchToMonths = useCallback(() => setMode('month'), []);
  const switchToYears = useCallback(() => setMode('year'), []);

  const handleSelectedDateUpdate = useCallback(
    (date: Date) => {
      if (onSelect) {
        onSelect(date);
      } else {
        onChange(date);
      }
    },
    [onChange, onSelect],
  );

  const adjustDate = useCallback(
    (daysToAdd: number, monthsToAdd: number, yearsToAdd: number) => {
      let date: Date | null;
      if (selectedDate) {
        date = new Date(
          mode === 'year' ? selectedDate.getFullYear() + yearsToAdd : selectedDate.getFullYear(),
          mode === 'month' ? selectedDate.getMonth() + monthsToAdd : selectedDate.getMonth(),
          mode === 'day' ? selectedDate.getDate() + daysToAdd : selectedDate.getDate(),
        );
      } else {
        date = getStartOfDay(new Date());
      }
      date &&= moveToWithinRange(date, min, max);
      if (date?.getTime() !== selectedDate?.getTime()) {
        onChange(date);
      }
    },
    [selectedDate, min, max, mode, onChange],
  );

  const handleKeyDown = useCallback(
    (event: KeyboardEvent<HTMLDivElement>) => {
      switch (event.key) {
        case 'ArrowLeft':
          adjustDate(-1, -1, -1);
          event.preventDefault();
          break;
        case 'ArrowUp':
          adjustDate(-7, -4, -4);
          event.preventDefault();
          break;
        case 'ArrowRight':
          adjustDate(1, 1, 1);
          event.preventDefault();
          break;
        case 'ArrowDown':
          adjustDate(7, 4, 4);
          event.preventDefault();
          break;
        default:
          break;
      }
    },
    [adjustDate],
  );

  return (
    // eslint-disable-next-line jsx-a11y/no-static-element-interactions
    <div onKeyDown={handleKeyDown}>
      {mode === 'day' && (
        <DayCalendar
          selectedDate={selectedDate}
          min={min}
          max={max}
          viewMonth={viewMonth}
          viewYear={viewYear}
          monthFormat={monthFormat}
          onSelect={handleSelectedDateUpdate}
          onLabelClick={switchToYears}
          onViewDateUpdate={handleViewDateUpdate}
        />
      )}
      {mode === 'month' && (
        <MonthCalendar
          selectedDate={selectedDate}
          min={min}
          max={max}
          viewYear={viewYear}
          {...(placeholder != null && { placeholder })}
          onSelect={switchToDays}
          onLabelClick={switchToYears}
          onViewDateUpdate={handleViewDateUpdate}
        />
      )}
      {mode === 'year' && (
        <YearCalendar
          selectedDate={selectedDate}
          min={min}
          max={max}
          viewYear={viewYear}
          {...(placeholder != null && { placeholder })}
          onSelect={switchToMonths}
          onViewDateUpdate={handleViewDateUpdate}
        />
      )}
    </div>
  );
};

export default Calendar;
