import React, { ReactElement, MouseEvent, useState, useCallback } from 'react';
import getDate from 'date-fns/fp/getDate';
import getMonth from 'date-fns/fp/getMonth';
import getYear from 'date-fns/fp/getYear';
import addMonths from 'date-fns/fp/addMonths';
import addYears from 'date-fns/fp/addYears';
import subMonths from 'date-fns/fp/subMonths';
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 Divider from '../../Divider';

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

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

export interface DoubleCalendarProps {
  dateClickCount: number;
  endDate?: Date;
  isSettingEndDate: boolean;
  isSettingStartDate: boolean;
  maxDate?: Date;
  minDate?: Date;
  onSelectDate?: (date: Date) => void;
  startDate?: Date;
}

const DoubleCalendar = ({
  startDate,
  endDate,
  minDate,
  maxDate,
  onSelectDate,
  isSettingStartDate,
  isSettingEndDate,
  dateClickCount,
}: DoubleCalendarProps): ReactElement => {
  const currentDate = new Date();
  const initialDate = startDate !== undefined ? startDate : currentDate;
  const [date, setDate] = useState<Date>(initialDate);
  const [tempDate, setTempDate] = useState<Date>();
  const month = getMonth(date);
  const year = getYear(date);
  const dateInNextMonth = addMonths(1, date);
  const nextMonth = getMonth(dateInNextMonth);
  const nextYear = getYear(dateInNextMonth);

  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 nextMonthOptions = generateMonthOptions({
    minDate: caldendarMinDate,
    maxDate: calendarMaxDate,
    year: nextYear,
  });

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

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

  const onChangeNextMonth = useCallback(
    newMonth => {
      setDate(subMonths(1, setMonth(newMonth, dateInNextMonth)));
    },
    [dateInNextMonth, setDate]
  );

  const onChangeNextYear = useCallback(
    newYear => {
      setDate(subMonths(1, setYear(newYear, dateInNextMonth)));
    },
    [dateInNextMonth, setDate]
  );

  const onMouseOverDay = useCallback(
    (hoveredDate: Date): void => {
      setTempDate(hoveredDate);
    },
    [setTempDate]
  );

  const onDateClick = useCallback(
    (e: MouseEvent<HTMLSpanElement>, clickedDate: Date): void => {
      if (onSelectDate !== undefined) {
        onSelectDate(clickedDate);
      }
      e.preventDefault();
    },
    [onSelectDate]
  );

  const renderCalendar = useCallback(
    (monthMatrix: { date: Date; disabled: boolean }[][]): ReactElement => (
      <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 dayState = getDoubleCalendarDayState({
                dateOfWeek,
                isSettingStartDate,
                isSettingEndDate,
                dateClickCount,
                startDate,
                endDate,
                tempDate,
              });
              return (
                // eslint-disable-next-line react/no-array-index-key
                <DayWrapper key={`${weekNumber}-${day}`} themeState={dayState}>
                  <Day
                    themeState={dayState}
                    onClick={
                      dayState !== 'disabled'
                        ? (e): void => onDateClick(e, dateOfWeek.date)
                        : undefined
                    }
                    onMouseOver={(): void => onMouseOverDay(dateOfWeek.date)}
                    onFocus={(): void => onMouseOverDay(dateOfWeek.date)}
                  >
                    {day}
                  </Day>
                </DayWrapper>
              );
            })}
          </CalendarRow>
        ))}
      </CalendarWrapper>
    ),
    [
      isSettingStartDate,
      isSettingEndDate,
      dateClickCount,
      startDate,
      endDate,
      tempDate,
      onDateClick,
      onMouseOverDay,
    ]
  );

  return (
    <Container>
      <DoubleCalendarContainer>
        <CalendarContainer>
          <Navigation>
            <Select
              options={monthOptions}
              value={month}
              onChange={onChangeMonth}
              data-test-id="first-month-select"
            />
            <Select
              options={yearOptions}
              value={year}
              onChange={onChangeYear}
              data-test-id="first-year-select"
            />
          </Navigation>
          {renderCalendar(getMonthMatrix({ month, year, minDate, maxDate }))}
        </CalendarContainer>
        <Divider marginX="small" />
        <CalendarContainer>
          <Navigation>
            <Select
              options={nextMonthOptions}
              value={nextMonth}
              onChange={onChangeNextMonth}
              data-test-id="second-month-select"
            />
            <Select
              options={yearOptions}
              value={nextYear}
              onChange={onChangeNextYear}
              data-test-id="second-year-select"
            />
          </Navigation>
          {renderCalendar(
            getMonthMatrix({
              month: nextMonth,
              year: nextYear,
              minDate,
              maxDate,
            })
          )}
        </CalendarContainer>
      </DoubleCalendarContainer>
    </Container>
  );
};

export default DoubleCalendar;
