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 '../../../shared/components/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;
