import { setYear } from "date-fns";
import { Matcher, isMatch } from "./is-match";

export const nextEnabled = (
  months: Date[],
  key: string,
  disabled: Matcher[],
  currentMonth: Date,
  setYearState: (date: Date) => void,
  yearState: Date,
  dropdownCaption: boolean,
  fromDate?: Date,
  toDate?: Date,
): Date => {
  const currentIndex = currentMonth.getMonth();

  if (key === "Home") {
    const nextDate = nextOnRow(
      currentIndex,
      months,
      yearState,
      disabled,
      "home",
    );
    if (nextDate) {
      return nextDate;
    }
  }
  if (key === "End") {
    const nextDate = nextOnRow(
      currentIndex,
      months,
      yearState,
      disabled,
      "end",
    );
    if (nextDate) {
      return nextDate;
    }
  }
  if (key === "PageUp") {
    if (
      !dropdownCaption ||
      (fromDate && yearState.getFullYear() - 1 >= fromDate?.getFullYear())
    ) {
      setYearState(setYear(yearState, Number(yearState.getFullYear() - 1)));
    }
  }
  if (key === "PageDown") {
    if (
      !dropdownCaption ||
      (toDate && yearState.getFullYear() + 1 <= toDate?.getFullYear())
    ) {
      setYearState(setYear(yearState, Number(yearState.getFullYear() + 1)));
    }
  }
  if (key === "ArrowRight") {
    const nextMonth = loopForward(
      currentIndex,
      months,
      yearState,
      setYearState,
      disabled,
      false,
      dropdownCaption,
      fromDate,
      toDate,
    );
    if (nextMonth) {
      return setYear(months[nextMonth.index], nextMonth.year);
    }
  }
  if (key === "ArrowLeft") {
    const prevMonth = loopBack(
      currentIndex - 1,
      months,
      disabled,
      yearState,
      setYearState,
      false,
      dropdownCaption,
      fromDate,
      toDate,
    );
    if (prevMonth) {
      return setYear(months[prevMonth.index], prevMonth.year);
    }
  }
  if (key === "ArrowDown") {
    if (
      months[currentIndex + 4] &&
      !isMatch(
        setYear(months[currentIndex + 4], yearState.getFullYear()),
        disabled,
      )
    ) {
      return setYear(months[currentIndex + 4], yearState.getFullYear());
    }

    const fallbackNext = loopForward(
      currentIndex,
      months,
      yearState,
      setYearState,
      disabled,
      true,
      dropdownCaption,
      fromDate,
      toDate,
    );

    if (
      fallbackNext &&
      getRow(fallbackNext.index) !== getRow(currentIndex + 8)
    ) {
      return setYear(months[fallbackNext.index], fallbackNext.year);
    }
  }

  if (key === "ArrowUp") {
    if (
      months[currentIndex - 4] &&
      !isMatch(
        setYear(months[currentIndex - 4], Number(yearState.getFullYear())),
        disabled,
      )
    )
      return setYear(months[currentIndex - 4], Number(yearState.getFullYear()));
    const fallbackPrev = loopBack(
      currentIndex,
      months,
      disabled,
      yearState,
      setYearState,
      true,
      dropdownCaption,
      fromDate,
      toDate,
    );
    if (fallbackPrev)
      return setYear(months[fallbackPrev.index], fallbackPrev.year);
  }
  return currentMonth;
};

const loopBack = (
  currentIndex: number,
  months: Date[],
  disabled: Matcher[],
  yearState: Date,
  setYearState: (date: Date) => void,
  rowCheck: boolean,
  dropdownCaption: boolean,
  fromDate?: Date,
  toDate?: Date,
): { index: number; year: number } | undefined => {
  let currentYear = setYear(yearState, Number(yearState.getFullYear()));
  for (let i = currentIndex; i >= -1; i--) {
    if (i === -1) {
      if (
        isOutOfRange(
          dropdownCaption,
          setYear(currentYear, Number(currentYear.getFullYear() - 1)),
          fromDate,
          toDate,
        )
      )
        return;
      currentYear = setYear(currentYear, Number(currentYear.getFullYear() - 1));
      setYearState(currentYear);
      i = 11;
    }
    const month = months[i];
    const isDisabled = !isMatch(
      setYear(month, Number(currentYear.getFullYear())),
      disabled,
    );
    if (rowCheck) {
      if (isDisabled && getRow(i) !== getRow(currentIndex)) {
        return { index: i, year: Number(currentYear.getFullYear()) };
      }
    } else {
      if (isDisabled) {
        return { index: i, year: Number(currentYear.getFullYear()) };
      }
    }
  }
};

const loopForward = (
  currentIndex: number,
  months: Date[],
  yearState: Date,
  setYearState: (date: Date) => void,
  disabled: Matcher[],
  rowCheck: boolean,
  dropdownCaption: boolean,
  fromDate?: Date,
  toDate?: Date,
): { index: number; year: number } | undefined => {
  let currentYear = setYear(yearState, Number(yearState.getFullYear()));
  for (let i = currentIndex + 1; i < months.length + 1; i++) {
    if (i === 12) {
      if (
        isOutOfRange(
          dropdownCaption,
          setYear(currentYear, Number(currentYear.getFullYear() + 1)),
          fromDate,
          toDate,
        )
      )
        return;
      currentYear = setYear(currentYear, Number(currentYear.getFullYear() + 1));
      setYearState(currentYear);
      i = 0;
    }
    const month = months[i];
    const isDisabled = !isMatch(
      setYear(month, Number(currentYear.getFullYear())),
      disabled,
    );
    if (rowCheck) {
      if (isDisabled && getRow(i) !== getRow(currentIndex)) {
        return { index: i, year: Number(currentYear.getFullYear()) };
      }
    } else {
      if (isDisabled) {
        return { index: i, year: Number(currentYear.getFullYear()) };
      }
    }
  }
};

const getRow = (index: number): number => {
  if (index >= 0 && index <= 3) return 1;
  if (index >= 4 && index <= 7) return 2;
  return 3;
};

const isOutOfRange = (
  dropdownCaption: boolean,
  year: Date,
  fromDate?: Date,
  toDate?: Date,
): boolean => {
  if (
    dropdownCaption &&
    fromDate &&
    toDate &&
    (year.getFullYear() < fromDate?.getFullYear() ||
      year.getFullYear() > toDate?.getFullYear())
  ) {
    return true;
  }

  return false;
};

const nextOnRow = (
  currentIndex: number,
  months: Date[],
  yearState: Date,
  disabled: Matcher[],
  mode: "home" | "end",
) => {
  const row = getRow(currentIndex);
  let monthsOfRow: Date[] = [];

  switch (row) {
    case 1:
      monthsOfRow = months.slice(0, 4);
      break;
    case 2:
      monthsOfRow = months.slice(4, 8);
      break;
    case 3:
      monthsOfRow = months.slice(8, 12);
      break;
    default:
      break;
  }

  if (mode === "end") monthsOfRow = monthsOfRow.reverse();

  for (let i = 0; i < monthsOfRow.length; i++) {
    const month = monthsOfRow[i];

    if (!isMatch(setYear(month, Number(yearState.getFullYear())), disabled)) {
      return setYear(month, Number(yearState.getFullYear()));
    }
  }
};
