import { twJoin } from "tailwind-merge";
import { Popover } from "@mui/material";
import {
  forwardRef,
  type ComponentProps,
  type ComponentPropsWithoutRef,
  type MutableRefObject,
  type ReactNode,
} from "react";
import { I18nProvider } from "@react-aria/i18n";
import type { DateValue } from "@react-aria/datepicker";
import type { DatePickerStateOptions } from "@react-stately/datepicker";
import type { DOMAttributes, FocusableElement } from "@react-types/shared";

import { Input, type InputProps } from "../input/Input";
import { Calendar } from "./Calendar";
import { convertButtonProps } from "./convertButtonProps";
import { DateField } from "./DateField";
import { useInputStyles } from "../input/Input.styles";
import { InputLabel } from "../inputLabel/InputLabel";
import { ClearButton } from "../input/ClearButton";
import { RequiredIndicator } from "../input/RequiredIndicator";
import { ErrorIcon } from "../input/ErrorIcon";
import { twMerge } from "../../../tailwind.config";
import { useStringAdapters, type DateString } from "./useStringAdapters";
import { useReactAriaDatePicker } from "./useReactAriaDatePicker";
import { Tooltip, type TooltipProps } from "../tooltip/Tooltip";

export interface DatePickerProps
  extends Pick<
      DatePickerStateOptions<DateValue>,
      | "defaultOpen"
      | "isOpen"
      | "description"
      | "errorMessage"
      | "onBlur"
      | "onFocus"
      | "autoFocus"
    >,
    Pick<
      InputProps,
      | "height"
      | "variant"
      | "fullWidth"
      | "disabled"
      | "error"
      | "errorText"
      | "clearButton"
      | "onClear"
      | "required"
      | "aria-label"
      | "aria-labelledby"
      | "aria-describedby"
      | "aria-details"
      | "ESCAPE_className"
    > {
  defaultValue?: DateString;
  value?: DateString;
  label?: string | ((labelProps: DOMAttributes<FocusableElement>) => ReactNode);
  min?: DateString;
  max?: DateString;
  onChange?: (event: { target: { value: DateString } }) => void;
  tooltip?: TooltipProps["tooltip"];
  tooltipProps?: Omit<TooltipProps, "tooltip" | "children">;
  locale?: string;

  /** Way to override the default appearance of the date picker (input field + button). */
  renderPicker?: (
    partProps: Pick<
      ReturnType<typeof useReactAriaDatePicker>,
      "labelProps" | "groupProps" | "fieldProps"
    > & {
      buttonProps: Omit<ComponentPropsWithoutRef<"button">, "name" | "color">;
    },
    ref: ReturnType<typeof useReactAriaDatePicker>["ref"],
    state: ReturnType<typeof useReactAriaDatePicker>["state"]
  ) => ReactNode;
  /** Way to override the popup part of the date picker. */
  renderPopover?: (
    partProps: Pick<
      ReturnType<typeof useReactAriaDatePicker>,
      "dialogProps" | "calendarProps"
    >,
    ref: ReturnType<typeof useReactAriaDatePicker>["ref"],
    state: ReturnType<typeof useReactAriaDatePicker>["state"]
  ) => ReactNode;
  ESCAPE_popoverProps?: Omit<ComponentProps<typeof Popover>, "open">;
  autoFocusCalendar?: boolean;
}

export const DatePicker = forwardRef<HTMLSpanElement, DatePickerProps>(
  (
    {
      renderPicker,
      renderPopover,
      autoFocusCalendar = true,
      defaultValue: stringDefaultValue,
      value: stringValue,
      onChange: onChangeString,
      label,
      disabled = false,
      fullWidth = false,
      height = "40",
      variant = "outlined",
      required = false,
      error = false,
      errorText,
      clearButton = true,
      locale,
      onClear,
      min: stringMinValue,
      max: stringMaxValue,
      tooltip,
      tooltipProps = {},
      ESCAPE_className,
      ESCAPE_popoverProps,
      ...restProps
    },
    externalRef
  ) => {
    const adapted = useStringAdapters({
      stringDefaultValue,
      stringValue,
      onChangeString,
      stringMinValue,
      stringMaxValue,
    });

    const {
      state,
      ref,
      groupProps,
      labelProps,
      fieldProps,
      dialogProps,
      buttonProps,
      calendarProps,
    } = useReactAriaDatePicker({
      isDisabled: disabled,
      label,
      isRequired: required,
      isInvalid: error,
      shouldForceLeadingZeros: true,
      autoFocusCalendar,
      ...adapted,
      ...restProps,
    });

    const classes = useInputStyles({
      variant,
      height,
      disabled,
      error,
    });

    return (
      <I18nProvider locale={locale}>
        {renderPicker === undefined ? (
          <div
            className={twMerge(
              "relative",
              fullWidth ? "flex" : "inline-flex",
              "flex-col",
              ESCAPE_className
            )}
          >
            {typeof label === "string" ? (
              <InputLabel {...labelProps}>{label}</InputLabel>
            ) : (
              label?.(labelProps)
            )}

            <Tooltip
              tooltip={tooltip ?? ""}
              disabled={tooltip === undefined}
              {...tooltipProps}
            >
              <div
                {...groupProps}
                ref={ref as MutableRefObject<HTMLDivElement>}
                className={twJoin(classes.root, "flex items-center gap-8")}
              >
                <DateField
                  className={twMerge(
                    classes.input,
                    !state.value && "text-N100 focus-within:text-N700",
                    "flex flex-1 pr-0",
                    disabled && "text-N60"
                  )}
                  ref={externalRef}
                  {...fieldProps}
                />
                {clearButton && !disabled && (
                  <ClearButton
                    hidden={state.value === null}
                    height={height}
                    onClear={() => {
                      onClear?.();
                      state.setValue(null);
                    }}
                  />
                )}
                {error && <ErrorIcon errorText={errorText} />}
                <Input.Button
                  icon="calendar"
                  disabled={disabled}
                  className={height === "40" ? "mr-12" : "mr-8"}
                  {...convertButtonProps(buttonProps)}
                />
                {required && <RequiredIndicator hasLabel={!!label} />}
              </div>
            </Tooltip>
          </div>
        ) : (
          renderPicker(
            {
              labelProps,
              groupProps,
              fieldProps,
              buttonProps: convertButtonProps(buttonProps),
            },
            ref,
            state
          )
        )}
        {renderPopover === undefined ? (
          <Popover
            anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
            transformOrigin={{
              horizontal: "right",
              vertical: -8,
            }}
            onClose={() => state.setOpen(false)}
            open={state.isOpen}
            anchorEl={ref.current}
            classes={{ paper: "rounded-12 bg-N0 p-16 text-N700 shadow-S300" }}
            {...ESCAPE_popoverProps}
          >
            <div {...dialogProps}>
              <Calendar {...calendarProps} />
            </div>
          </Popover>
        ) : (
          renderPopover(
            {
              dialogProps,
              calendarProps,
            },
            ref,
            state
          )
        )}
      </I18nProvider>
    );
  }
);

DatePicker.displayName = "DatePicker";
