import {
  addWeeks,
  eachDayOfInterval,
  endOfMonth,
  endOfWeek,
  format,
  getISODay,
  getISOWeek,
  getMonth,
  getYear,
  isSameMonth,
  startOfMonth,
  startOfWeek,
} from "date-fns";
import { chunk, isFunction, range } from "lodash";
import React, { useContext } from "react";
import { buildClassName } from "../../../shared/utils/class-util";
import { getLocale } from "../../../shared/utils/localization-util";
import SettingsContext from "../../settings-context";
import CalendarDay from "./calendar-day";
import Icon from "../icon";

interface CalendarProps {
  year?: number;
  month?: number;
  hasPreviousButton?: boolean;
  hasNextButton?: boolean;
  hasFixedHeight?: boolean;
  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
  disabledDaysFunction?: (date: Date) => boolean;
  selectedDaysFunction?: (date: Date) => boolean;
  extraClassNamesFunction?: (date: Date) => string[];
  dayContentFunction?: (date: Date) => JSX.Element | null | undefined;
  onDayClick?: (date: Date) => void;
  onDayMouseOver?: (date: Date) => void;
  onNextClick?: (year: number, month: number) => void;
  onPreviousClick?: (year: number, month: number) => void;
}

const Calendar: React.FC<CalendarProps> = ({
  year = getYear(new Date()),
  month = getMonth(new Date()),
  hasPreviousButton = true,
  hasNextButton = true,
  hasFixedHeight = true,
  weekStartsOn = 1,
  disabledDaysFunction,
  selectedDaysFunction,
  extraClassNamesFunction,
  dayContentFunction,
  onDayClick,
  onDayMouseOver,
  onNextClick,
  onPreviousClick,
}) => {
  const {
    language,
  } = useContext(SettingsContext);
  
  const locale = getLocale(language);
  const handleDayClick = (day: Date) => {
    if (onDayClick) {
      onDayClick(day);
    }
  };

  const handleDayMouseOver = (day: Date) => {
    if (onDayMouseOver) {
      onDayMouseOver(day);
    }
  };

  const handleNextClick = () => {
    const nextMonth = (month + 1) % 12;
    const nextMonthsYear = nextMonth < month ? year + 1 : year;

    if (onNextClick) {
      onNextClick(nextMonthsYear, nextMonth);
    }
  };

  const handlePreviousClick = () => {
    const previousMonth = (month - 1) % 12;
    const previousMonthsYear = previousMonth > month ? year - 1 : year;

    if (onPreviousClick) {
      onPreviousClick(previousMonthsYear, previousMonth);
    }
  };

  const focusDate = new Date(year, month);
  const firstDay = startOfWeek(startOfMonth(focusDate), { weekStartsOn });
  const lastDay = hasFixedHeight
    ? endOfWeek(addWeeks(firstDay, 5), { weekStartsOn })
    : endOfWeek(endOfMonth(focusDate), { weekStartsOn });
  const calendarDays = eachDayOfInterval({ start: firstDay, end: lastDay });

  const mapDay = (day: Date) => {
    const isoDay = getISODay(day);
    const isDisabled = isFunction(disabledDaysFunction) && disabledDaysFunction(day);
    const isSelected = isFunction(selectedDaysFunction) && selectedDaysFunction(day);
    const isOutsideMonth = !isSameMonth(day, focusDate);
    const extraClassNames = isFunction(extraClassNamesFunction)
      ? extraClassNamesFunction(day)
      : undefined;

    return (
      <CalendarDay
        key={`day_${isoDay}`}
        day={day}
        isSelected={isSelected}
        isDisabled={isDisabled}
        isOutsideMonth={isOutsideMonth}
        extraClassNames={extraClassNames}
        onClick={handleDayClick}
        onMouseOver={handleDayMouseOver}
      >
        {isFunction(dayContentFunction) ? dayContentFunction(day) : undefined}
      </CalendarDay>
    );
  };

  const mapWeek = (weekDays: Date[]) => {
    if (weekDays.length === 0) {
      return null;
    }

    const isoWeek = getISOWeek(weekDays[0]);

    return (
      <div className="calendar__week" key={`week_${isoWeek}`}>
        {weekDays.map(mapDay)}
      </div>
    );
  };

  return (
    <div className="calendar">
      <div className="calendar__header">
        <div className="calendar__pager">
          <div
            className={buildClassName([
              "calendar__previous",
              !hasPreviousButton && "calendar__previous--disabled",
            ])}
            onClick={handlePreviousClick}
          >
            <Icon name="ui-chevron" />
          </div>
          <div className="calendar__current-month">
            {format(focusDate, "MMMM yyyy", { locale })}
          </div>
          <div
            className={buildClassName([
              "calendar__next",
              !hasNextButton && "calendar__next--disabled",
            ])}
            onClick={handleNextClick}
          >
            <Icon name="ui-chevron" />
          </div>
        </div>
        <div className="calendar__day-labels">
          {range(0, 7).map((i) => (
            <div className="calendar__day-label" key={`day_${i}`}>
              {format(calendarDays[i], "EEEEEE", { locale: locale })}
            </div>
          ))}
        </div>
      </div>
      <div className="calendar__body">{chunk(calendarDays, 7).map(mapWeek)}</div>
    </div>
  );
};

export default Calendar;
