import startOfMonth from 'date-fns/fp/startOfMonth';
import endOfMonth from 'date-fns/fp/endOfMonth';
import startOfWeek from 'date-fns/fp/startOfWeek';
import endOfWeek from 'date-fns/fp/endOfWeek';
import eachWeekOfInterval from 'date-fns/fp/eachWeekOfInterval';
import eachDayOfInterval from 'date-fns/fp/eachDayOfInterval';
import addDays from 'date-fns/fp/addDays';
import addWeeks from 'date-fns/fp/addWeeks';
import subDays from 'date-fns/fp/subDays';
import getMonth from 'date-fns/fp/getMonth';
import getYear from 'date-fns/fp/getYear';
import isSameDay from 'date-fns/fp/isSameDay';
import isBefore from 'date-fns/fp/isBefore';
import isAfter from 'date-fns/fp/isAfter';
import eachYearOfInterval from 'date-fns/fp/eachYearOfInterval';

const MONTHS = [
  'Jan',
  'Feb',
  'Mar',
  'Apr',
  'May',
  'Jun',
  'Jul',
  'Aug',
  'Sep',
  'Oct',
  'Nov',
  'Dec',
];

const DAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];

const getMonthMatrix = ({
  month,
  year,
  minDate,
  maxDate,
}: {
  maxDate?: Date;
  minDate?: Date;
  month: number;
  year: number;
}): { date: Date; disabled: boolean }[][] => {
  const date = new Date(year, month);
  const weekStartDates = eachWeekOfInterval({
    start: startOfMonth(date),
    end: addWeeks(1, endOfMonth(date)),
  });

  const monthMatrix = weekStartDates
    .filter((_date, index) => index < 6)
    .map(weekStartDate =>
      eachDayOfInterval({
        start: startOfWeek(weekStartDate),
        end: endOfWeek(weekStartDate),
      }).map(dateOfWeek => ({
        date: dateOfWeek,
        disabled:
          getMonth(dateOfWeek) !== month ||
          (minDate !== undefined && isBefore(minDate, dateOfWeek)) ||
          (maxDate !== undefined && isAfter(maxDate, dateOfWeek)),
      }))
    );

  return monthMatrix;
};

const getSingleCalendarDayState = ({
  dateOfWeek,
  selectedDate,
}: {
  dateOfWeek: { date: Date; disabled: boolean };
  selectedDate?: Date;
}): 'enabled' | 'disabled' | 'selected' | 'today' => {
  if (dateOfWeek.disabled === true) {
    return 'disabled';
  }

  if (selectedDate !== undefined && isSameDay(dateOfWeek.date, selectedDate)) {
    return 'selected';
  }

  if (isSameDay(dateOfWeek.date, new Date())) {
    return 'today';
  }

  return 'enabled';
};

const getWeekCalendarState = ({
  dateOfWeek,
  selectedDate,
  tempDate,
}: {
  dateOfWeek: { date: Date; disabled: boolean };
  selectedDate?: Date;
  tempDate?: Date;
}): 'enabled' | 'disabled' | 'selected' | 'inHoveringWeek' => {
  if (selectedDate !== undefined) {
    const firstDateOfWeek = startOfWeek(selectedDate);
    if (isSameDay(firstDateOfWeek, dateOfWeek.date)) {
      return 'selected';
    }
  }
  if (tempDate !== undefined) {
    const firstDateOfWeek = startOfWeek(tempDate);
    const lastDateOfWeek = endOfWeek(tempDate);
    if (
      (isAfter(firstDateOfWeek, dateOfWeek.date) &&
        isBefore(lastDateOfWeek, dateOfWeek.date)) ||
      isSameDay(firstDateOfWeek, dateOfWeek.date) ||
      isSameDay(lastDateOfWeek, dateOfWeek.date)
    ) {
      return 'inHoveringWeek';
    }
  }

  if (dateOfWeek.disabled === true) {
    return 'disabled';
  }

  return 'enabled';
};

const getWeekCalendarDayState = ({
  dateOfWeek,
  selectedDate,
  tempDate,
}: {
  dateOfWeek: { date: Date; disabled: boolean };
  selectedDate?: Date;
  tempDate?: Date;
}):
  | 'enabled'
  | 'disabled'
  | 'today'
  | 'inHoveringWeekDate'
  | 'selectedWeekDate'
  | 'selectedFirstDateOfWeek'
  | 'selectedLastDateOfWeek'
  | 'inHoveringFirstDateOfWeek'
  | 'inHoveringLastDateOfWeek' => {
  if (selectedDate !== undefined) {
    const firstDateOfWeek = startOfWeek(selectedDate);
    const lastDateOfWeek = endOfWeek(selectedDate);
    if (isSameDay(firstDateOfWeek, dateOfWeek.date)) {
      return 'selectedFirstDateOfWeek';
    }

    if (isSameDay(lastDateOfWeek, dateOfWeek.date)) {
      return 'selectedLastDateOfWeek';
    }
    if (
      isAfter(firstDateOfWeek, dateOfWeek.date) &&
      isBefore(lastDateOfWeek, dateOfWeek.date)
    ) {
      return 'selectedWeekDate';
    }
  }
  if (tempDate !== undefined) {
    const firstDateOfWeek = startOfWeek(tempDate);
    const lastDateOfWeek = endOfWeek(tempDate);

    if (isSameDay(lastDateOfWeek, dateOfWeek.date)) {
      return 'inHoveringLastDateOfWeek';
    }
    if (
      isAfter(firstDateOfWeek, dateOfWeek.date) &&
      isBefore(lastDateOfWeek, dateOfWeek.date)
    ) {
      return 'inHoveringWeekDate';
    }
    if (isSameDay(firstDateOfWeek, dateOfWeek.date)) {
      return 'inHoveringFirstDateOfWeek';
    }
  }

  if (isSameDay(dateOfWeek.date, new Date())) {
    return 'today';
  }
  if (dateOfWeek.disabled === true) {
    return 'disabled';
  }

  return 'enabled';
};

