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

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

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

type SelectMonthAndYearProps = {
  currentDate?: Date;
  month: number;
  onChangeMonth: (month?: string | number) => void;
  onChangeYear: (year?: string | number) => void;
  year: number;
};
export const SelectMonthAndYear = ({
  currentDate = new Date(),
  year,
  month,
  onChangeMonth,
  onChangeYear,
}: SelectMonthAndYearProps): ReactElement => {
  const caldendarMinDate = subYears(100, currentDate);

  const calendarMaxDate = addYears(100, currentDate);

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

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

  return (
    <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>
  );
};

export const FirstRowOfWeekCalendar = () => {
  return (
    <CalendarRow>
      <WeekWrapper>
        <Day themeState="dayLabel">Week</Day>
      </WeekWrapper>
      <Divider variant="vertical" marginX="xsmall" />
      {DAYS.map(day => (
        <DayWrapper key={day}>
          <Day themeState="dayLabel">{day}</Day>
        </DayWrapper>
      ))}
    </CalendarRow>
  );
};

type WeekComponentProps = {
  firstDateState: WeekState;
  onMouseOver: () => void;
  showedWeekNumber: number;
};
export const WeekComponent = ({
  onMouseOver,
  firstDateState,
  showedWeekNumber,
}: WeekComponentProps): ReactElement => {
  return (
    <WeekWrapper>
      <Week
        onMouseOver={onMouseOver}
        onFocus={onMouseOver}
        themeState={firstDateState}
      >
        {showedWeekNumber}
      </Week>
    </WeekWrapper>
  );
};

type DayComponent = {
  dateState: DayState;
  day: number;
  onMouseOver: () => void;
};
export const DayComponent = ({
  day,
  dateState,
  onMouseOver,
}: DayComponent): ReactElement => {
  return (
    <DayWrapper
      themeState={dateState}
      onMouseOver={onMouseOver}
      onFocus={onMouseOver}
    >
      <Day themeState={dateState}>{day}</Day>
    </DayWrapper>
  );
};

type DayProps = {
  date: Date;
  disabled: boolean;
};
type WeekProps = DayProps[];
type CalendarComponentProps = {
  monthMatrix: WeekProps[];
  onSelectFirstDateOfWeek?: (date: Date) => void;
  selectedFirstDateOfWeek?: Date;
  setTempDate: (date: Date) => void;
  tempDate?: Date;
};
const CalendarMatrix = ({
  monthMatrix,
  onSelectFirstDateOfWeek,
  selectedFirstDateOfWeek,
  setTempDate,
  tempDate,
}: CalendarComponentProps): ReactElement => {
  return (
    <>
      {monthMatrix.map((week: WeekProps, weekNumber: number) => {
        const firstDate = week[0] !== undefined ? week[0].date : new Date();
        const onDateClick = (e: MouseEvent): void => {
          if (onSelectFirstDateOfWeek !== undefined) {
            onSelectFirstDateOfWeek(firstDate);
          }
          e.preventDefault();
        };

        const onMouseOverDay = (hoveredDate: Date): void => {
          setTempDate(hoveredDate);
        };

        const firstDateObject = {
          date: week[0] !== undefined ? week[0].date : new Date(),
          disabled: week[0] !== undefined ? week[0]?.disabled : false,
        };
        const showedWeekNumber = getWeek(firstDate);
        const firstDateState = getWeekCalendarState({
          dateOfWeek: firstDateObject,
          selectedDate: selectedFirstDateOfWeek,
          tempDate,
        });

        return (
          // eslint-disable-next-line react/no-array-index-key
          <CalendarRow key={weekNumber} onClick={e => onDateClick(e)}>
            <WeekComponent
              onMouseOver={(): void => onMouseOverDay(firstDate)}
              firstDateState={firstDateState}
              showedWeekNumber={showedWeekNumber}
            />
            <Divider variant="vertical" marginX="xsmall" />
            {week.map(dateOfWeek => {
              const day = getDate(dateOfWeek.date);
              const dateState = getWeekCalendarDayState({
                dateOfWeek,
                selectedDate: selectedFirstDateOfWeek,
                tempDate,
              });

              return (
                <DayComponent
                  // eslint-disable-next-line react/no-array-index-key
                  key={`${weekNumber}-${day}`}
                  day={day}
                  dateState={dateState}
                  onMouseOver={(): void => onMouseOverDay(dateOfWeek.date)}
                />
              );
            })}
          </CalendarRow>
        );
      })}
    </>
  );
};

export interface WeekCalendarProps {
  onSelectFirstDateOfWeek?: (date: Date) => void;
  selectedFirstDateOfWeek?: Date;
}
const WeekCalendar = ({
  onSelectFirstDateOfWeek,
  selectedFirstDateOfWeek,
}: WeekCalendarProps): ReactElement => {
  const currentDate = new Date();
  const initialDate =
    selectedFirstDateOfWeek !== undefined
      ? selectedFirstDateOfWeek
      : currentDate;
  const [date, setDate] = useState<Date>(initialDate);
  const [tempDate, setTempDate] = useState<Date>();
  const month = getMonth(date);
  const year = getYear(date);

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

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

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

  const monthMatrix = getMonthMatrix({ month, year });
  return (
    <Container>
      <SelectMonthAndYear
        currentDate={currentDate}
        year={year}
        month={month}
        onChangeMonth={onChangeMonth}
        onChangeYear={onChangeYear}
      />
      <CalendarWrapper>
        <FirstRowOfWeekCalendar />
        <CalendarMatrix
          monthMatrix={monthMatrix}
          onSelectFirstDateOfWeek={onSelectFirstDateOfWeek}
          selectedFirstDateOfWeek={selectedFirstDateOfWeek}
          setTempDate={setTempDate}
          tempDate={tempDate}
        />
      </CalendarWrapper>
    </Container>
  );
};

export default WeekCalendar;
