import { cn } from "@/lib/utils";
import { KeyboardEvent, useId, useState } from "react";
import {
  type FieldErrors,
  type FieldValues,
  type Path,
  type UseFormRegister,
  type UseFormRegisterReturn,
  type UseFormSetValue,
  type UseFormWatch,
} from "react-hook-form";
import { toast } from "react-toastify";

type TagInputProps<T extends FieldValues> = {
  name: Path<T>;
  label: string;
  placeholder: string;
  className?: string;
  required?: boolean;
  disabled?: boolean;
  active?: boolean;
  value?: string[];
  icon?: React.ReactNode;
  iconPosition?: "left" | "right";
  height?: "sm" | "default";
  register?: UseFormRegister<T>;
  setValue?: UseFormSetValue<T>;
  watch?: UseFormWatch<T>;
  errors?: FieldErrors<T>;
  maxTags?: number;
  shouldAddWithSpace?: boolean;
  allowDuplicates?: boolean;
  tagValidator?: (tag: string) => boolean;
} & Partial<UseFormRegisterReturn>;

const TagInput = <T extends FieldValues>({
  className,
  label,
  placeholder,
  required,
  disabled,
  active,
  icon,
  name,
  register,
  setValue,
  shouldAddWithSpace = false,
  watch,
  errors,
  maxTags,
  allowDuplicates = false,
  tagValidator,
  onChange,
  onBlur,
  ref,
  ...props
}: TagInputProps<T>): React.ReactElement => {
  const id = useId();
  const [inputValue, setInputValue] = useState("");

  const watchedValue = watch ? watch(name) : props.value || [];
  const tags: string[] = Array.isArray(watchedValue) ? watchedValue : [];

  const error = errors && errors[name];

  const addTag = (tag: string) => {
    const trimmedTag = tag.trim();
    if (!trimmedTag) return;

    if (maxTags && tags.length >= maxTags) return;
    if (!allowDuplicates && tags.includes(trimmedTag)) {
      toast.error("Item already exists");
      return;
    }
    if (tagValidator && !tagValidator(trimmedTag)) return;

    const newTags = [...tags, trimmedTag];
    if (setValue) {
      setValue(name, newTags as any, { shouldValidate: true });
    }
    setInputValue("");
  };

  const removeTag = (indexToRemove: number) => {
    const newTags = tags.filter((_, index) => index !== indexToRemove);
    if (setValue) {
      setValue(name, newTags as any, { shouldValidate: true });
    }
  };

  const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
    if (
      e.key === "Enter" ||
      e.key === "," ||
      (shouldAddWithSpace && e.key === " ")
    ) {
      e.preventDefault();
      addTag(inputValue);
    } else if (e.key === "Backspace" && !inputValue && tags.length > 0) {
      removeTag(tags.length - 1);
    }
  };

  const handleInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
    if (inputValue) {
      addTag(inputValue);
    }
    onBlur?.(e);
  };

  return (
    <div className={className}>
      <label
        htmlFor={id}
        className="text-body-sm font-semibold text-dark dark:text-white"
      >
        {label}
        {required && <span className="ml-1 select-none text-error">*</span>}
      </label>

      <div
        className={cn(
          "relative mt-3 [&_svg]:pointer-events-none [&_svg]:absolute [&_svg]:top-1/2 [&_svg]:-translate-y-1/2 [&_svg]:text-dark-5 dark:[&_svg]:text-white/75",
          props.iconPosition === "left"
            ? "[&_svg]:left-4.5"
            : "[&_svg]:right-4.5",
        )}
      >
        <div
          className={cn(
            "w-full rounded-xl border border-stroke/70 bg-white/90 shadow-[0_1px_2px_rgba(16,24,40,0.04),0_8px_20px_rgba(16,24,40,0.04)] outline-none backdrop-blur-[1px] transition-all duration-200 focus-within:border-primary/70 focus-within:bg-white focus-within:shadow-[0_0_0_2px_rgba(59,130,246,0.14),0_10px_24px_rgba(59,130,246,0.08)] disabled:cursor-default disabled:bg-gray-2/70 data-[active=true]:border-primary/70 dark:border-dark-3/80 dark:bg-dark-2/85 dark:focus-within:border-primary/80 dark:focus-within:shadow-[0_0_0_2px_rgba(59,130,246,0.2),0_10px_24px_rgba(15,23,42,0.45)] dark:disabled:bg-dark dark:data-[active=true]:border-primary/80",
            error &&
              "border-red/80 focus-within:border-red focus-within:shadow-[0_0_0_2px_rgba(239,68,68,0.16),0_10px_24px_rgba(239,68,68,0.1)] dark:border-red/80 dark:focus-within:shadow-[0_0_0_2px_rgba(239,68,68,0.24),0_10px_24px_rgba(127,29,29,0.45)]",
            "flex min-h-[50px] flex-wrap items-center gap-2 px-3 py-2",
            props.iconPosition === "left" && "pl-12.5",
            props.height === "sm" && "min-h-[42px]",
            disabled && "pointer-events-none opacity-60",
          )}
          data-active={active}
        >
          {tags.map((tag, index) => (
            <span
              key={index}
              className="inline-flex items-center gap-1 rounded-lg border border-primary/20 bg-primary/10 px-2.5 py-1 text-sm font-semibold text-primary dark:border-primary/30 dark:bg-primary/20"
            >
              {tag}
              {!disabled && (
                <button
                  type="button"
                  onClick={() => removeTag(index)}
                  className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-xs transition hover:bg-primary/20"
                >
                  ×
                </button>
              )}
            </span>
          ))}

          <input
            id={id}
            type="text"
            placeholder={tags.length === 0 ? placeholder : ""}
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onKeyDown={handleKeyDown}
            onBlur={handleInputBlur}
            disabled={disabled || (maxTags ? tags.length >= maxTags : false)}
            className="min-w-[120px] flex-1 bg-transparent text-dark outline-none placeholder:text-dark-6 dark:text-white"
          />
        </div>

        {icon}

        <input
          type="hidden"
          {...(register ? register(name) : {})}
          value={tags.length > 0 ? JSON.stringify(tags) : ""}
          ref={ref}
        />
      </div>

      {error && (
        <p className="text-red mt-1.5 text-sm font-medium">
          {error.message as string}
        </p>
      )}

      {maxTags && (
        <p className="mt-1 text-xs text-dark-6 dark:text-dark-6">
          {tags.length}/{maxTags} tags
        </p>
      )}
    </div>
  );
};

export default TagInput;