const getDoubleCalendarDayState = ({
  dateOfWeek,
  isSettingStartDate,
  isSettingEndDate,
  dateClickCount,
  startDate,
  endDate,
  tempDate,
}: {
  dateClickCount: number;
  dateOfWeek: { date: Date; disabled: boolean };
  endDate?: Date;
  isSettingEndDate: boolean;
  isSettingStartDate: boolean;
  startDate?: Date;
  tempDate?: Date;
}):
  | 'enabled'
  | 'disabled'
  | 'selectedRangeDate'
  | 'selectedStartDate'
  | 'selectedEndDate'
  | 'today'
  | 'inRange'
  | 'inHoveringRange' => {
  if (
    dateOfWeek.disabled === true ||
    (startDate !== undefined &&
      isSettingEndDate === true &&
      dateClickCount === 1 &&
      isBefore(startDate, dateOfWeek.date)) ||
    (endDate !== undefined &&
      isSettingStartDate === true &&
      dateClickCount === 1 &&
      isAfter(endDate, dateOfWeek.date))
  ) {
    return 'disabled';
  }

  if (
    startDate !== undefined &&
    endDate !== undefined &&
    isSameDay(startDate, endDate) &&
    isSameDay(dateOfWeek.date, startDate)
  ) {
    return 'selectedRangeDate';
  }

  if (startDate !== undefined && isSameDay(dateOfWeek.date, startDate)) {
    return 'selectedStartDate';
  }

  if (endDate !== undefined && isSameDay(dateOfWeek.date, endDate)) {
    return 'selectedEndDate';
  }

  if (
    startDate !== undefined &&
    endDate !== undefined &&
    isAfter(startDate, dateOfWeek.date) &&
    isBefore(endDate, dateOfWeek.date)
  ) {
    return 'inRange';
  }

  if (
    (endDate !== undefined &&
      tempDate !== undefined &&
      isSettingStartDate === true &&
      isAfter(subDays(1, tempDate), dateOfWeek.date) &&
      isBefore(endDate, dateOfWeek.date)) ||
    (startDate !== undefined &&
      tempDate !== undefined &&
      isSettingEndDate === true &&
      isAfter(startDate, dateOfWeek.date) &&
      isBefore(addDays(1, tempDate), dateOfWeek.date))
  ) {
    return 'inHoveringRange';
  }

  if (isSameDay(dateOfWeek.date, new Date())) {
    return 'today';
  }

  return 'enabled';
};

const generateYearOptions = ({
  maxDate,
  minDate,
}: {
  maxDate: Date;
  minDate: Date;
}): { text: string; value: number }[] => {
  return eachYearOfInterval({ start: minDate, end: maxDate }).map(
    dateOfYear => {
      const year = getYear(dateOfYear);
      return {
        value: year,
        text: year.toString(),
      };
    }
  );
};

const generateMonthOptions = ({
  maxDate,
  minDate,
  year,
}: {
  maxDate: Date;
  minDate: Date;
  year: number;
}): { text: string; value: number }[] => {
  const monthOptions = MONTHS.map((month, index) => ({
    value: index,
    text: month,
  }));

  if (year > getYear(minDate) && year < getYear(maxDate)) return monthOptions;

  if (year <= getYear(minDate))
    return monthOptions.map(monthOpt =>
      monthOpt.value < getMonth(minDate)
        ? { ...monthOpt, disabled: true }
        : monthOpt
    );

  return monthOptions.map(monthOpt =>
    monthOpt.value > getMonth(maxDate)
      ? { ...monthOpt, disabled: true }
      : monthOpt
  );
};

const getMonthPickerOptions = ({
  maxDate,
  minDate,
  year,
}: {
  maxDate?: Date;
  minDate?: Date;
  year: number;
}): {
  date: Date;
  disabled: boolean;
  text: string;
}[] => {
  const result = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(monthIndex => {
    const monthStartDate = startOfMonth(new Date(year, monthIndex));
    const disabled =
      (minDate !== undefined && isBefore(minDate, monthStartDate)) ||
      (maxDate !== undefined && isAfter(maxDate, monthStartDate));
    const text = MONTHS[monthIndex];
    return {
      date: monthStartDate,
      disabled,
      text: text !== undefined ? text : '',
    };
  });

  return result;
};

export {
  MONTHS,
  DAYS,
  getMonthMatrix,
  getSingleCalendarDayState,
  getDoubleCalendarDayState,
  generateYearOptions,
  generateMonthOptions,
  getMonthPickerOptions,
  getWeekCalendarDayState,
  getWeekCalendarState,
};
