import React, {
  ReactElement,
  MouseEvent,
  useState,
  useCallback,
  useEffect,
} from 'react';
import getDate from 'date-fns/fp/getDate';
import getMonth from 'date-fns/fp/getMonth';
import getYear from 'date-fns/fp/getYear';
import addYears from 'date-fns/fp/addYears';
import subYears from 'date-fns/fp/subYears';
import setMonth from 'date-fns/fp/setMonth';
import setYear from 'date-fns/fp/setYear';

import Select from '../../Select';

import {
  Container,
  Navigation,
  CalendarWrapper,
  CalendarRow,
  DayWrapper,
  Day,
} from './StyledCalendar';

import {
  DAYS,
  getMonthMatrix,
  getSingleCalendarDayState,
  generateYearOptions,
  generateMonthOptions,
} from './utils';

import DoubleCalendar from './DoubleCalendar';
import MonthCalendar from './MonthCalendar';
import WeekCalendar from './WeekCalendar';

interface CalendarProps {
  maxDate?: Date;
  minDate?: Date;
  onSelectDate?: (date: Date) => void;
  selectedDate?: Date;
}

const Calendar = ({
  selectedDate,
  onSelectDate,
  minDate,
  maxDate,
}: CalendarProps): ReactElement => {
  const currentDate = new Date();
  const initialDate = selectedDate !== undefined ? selectedDate : currentDate;
  const [date, setDate] = useState<Date>(initialDate);
  const month = getMonth(date);
  const year = getYear(date);

  const caldendarMinDate =
    minDate !== undefined ? minDate : subYears(100, currentDate);

  const calendarMaxDate =
    maxDate !== undefined ? maxDate : addYears(100, currentDate);

  const yearOptions = generateYearOptions({
    minDate: caldendarMinDate,
    maxDate: calendarMaxDate,
  });

  const monthOptions = generateMonthOptions({
    minDate: caldendarMinDate,
    maxDate: calendarMaxDate,
    year,
  });

  const onChangeMonth = useCallback(
    newMonth => {
      setDate(setMonth(newMonth, date));
    },
    [date, setDate]
  );

  const onChangeYear = useCallback(
    newYear => {
      setDate(setYear(newYear, date));
    },
    [date, setDate]
  );

  useEffect(() => {
    setDate(selectedDate !== undefined ? selectedDate : new Date());
  }, [selectedDate, setDate]);

  const monthMatrix = getMonthMatrix({ month, year, minDate, maxDate });

  return (
    <Container>
      <Navigation>
        <Select
          options={monthOptions}
          value={month}
          onChange={onChangeMonth}
          data-test-id="month-select"
        />
        <Select
          options={yearOptions}
          value={year}
          onChange={onChangeYear}
          data-test-id="year-select"
        />
      </Navigation>
      <CalendarWrapper>
        <CalendarRow>
          {DAYS.map(day => (
            <DayWrapper key={day}>
              <Day themeState="dayLabel">{day}</Day>
            </DayWrapper>
          ))}
        </CalendarRow>
        {monthMatrix.map((week, weekNumber) => (
          // eslint-disable-next-line react/no-array-index-key
          <CalendarRow key={weekNumber}>
            {week.map(dateOfWeek => {
              const day = getDate(dateOfWeek.date);
              const onDateClick = (e: MouseEvent, clickedDate: Date): void => {
                if (onSelectDate !== undefined) {
                  onSelectDate(clickedDate);
                }
                e.preventDefault();
              };
              const dateState = getSingleCalendarDayState({
                dateOfWeek,
                selectedDate,
              });
              return (
                // eslint-disable-next-line react/no-array-index-key
                <DayWrapper key={`${weekNumber}-${day}`}>
                  <Day
                    themeState={dateState}
                    onClick={
                      dateState !== 'disabled'
                        ? (e): void => onDateClick(e, dateOfWeek.date)
                        : undefined
                    }
                  >
                    {day}
                  </Day>
                </DayWrapper>
              );
            })}
          </CalendarRow>
        ))}
      </CalendarWrapper>
    </Container>
  );
};

Calendar.Double = DoubleCalendar;
Calendar.Month = MonthCalendar;
Calendar.Week = WeekCalendar;

export default Calendar;
