import { twJoin } from "tailwind-merge";
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";

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;
};

const gapMapping = {
  4: "gap-4",
  6: "gap-6",
  8: "gap-8",
};

export function useInputStyles({
  variant,
  height,
  disabled,
  error,
  gap,
}: Pick<InputProps, "variant" | "height" | "disabled" | "error"> & {
  gap?: 4 | 6 | 8;
}) {
  let bg = "bg-N0";
  if (disabled) {
    bg = "bg-N20A";
  } else if (variant === "secondary" && !error) {
    bg = "bg-N20A hover:bg-N0 focus-within:bg-N0";
  }

  let outlineColor = "outline-transparent";
  if (error) {
    outlineColor = "outline-R200";
  } else if (disabled) {
    if (variant === "outlined") {
      outlineColor = "outline-N40";
    }
  } else {
    if (variant === "outlined") {
      outlineColor = "outline-N40";
    }

    outlineColor = twJoin(
      outlineColor,
      "hover:outline-N40",
      "focus-within:outline-B200 hover:focus-within:outline-B200",
      "[&.Mui-focused]:outline-B200"
    );
  }

  let ring: string | null = null;
  if (error) {
    ring = "ring-3 ring-R400ER";
  } else if (!disabled) {
    ring = twJoin(
      "hover:ring-3 hover:ring-N900HR",
      "focus-within:ring-3 focus-within:ring-B400FR hover:focus-within:ring-B400FR",
      "[&.Mui-focused]:ring-B400FR"
    );
  }

  const classes = {
    root: twJoin(
      "group rounded-6",
      bg,
      disabled ? "text-N60" : "text-N100",
      "outline-1 outline -outline-offset-1",
      outlineColor,
      ring,
      gap ? gapMapping[gap] : height === "40" ? "gap-8" : "gap-4",
      "before:hidden after:hidden"
    ),
    input: twJoin(
      "h-auto overflow-hidden overflow-ellipsis whitespace-nowrap text-N700 text-style-R200",
      "placeholder:text-style-R200 placeholder:opacity-100",
      "autofill:transition-shadow autofill:duration-200 autofill:shadow-no-chrome-autofill group-hover:autofill:shadow-N0 focus:autofill:shadow-N0",
      disabled
        ? "autofill:shadow-N20A"
        : variant === "outlined"
        ? "autofill:shadow-N0"
        : "autofill:shadow-N20",
      disabled
        ? "[-webkit-text-fill-color:inherit] placeholder:text-N60"
        : "placeholder:text-N100",
      "hover:border-N40",
      "[&::-webkit-search-cancel-button]:hidden",
      height === "40" ? "py-10 px-12" : "py-6 px-8"
    ),
    adornedStart: height === "40" ? "pl-12" : "pl-8",
    adornedEnd: height === "40" ? "pr-12" : "pr-8",
    inputAdornedStart: "pl-0",
    inputAdornedEnd: "pr-0",
    ...(variant === "outlined" ? { notchedOutline: "hidden" } : {}),
  };

  return classes;
}

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
            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}
          InputProps={{
            classes: inputClasses,
            startAdornment,
            endAdornment,
          }}
          onChange={handleChange}
          disabled={disabled}
          className={ESCAPE_className}
          {...props}
        />
        {required && <RequiredIndicator hasLabel={!!label} />}
        {maxLength !== undefined ? (
          <InputCounter maxLength={maxLength} value={value} />
        ) : null}
      </FormControl>
    );
  }
  // ↓ Required to assign `InputButton`
) as unknown as InputComponent;

/** 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;
