import clsx from "clsx";
import {
  addMinutes,
  differenceInMinutes,
  format,
  getDay,
  parse,
  startOfWeek,
} from "date-fns";
import React from "react";
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
import { useMount } from "react-use";

import { usePreferences } from "@/components/preferences/use-preferences";

import DateNavigationToolbar from "./date-navigation-toolbar";
import { DateTimeOption, DateTimePickerProps } from "./types";
import { formatDateWithoutTime, formatDateWithoutTz } from "./utils";

const WeekCalendar: React.VoidFunctionComponent<DateTimePickerProps> = ({
  title,
  options,
  onNavigate,
  date,
  onChange,
  duration,
  onChangeDuration,
}) => {
  const [scrollToTime, setScrollToTime] = React.useState<Date>();

  useMount(() => {
    // Bit of a hack to force rbc to scroll to the right time when we close/open a modal
    setScrollToTime(addMinutes(date, -60));
  });

  const { weekStartsOn, timeFormat, locale } = usePreferences();

  const localizer = React.useMemo(
    () =>
      dateFnsLocalizer({
        format,
        parse,
        startOfWeek: (date: Date | number) =>
          startOfWeek(date, {
            weekStartsOn: weekStartsOn === "monday" ? 1 : 0,
          }),
        getDay,
        locales: {
          default: locale,
        },
      }),
    [locale, weekStartsOn],
  );

  return (
    <Calendar
      key={timeFormat}
      events={options.map((option) => {
        if (option.type === "date") {
          return { title, start: new Date(option.date) };
        } else {
          return {
            title,
            start: new Date(option.start),
            end: new Date(option.end),
          };
        }
      })}
      culture="default"
      onNavigate={onNavigate}
      date={date}
      className="h-[calc(100vh-220px)] max-h-[800px] min-h-[400px] w-full"
      defaultView="week"
      views={["week"]}
      selectable={true}
      localizer={localizer}
      onSelectEvent={(event) => {
        onChange(
          options.filter(
            (option) =>
              !(
                option.type === "timeSlot" &&
                option.start === formatDateWithoutTz(event.start) &&
                event.end &&
                option.end === formatDateWithoutTz(event.end)
              ),
          ),
        );
      }}
      components={{
        toolbar: (props) => {
          return (
            <DateNavigationToolbar
              year={props.date.getFullYear()}
              label={props.label}
              onPrevious={() => {
                props.onNavigate("PREV");
              }}
              onToday={() => {
                props.onNavigate("TODAY");
              }}
              onNext={() => {
                props.onNavigate("NEXT");
              }}
            />
          );
        },
        eventWrapper: (props) => {
          return (
            <div
              // onClick prop doesn't work properly. Seems like some other element is cancelling the event before it reaches this element
              onMouseUp={props.onClick}
              className="absolute ml-1 max-h-full overflow-hidden rounded-md bg-green-100 bg-opacity-80 p-1 text-xs text-green-500 transition-colors hover:bg-opacity-50"
              style={{
                top: `calc(${props.style?.top}% + 4px)`,
                height: `calc(${props.style?.height}% - 8px)`,
                left: `${props.style?.xOffset}%`,
                width: `calc(${props.style?.width}%)`,
              }}
            >
              <div>{format(props.event.start, "p", { locale })}</div>
              <div className="w-full truncate font-bold">
                {props.event.title}
              </div>
            </div>
          );
        },
        week: {
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          header: ({ date }: any) => {
            const dateString = formatDateWithoutTime(date);
            const selectedOption = options.find((option) => {
              return option.type === "date" && option.date === dateString;
            });
            return (
              <span
                onClick={() => {
                  if (!selectedOption) {
                    onChange([
                      ...options,
                      {
                        type: "date",
                        date: formatDateWithoutTime(date),
                      },
                    ]);
                  } else {
                    onChange(
                      options.filter((option) => option !== selectedOption),
                    );
                  }
                }}
                className={clsx(
                  "inline-flex w-full items-center justify-center rounded-md py-2 text-sm hover:bg-slate-50 hover:text-gray-700",
                  {
                    "bg-green-50 text-green-600 hover:bg-green-50 hover:bg-opacity-75 hover:text-green-600":
                      !!selectedOption,
                  },
                )}
              >
                <span className="mr-1 font-normal opacity-50">
                  {format(date, "E")}
                </span>
                <span className="font-medium">{format(date, "dd")}</span>
              </span>
            );
          },
        },
        timeSlotWrapper: ({ children }) => {
          return <div className="h-12 text-xs text-gray-500">{children}</div>;
        },
      }}
      step={15}
      onSelectSlot={({ start, end, action }) => {
        // on select slot
        const startDate = new Date(start);
        const endDate = new Date(end);

        const newEvent: DateTimeOption = {
          type: "timeSlot",
          start: formatDateWithoutTz(startDate),
          end: formatDateWithoutTz(endDate),
        };

        if (action === "select") {
          const diff = differenceInMinutes(endDate, startDate);
          if (diff < 60 * 24) {
            onChangeDuration(diff);
          }
        } else {
          newEvent.end = formatDateWithoutTz(addMinutes(startDate, duration));
        }

        const alreadyExists = options.some(
          (option) =>
            option.type === "timeSlot" &&
            option.start === newEvent.start &&
            option.end === newEvent.end,
        );

        if (!alreadyExists) {
          onChange([...options, newEvent]);
        }
      }}
      scrollToTime={scrollToTime}
    />
  );
};

export default WeekCalendar;