import { ComponentPropsWithoutRef, forwardRef, type ReactNode } from "react";
import { useLocale } from "@react-aria/i18n";
import { type AriaNumberFieldProps } from "@react-aria/numberfield";
import type { DOMAttributes, FocusableElement } from "@react-types/shared";

import { type InputProps } from "../input/Input";
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 { Tooltip, type TooltipProps } from "../tooltip/Tooltip";
import { useMergeRefs } from "use-callback-ref";
import { twJoin } from "tailwind-merge";
import { useStringAdapters } from "./useStringAdapters";
import { useReactAriaNumberField } from "./useReactAriaNumberField";

export interface NumberFieldProps
  extends Pick<
      AriaNumberFieldProps,
      "autoFocus" | "formatOptions" | "id" | "step" | "onFocus" | "onBlur"
    >,
    Pick<
      InputProps,
      | "height"
      | "variant"
      | "fullWidth"
      | "disabled"
      | "error"
      | "errorText"
      | "clearButton"
      | "onClear"
      | "required"
      | "aria-label"
      | "aria-labelledby"
      | "aria-describedby"
      | "aria-details"
      | "ESCAPE_className"
    >,
    Omit<
      ComponentPropsWithoutRef<"input">,
      "step" | "height" | "onFocus" | "onBlur"
    > {
  label?: string | ((labelProps: DOMAttributes<FocusableElement>) => ReactNode);
  defaultValue?: string;
  value?: string;
  onChange?: (event: { target: { value: string } }) => void;
  min?: string;
  max?: string;
  tooltip?: TooltipProps["tooltip"];
  tooltipProps?: Omit<TooltipProps, "tooltip" | "children">;
  locale?: string;

  /** Way to override the default appearance of the input field. */
  renderField?: (
    partProps: Pick<
      ReturnType<typeof useReactAriaNumberField>,
      "labelProps" | "groupProps" | "inputProps"
    >,
    ref: ReturnType<typeof useReactAriaNumberField>["ref"],
    state: ReturnType<typeof useReactAriaNumberField>["state"]
  ) => ReactNode;
}

export const NumberField = forwardRef<HTMLInputElement, NumberFieldProps>(
  (
    {
      renderField,
      onChange: onChangeString,
      defaultValue: stringDefaultValue,
      value: stringValue,
      label,
      disabled = false,
      fullWidth = false,
      height = "40",
      variant = "outlined",
      required = false,
      error = false,
      errorText,
      clearButton = true,
      locale: localeProp,
      min: stringMinValue,
      max: stringMaxValue,
      tooltip,
      tooltipProps = {},
      ESCAPE_className,
      ...restProps
    },
    externalRef
  ) => {
    const { locale: defaultLocale } = useLocale();
    const adapted = useStringAdapters({
      stringDefaultValue,
      stringValue,
      onChangeString,
      stringMinValue,
      stringMaxValue,
    });

    const { state, ref, groupProps, inputProps, labelProps } =
      useReactAriaNumberField({
        label,
        isDisabled: disabled,
        isRequired: required,
        isInvalid: error,
        errorMessage: errorText,
        locale: localeProp ?? defaultLocale,
        ...restProps,
        ...adapted,
      });

    const commonRef = useMergeRefs([externalRef, ref]);

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

    if (renderField) {
      return (
        <>{renderField({ labelProps, groupProps, inputProps }, ref, state)}</>
      );
    }

    return (
      <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}
            className={twJoin(
              classes.root,
              "flex items-center gap-8",
              height === "40" ? "px-12" : "px-8"
            )}
          >
            <input
              className={twMerge(
                classes.input,
                "border-none bg-transparent outline-none",
                "flex flex-1 px-0",
                disabled && "text-N60"
              )}
              {...inputProps}
              ref={commonRef}
            />
            {clearButton && !disabled && (
              <ClearButton
                hidden={Number.isNaN(state.numberValue)}
                height={height}
                inputRef={ref}
                onChange={onChangeString}
              />
            )}
            {error && <ErrorIcon errorText={errorText} />}
            {required && <RequiredIndicator hasLabel={!!label} />}
          </div>
        </Tooltip>
      </div>
    );
  }
);

NumberField.displayName = "NumberField";
