import { FormControl, TextField, type TextFieldProps } from "@mui/material";
import React, {
  ComponentPropsWithoutRef,
  FC,
  forwardRef,
  ReactElement,
  ReactNode,
  Ref,
  useEffect,
} from "react";
import { useCallbackRef, useMergeRefs } from "use-callback-ref";

import type { IconProps } from "../icon/Icon";
import { InputLabel } from "../inputLabel/InputLabel";
import { ErrorIcon } from "./ErrorIcon";
import { RequiredIndicator } from "./RequiredIndicator";
import { useTrackedValue } from "./useTrackedValue";
import { InputCounter } from "./InputCounter";
import { InputButton } from "./InputButton";
import { combineIntoAdornment } from "./combineIntoAdornment";
import { ClearButton } from "./ClearButton";
import { useInputStyles } from "./Input.styles";

export interface InputProps
  extends Omit<TextFieldProps, "variant" | "ref" | "className">,
    Omit<ComponentPropsWithoutRef<"input">, keyof TextFieldProps> {
  /**
   * A string or a custom label describing this field.
   *
   * Use `Input.Label` to access the original styled label.
   *
   * Note: if you're using a custom label, it is your responsibility
   * to pass the `htmlFor={id}` prop to it with the ID that matches
   * the one passed to the `<Input />` itself.
   */
  label?: string | ReactNode;
  height?: "32" | "40";
  variant?: "secondary" | "outlined";
  errorText?: string;
  leftIcon?: ReactElement | IconProps["name"];
  rightIcon?: ReactElement | IconProps["name"];
  maxLength?: number;
  ref?: Ref<HTMLInputElement>;
  clearButton?: boolean;
  onClear?: () => void;
  ESCAPE_className?: string;
}

export type InputComponent = FC<InputProps> & {
  Button: typeof InputButton;
  Label: typeof InputLabel;
};

// False positive because of the type assertion
// eslint-disable-next-line react/display-name
export const Input = forwardRef<HTMLInputElement, InputProps>(
  (
    {
      height = "40",
      id = undefined,
      variant = "outlined",
      label,
      error,
      required,
      fullWidth,
      errorText,
      leftIcon,
      rightIcon,
      maxLength,
      disabled = false,
      clearButton = true,
      onChange,
      onClear,
      ESCAPE_className,
      ...props
    },
    ref
  ) => {
    useMultipleIconWarning(error, rightIcon);
    const [value, takeValueSnapshot, handleChange] = useTrackedValue(onChange);
    const localRef = useCallbackRef<HTMLInputElement>(null, takeValueSnapshot);
    const mergedRef = useMergeRefs([localRef, ref]);
    const inputClasses = useInputStyles({ height, variant, disabled, error });

    const startAdornment = combineIntoAdornment([leftIcon], disabled);
    const endAdornment = combineIntoAdornment(
      [
        clearButton && !disabled && value !== "" ? (
          <ClearButton
            height={height}
            onChange={handleChange}
            onClear={onClear}
            inputRef={localRef}
          />
        ) : undefined,
        !error ? rightIcon : undefined,
        error ? <ErrorIcon errorText={errorText} /> : undefined,
      ],
      disabled
    );

    return (
      <FormControl fullWidth={fullWidth}>
        {typeof label === "string" ? (
          <InputLabel htmlFor={id}>{label}</InputLabel>
        ) : (
          label
        )}
        <TextField
          inputRef={mergedRef}
          error={error}
          variant={muiVariants[variant]}
          id={id}
          onChange={handleChange}
          disabled={disabled}
          className={ESCAPE_className}
          {...props}
          InputProps={{
            classes: inputClasses,
            startAdornment,
            endAdornment,
            ...props.InputProps,
          }}
        />
        {required && <RequiredIndicator hasLabel={!!label} />}
        {maxLength !== undefined ? (
          <InputCounter maxLength={maxLength} value={value} />
        ) : null}
      </FormControl>
    );
  }
  // ↓ Required to assign `InputButton`
) as unknown as InputComponent;

Input.displayName = "Input";

/** Print a warning if the `error` prop is specified together with `rightIcon`. */
function useMultipleIconWarning(
  error: InputProps["error"],
  rightIcon: InputProps["rightIcon"]
) {
  useEffect(() => {
    if (error && rightIcon !== undefined) {
      console.warn(
        "[docoli] Input: rightIcon will be ignored when error is true"
      );
    }
  }, [error, rightIcon]);
}

Input.Button = InputButton;
Input.Label = InputLabel;

const muiVariants = {
  secondary: "filled",
  outlined: "outlined",
} as const;
