import React, {
  ReactNode,
  useEffect,
  useRef,
  useState,
  forwardRef,
} from "react";
import classNames from "classnames";
import ReactDatePicker, { ReactDatePickerProps } from "react-datepicker";
import { addMonths, format, isEqual, isValid, startOfMonth } from "date-fns";
import { Input, INPUT_ICON_POSITION } from "../Input";
import { bemHOF } from "../../utilities/bem";
import { PopoverMenu } from "../PopoverMenu";
import { Text } from "../Text";
import { Box, BoxProps } from "../Box";
import { Heading } from "../Heading";
import { Flex } from "../Flex";
import { Button, BUTTON_VARIANT } from "../Button";
import { ICON_TYPE } from "../Icon";

const cn = bemHOF("DatePicker");

const MONTH_YEAR_FORMAT = "MMMM yyyy";

export const DatePickerInput = forwardRef((props, ref) => (
  <Input
    icon={ICON_TYPE.CALENDAR}
    iconPosition={INPUT_ICON_POSITION.RIGHT}
    ref={ref}
    {...props}
  />
));

const renderDayContents = (day: number) => {
  return (
    <Text as="span" className={cn({ e: "day-text" })}>
      {day}
    </Text>
  );
};

interface CustomHeaderProps {
  date: Date;
  decreaseMonth: () => void;
  increaseMonth: () => void;
  prevMonthButtonDisabled: boolean;
  nextMonthButtonDisabled: boolean;
}

type HeaderPropsState = CustomHeaderProps | null;

const CustomHeaderButtons = ({
  decreaseMonth,
  increaseMonth,
  prevMonthButtonDisabled,
  nextMonthButtonDisabled,
}: Omit<CustomHeaderProps, "date">) => (
  <Flex className={cn({ e: "month-btns-wrapper" })}>
    <Button
      icon={ICON_TYPE.CHEVRON_LEFT}
      variant={BUTTON_VARIANT.SECONDARY}
      onClick={decreaseMonth}
      disabled={prevMonthButtonDisabled}
    />
    <Button
      icon={ICON_TYPE.CHEVRON_RIGHT}
      variant={BUTTON_VARIANT.SECONDARY}
      onClick={increaseMonth}
      disabled={nextMonthButtonDisabled}
    />
  </Flex>
);

const CustomHeader = ({
  date,
  decreaseMonth,
  increaseMonth,
  prevMonthButtonDisabled,
  nextMonthButtonDisabled,
  monthsShown,
  setHeaderProps,
}: CustomHeaderProps & {
  monthsShown: number;
  setHeaderProps: (p: CustomHeaderProps) => void;
}) => {
  useEffect(() => {
    if (monthsShown > 1) {
      setHeaderProps({
        date,
        decreaseMonth,
        increaseMonth,
        prevMonthButtonDisabled,
        nextMonthButtonDisabled,
      });
    }
  }, [
    monthsShown,
    date,
    decreaseMonth,
    increaseMonth,
    prevMonthButtonDisabled,
    nextMonthButtonDisabled,
    setHeaderProps,
  ]);

  if (monthsShown > 1) {
    return null;
  }

  return (
    <Flex className={cn({ e: "header" })}>
      <Heading.H4 className={cn({ e: "heading" })}>
        {format(date, MONTH_YEAR_FORMAT)}
      </Heading.H4>
      <CustomHeaderButtons
        decreaseMonth={decreaseMonth}
        prevMonthButtonDisabled={prevMonthButtonDisabled}
        increaseMonth={increaseMonth}
        nextMonthButtonDisabled={nextMonthButtonDisabled}
      />
    </Flex>
  );
};

const generateCustomHeaderRenderer = ({
  monthsShown,
  setHeaderProps,
}: {
  monthsShown: number;
  setHeaderProps: (p: CustomHeaderProps) => void;
}) => {
  return (customHeaderProps: CustomHeaderProps) => {
    return (
      <CustomHeader
        {...customHeaderProps}
        monthsShown={monthsShown}
        setHeaderProps={setHeaderProps}
      />
    );
  };
};

const checkHeaderPropsEquality = (
  prev: HeaderPropsState,
  next: CustomHeaderProps,
) => {
  if (!prev) return false;
  if (!isEqual(prev.date, next.date)) return false;
  if (prev.prevMonthButtonDisabled !== next.prevMonthButtonDisabled)
    return false;
  if (prev.nextMonthButtonDisabled !== next.nextMonthButtonDisabled)
    return false;
  return true;
};

const DAY_ABBREVIATIONS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
const DayNames = () => (
  <Box className="react-datepicker__day-names">
    {DAY_ABBREVIATIONS.map((abv) => (
      <Box key={abv} className="react-datepicker__day-name">
        {abv}
      </Box>
    ))}
  </Box>
);

const MultiMonthHeader = ({
  headerProps,
  monthsShown,
  inline,
  calInstance,
}: {
  headerProps: HeaderPropsState;
  monthsShown: number;
  inline: boolean;
  calInstance?: ReactDatePicker | null;
}) => {
  if (!headerProps) return null;
  const { date, ...restHeaderProps } = headerProps;
  const dateStartOfMonth = startOfMonth(headerProps.date);

  const getMonthSelectedIn = () => {
    if (!calInstance) return 0;
    const { state } = calInstance;
    if (
      state.hasOwnProperty("monthSelectedIn") &&
      typeof (state as any).monthSelectedIn === "number"
    ) {
      return (state as any).monthSelectedIn as number;
    }
    return 0;
  };

  const monthSelectedIn = getMonthSelectedIn();

  return (
    <Flex className="pt-2">
      {[...Array(monthsShown)].map((_, i) => {
        const dateThisMonth = addMonths(dateStartOfMonth, i - monthSelectedIn);
        const displayDate = format(dateThisMonth, MONTH_YEAR_FORMAT);
        const showButtons = !inline && monthsShown === i + 1;
        return (
          <Box key={displayDate} className={cn({ e: "multi-month-header" })}>
            <Flex className="items-center justify-between">
              <Heading.H4 className={cn({ e: "heading", m: "multi-month" })}>
                {displayDate}
              </Heading.H4>
              {showButtons && <CustomHeaderButtons {...restHeaderProps} />}
            </Flex>
            <DayNames />
          </Box>
        );
      })}
    </Flex>
  );
};

