import * as React from "react";
import classNames from "classnames";
import {
  AriaCalendarProps,
  AriaRangeCalendarProps,
  useCalendar,
  useCalendarCell,
  useCalendarGrid,
  useLocale,
  useRangeCalendar,
} from "react-aria";
import {
  CalendarState,
  RangeCalendarState,
  useCalendarState,
  useRangeCalendarState,
} from "react-stately";

import { ChevronLeft } from "@faulty/gdq-icons/icons/ChevronLeft";
import { ChevronRight } from "@faulty/gdq-icons/icons/ChevronRight";
import {
  CalendarDate,
  createCalendar,
  DateValue,
  getWeeksInMonth,
  isSameDay,
} from "@internationalized/date";

import { Clickable } from "../Clickable/Clickable";
import { Interactive } from "../Interactive/Interactive";
import { Stack } from "../Stack/Stack";
import { Header } from "../Text/Text";

import styles from "./Calendar.module.css";

export interface CalendarProps<T extends DateValue> extends AriaCalendarProps<T> {
  className?: string;
}

export function Calendar<T extends DateValue>(props: CalendarProps<T>) {
  const { className } = props;

  let { locale } = useLocale();
  let state = useCalendarState({
    ...props,
    locale,
    createCalendar,
  });

  let { calendarProps, prevButtonProps, nextButtonProps, title } = useCalendar(props, state);

  return (
    <Stack {...calendarProps} className={classNames(styles.calendar, className)} spacing="space-md">
      <Stack direction="horizontal" justify="space-between" align="center">
        <Interactive>
          <Clickable {...prevButtonProps}>
            <ChevronLeft size={32} />
          </Clickable>
        </Interactive>
        <Header tag="h2" variant="header-sm/normal">
          {title}
        </Header>
        <Interactive>
          <Clickable {...nextButtonProps}>
            <ChevronRight size={32} />
          </Clickable>
        </Interactive>
      </Stack>
      <CalendarGrid state={state} />
    </Stack>
  );
}

export interface RangeCalendarProps<T extends DateValue> extends AriaRangeCalendarProps<T> {}

export function RangeCalendar<T extends DateValue>(props: RangeCalendarProps<T>) {
  let { locale } = useLocale();
  let state = useRangeCalendarState({
    ...props,
    locale,
    createCalendar,
  });

  let ref = React.useRef<HTMLDivElement>(null);
  let { calendarProps, prevButtonProps, nextButtonProps, title } = useRangeCalendar(
    props,
    state,
    ref,
  );

  return (
    <Stack {...calendarProps} ref={ref} className={styles.calendar} spacing="space-md">
      <Stack direction="horizontal" justify="space-between" align="center">
        <Interactive>
          <Clickable {...prevButtonProps}>
            <ChevronLeft size={32} />
          </Clickable>
        </Interactive>
        <Header tag="h2" variant="header-sm/normal">
          {title}
        </Header>
        <Interactive>
          <Clickable {...nextButtonProps}>
            <ChevronRight size={32} />
          </Clickable>
        </Interactive>
      </Stack>
      <CalendarGrid state={state} />
    </Stack>
  );
}

interface CalendarGridProps {
  state: CalendarState | RangeCalendarState;
}

function CalendarGrid(props: CalendarGridProps) {
  const { state } = props;
  let { locale } = useLocale();
  let { gridProps, headerProps, weekDays } = useCalendarGrid({}, state);

  // Get the number of weeks in the month so we can render the proper number of rows.
  let weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale);

  return (
    <table {...gridProps} className={styles.grid}>
      <thead {...headerProps}>
        <tr>
          {weekDays.map((day, index) => (
            <th key={index} className={styles.weekday}>
              {day}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {[...new Array(weeksInMonth).keys()].map((weekIndex) => (
          <tr key={weekIndex}>
            {state
              .getDatesInWeek(weekIndex)
              .map((date, i) =>
                date ? <CalendarCell key={i} state={state} date={date} /> : <td key={i} />,
              )}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

interface CalendarCellProps {
  state: CalendarState | RangeCalendarState;
  date: CalendarDate;
}

function CalendarCell(props: CalendarCellProps) {
  const { state, date } = props;

  let ref = React.useRef<HTMLDivElement>(null);
  let {
    cellProps,
    buttonProps,
    isSelected,
    isOutsideVisibleRange,
    isDisabled,
    isUnavailable,
    formattedDate,
  } = useCalendarCell({ date }, state, ref);

  const isRangeCalendar = "highlightedRange" in state;

  const isStart = isRangeCalendar ? isSameDay(date, state.highlightedRange.start) : false;
  const isEnd = isRangeCalendar ? isSameDay(date, state.highlightedRange.end) : false;
  const isWithinRange = isRangeCalendar
    ? date.compare(state.highlightedRange.start) >= 0 &&
      date.compare(state.highlightedRange.end) <= 0
    : false;

  return (
    <td {...cellProps}>
      <div
        {...buttonProps}
        ref={ref}
        hidden={isOutsideVisibleRange}
        className={classNames(styles.cell, {
          [styles.selected]: isSelected || isWithinRange,
          [styles.disabled]: isDisabled,
          [styles.unavailable]: isUnavailable,
          [styles.range]: isRangeCalendar,
          [styles.rangeStart]: isStart,
          [styles.rangeEnd]: isEnd,
        })}>
        {formattedDate}
      </div>
    </td>
  );
}
