import { endOfDay, endOfMonth, getMonth, getYear, isAfter, isEqual, isWithinInterval, Locale, startOfDay } from 'date-fns';

import { isNil } from 'lodash';
import React, { useContext, useEffect, useState } from 'react';
import Calendar from './calendar';
import SettingsContext from '../../settings-context';

interface DateRangePickerProps {
  fromDate?: Date;
  toDate?: Date;
  focusMonth?: { month: number; year: number };
  locale?: Locale;
  duration?: number;
  disabledDaysFunction?: (date: Date) => boolean;
  extraClassNamesFunction?: (date: Date) => string[];
  dayContentFunction?: (date: Date) => JSX.Element | null | undefined;
  onSelectionChange?: (fromDate: Date, toDate: Date) => void;
  onFromDateChange?: (date?: Date) => void;
  onToDateChange?: (date?: Date) => void;
  onFocusMonthChange?: (focusMonth: { month: number; year: number }) => void;
  toDateByFromDate?: (fromDate?: Date) => Date | undefined;
}

const DateRangePicker: React.FC<DateRangePickerProps> = (props) => {
  const [fromDate, setFromDate] = useState<Date | undefined>(props.fromDate);
  const [toDate, setToDate] = useState<Date | undefined>(props.toDate);
  const [focusMonth, setFocusMonth] = useState<{ month: number; year: number }>(
    props.focusMonth ?? {
      year: getYear(new Date()),
      month: getMonth(new Date())
    }
  );
  const [isWaitingForToDate, setWaitingForToDate] = useState<boolean>(false);
  const { searchType = 0 } = useContext(SettingsContext);

  const handleDayClick = (day: Date) => {
    const { onSelectionChange } = props;

    if (isWaitingForToDate && !isNil(fromDate) && isAfter(day, fromDate)) {
      if (searchType === 1) {
        // Only allow selecting the exact toDate
        const expectedToDate = props.toDateByFromDate?.(fromDate);
        if (expectedToDate && isEqual(day, expectedToDate)) {
          setToDate(day);
          setWaitingForToDate(false);

          props.onToDateChange?.(undefined);
          onSelectionChange?.(fromDate, day);
        }
      } else {
        // searchType === 0 (original behavior)
        setToDate(day);
        setWaitingForToDate(false);

        props.onToDateChange?.(undefined);
        onSelectionChange?.(fromDate, day);
      }
    } else {
      setFromDate(day);

      if (searchType === 1 && props.toDateByFromDate) {
        const to = props.toDateByFromDate(day);
        setToDate(to);

        if (to) {
          onSelectionChange?.(day, to);
        }
      } else if (props.duration) {
        const to = new Date(Date.UTC(day.getFullYear(), day.getMonth(), day.getDate() + props.duration));
        setToDate(to);
        onSelectionChange?.(day, to);
      } else {
        setToDate(undefined);
        setWaitingForToDate(true);
      }

      props.onFromDateChange?.(day);
    }
  };

  const handleDayMouseOver = (day: Date) => {
    if (isWaitingForToDate && (isNil(fromDate) || isEqual(day, fromDate) || isAfter(day, fromDate))) {
      setToDate(day);
    }
  };

  const handlePreviousClick = () => {
    const { month, year } = focusMonth;

    const previousMonth = (month - 1) % 12;
    const previousMonthsYear = previousMonth > month ? year - 1 : year;

    const newFocusMonth = { year: previousMonthsYear, month: previousMonth };

    setFocusMonth(newFocusMonth);

    if (props.onFocusMonthChange) {
      props.onFocusMonthChange(newFocusMonth);
    }
  };

  const handleNextClick = () => {
    const { month, year } = focusMonth;

    const nextMonth = (month + 1) % 12;
    const nextMonthsYear = nextMonth < month ? year + 1 : year;

    const newFocusMonth = { year: nextMonthsYear, month: nextMonth };

    setFocusMonth(newFocusMonth);

    if (props.onFocusMonthChange) {
      props.onFocusMonthChange(newFocusMonth);
    }
  };

  const today = startOfDay(new Date());

  const firstCalendarYear = focusMonth.year;
  const firstCalendarMonth = focusMonth.month;

  const secondCalendarMonth = (firstCalendarMonth + 1) % 12;
  const secondCalendarYear = secondCalendarMonth < firstCalendarMonth ? firstCalendarYear + 1 : firstCalendarYear;

  const checkIfDateIsSelected = (date: Date) =>
    isNil(toDate)
      ? !isNil(fromDate) && isEqual(date, fromDate)
      : !isNil(fromDate) &&
        isWithinInterval(date, {
          start: startOfDay(fromDate),
          end: endOfDay(toDate)
        });

  useEffect(() => {
    setFromDate(props.fromDate);
    setToDate(props.toDate);
  }, [props.fromDate?.valueOf(), props.toDate?.valueOf()]);

  useEffect(() => {
    if (props.fromDate) {
      setFocusMonth({
        month: props.fromDate.getMonth(),
        year: props.fromDate.getFullYear()
      });
    }
  }, [props.fromDate?.valueOf()]);

  return (
    <div className="date-range-picker">
      <div className="date-range-picker__from">
        <Calendar
          year={firstCalendarYear}
          month={firstCalendarMonth}
          onDayClick={handleDayClick}
          onDayMouseOver={handleDayMouseOver}
          onPreviousClick={handlePreviousClick}
          hasPreviousButton={isAfter(new Date(firstCalendarYear, firstCalendarMonth), endOfMonth(today))}
          hasNextButton={false}
          selectedDaysFunction={checkIfDateIsSelected}
          disabledDaysFunction={props.disabledDaysFunction}
          extraClassNamesFunction={props.extraClassNamesFunction}
          dayContentFunction={props.dayContentFunction}
          hasFixedHeight={true}
        />
      </div>
      <div className="date-range-picker__to">
        <Calendar
          year={secondCalendarYear}
          month={secondCalendarMonth}
          onDayClick={handleDayClick}
          onDayMouseOver={handleDayMouseOver}
          onNextClick={handleNextClick}
          hasPreviousButton={false}
          selectedDaysFunction={checkIfDateIsSelected}
          disabledDaysFunction={props.disabledDaysFunction}
          extraClassNamesFunction={props.extraClassNamesFunction}
          dayContentFunction={props.dayContentFunction}
          hasFixedHeight={true}
        />
      </div>
    </div>
  );
};

export default DateRangePicker;