const getPopoverMenuWithMultiMonthHeader = ({
  headerProps,
  monthsShown,
  inline,
  calInstance,
}: {
  headerProps: HeaderPropsState;
  monthsShown: number;
  inline: boolean;
  calInstance: ReactDatePicker | null;
}) => {
  return ({
    children,
    className,
  }: {
    children: ReactNode;
    className?: string;
  }) => {
    return (
      <PopoverMenu className={className}>
        <MultiMonthHeader
          headerProps={headerProps}
          monthsShown={monthsShown}
          inline={inline}
          calInstance={calInstance}
        />
        {children}
      </PopoverMenu>
    );
  };
};

const getCalendarContainer = ({
  inline,
  monthsShown,
  headerProps,
  calInstance,
}: {
  inline: boolean;
  monthsShown: number;
  headerProps: HeaderPropsState;
  calInstance: ReactDatePicker | null;
}) => {
  if (inline) return undefined;
  if (monthsShown > 1)
    return getPopoverMenuWithMultiMonthHeader({
      headerProps,
      monthsShown,
      inline,
      calInstance,
    });
  return ({ children, className }: BoxProps) => (
    <PopoverMenu className={className}>{children}</PopoverMenu>
  );
};

const UnwrappedDatePicker = ({
  className,
  monthsShown = 1,
  calendarClassName,
  customInput = <DatePickerInput />,
  onHeaderPropsUpdate,
  inline = false,
  popperModifiers,
  headerProps,
  rdpRef,
  ...rest
}: ReactDatePickerProps & {
  onHeaderPropsUpdate: (props: CustomHeaderProps) => void;
  headerProps: HeaderPropsState;
  rdpRef: React.RefObject<ReactDatePicker>;
}) => {
  const isMultiMonth = monthsShown > 1;
  const calInstance = rdpRef.current;

  return (
    <ReactDatePicker
      ref={rdpRef}
      className={classNames(cn(), className)}
      calendarClassName={classNames(
        isMultiMonth && cn({ e: "calendar", m: "multi-month" }),
        calendarClassName,
      )}
      showPopperArrow={false}
      customInput={customInput}
      disabledKeyboardNavigation
      renderDayContents={renderDayContents}
      renderCustomHeader={generateCustomHeaderRenderer({
        monthsShown,
        setHeaderProps: onHeaderPropsUpdate,
      })}
      monthsShown={monthsShown}
      calendarContainer={getCalendarContainer({
        inline,
        monthsShown,
        headerProps,
        calInstance,
      })}
      inline={inline}
      popperClassName={inline ? undefined : classNames(cn({ e: "Popper" }))}
      popperModifiers={
        inline
          ? undefined
          : {
              preventOverflow: {
                boundariesElement: "scrollParent",
              },
              ...popperModifiers,
            }
      }
      {...rest}
    />
  );
};

export const DatePicker = ({
  selected: selectedDateProp,
  ...props
}: ReactDatePickerProps) => {
  const [headerProps, setHeaderProps] = useState<HeaderPropsState>(null);
  const isMultiMonth = !!props.monthsShown && props.monthsShown > 1;
  const rdpRef = useRef<ReactDatePicker>(null);
  const calInstance = rdpRef.current;

  let selected = selectedDateProp;
  if (selected) {
    selected = isValid(selected) ? selected : null;
  }

  const handleHeaderPropsUpdate = (newHeaderProps: CustomHeaderProps) => {
    const equal = checkHeaderPropsEquality(headerProps, newHeaderProps);
    if (equal) return;
    setHeaderProps(newHeaderProps);
  };

  return isMultiMonth && props.inline ? (
    <Flex className={props.wrapperClassName}>
      {headerProps && (
        <Button
          icon={ICON_TYPE.CHEVRON_LEFT}
          variant={BUTTON_VARIANT.MINIMAL}
          onClick={headerProps.decreaseMonth}
          disabled={headerProps.prevMonthButtonDisabled}
          className={cn({ e: "multi-month-btn" })}
        />
      )}
      <Box>
        <MultiMonthHeader
          headerProps={headerProps}
          monthsShown={props.monthsShown || 1}
          inline={props.inline}
          calInstance={calInstance}
        />
        <UnwrappedDatePicker
          onHeaderPropsUpdate={handleHeaderPropsUpdate}
          headerProps={headerProps}
          rdpRef={rdpRef}
          selected={selected}
          {...props}
        />
      </Box>
      {headerProps && (
        <Button
          icon={ICON_TYPE.CHEVRON_RIGHT}
          variant={BUTTON_VARIANT.MINIMAL}
          onClick={headerProps.increaseMonth}
          disabled={headerProps.nextMonthButtonDisabled}
          className={cn({ e: "multi-month-btn" })}
        />
      )}
    </Flex>
  ) : (
    <UnwrappedDatePicker
      onHeaderPropsUpdate={handleHeaderPropsUpdate}
      headerProps={headerProps}
      rdpRef={rdpRef}
      selected={selected}
      {...props}
    />
  );
};

DatePicker.Input = DatePickerInput;
