"use client";

import { cn } from "@/lib/utils";
import React, {
  useCallback,
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
} from "react";
import { createPortal } from "react-dom";
import {
  type FieldErrors,
  type FieldValues,
  type Path,
  type UseFormRegisterReturn,
} from "react-hook-form";

interface Option {
  value: string;
  label: string;
}

type MultiSelectProps<T extends FieldValues> = {
  label: string;
  items?: { value: string; label: string }[] | null;
  value?: string[];
  errors?: FieldErrors<T>;
  required?: boolean;
  placeholder?: string;
  className?: string;
  disabled?: boolean;
  active?: boolean;
  height?: "sm" | "default";
  onSearch?: (query: string) => void;
  onLoadMore?: () => void;
  hasMore?: boolean;
  isLoading?: boolean;
  isLoadingMore?: boolean;
} & UseFormRegisterReturn<Path<T>>;

function MultiSelect<T extends FieldValues>({
  name,
  label,
  items,
  errors,
  placeholder = "Select an option",
  className,
  disabled,
  active,
  height,
  onChange,
  onBlur,
  ref,
  value,
  required,
  onSearch,
  onLoadMore,
  hasMore,
  isLoading,
  isLoadingMore,
}: MultiSelectProps<T>) {
  const id = useId();
  const error = errors && errors[name];

  const [selectedItems, setSelectedItems] = useState<Option[]>([]);
  const [searchQuery, setSearchQuery] = useState("");
  const [show, setShow] = useState(false);
  const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
  const [portalReady, setPortalReady] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);
  const listRef = useRef<HTMLDivElement>(null);
  const trigger = useRef<HTMLDivElement | null>(null);
  const initializedRef = useRef(false);

  useEffect(() => {
    setPortalReady(typeof document !== "undefined");
  }, []);

  const updateDropdownPosition = useCallback(() => {
    if (!trigger.current) return;
    const rect = trigger.current.getBoundingClientRect();
    setDropdownStyle({
      position: "fixed",
      top: rect.bottom + 4,
      left: rect.left,
      width: rect.width,
    });
  }, []);

  useLayoutEffect(() => {
    if (!show) return;
    updateDropdownPosition();
    const handle = () => updateDropdownPosition();
    window.addEventListener("resize", handle);
    window.addEventListener("scroll", handle, true);
    return () => {
      window.removeEventListener("resize", handle);
      window.removeEventListener("scroll", handle, true);
    };
  }, [show, updateDropdownPosition]);

  // Initialize selectedItems from external value + items (edit mode)
  useEffect(() => {
    if (initializedRef.current) return;
    if (!value || value.length === 0) return;
    const safeItems = Array.isArray(items) ? items : [];
    const found = safeItems.filter((item) => value.includes(item.value));
    if (found.length > 0) {
      setSelectedItems(found);
      initializedRef.current = true;
    }
  }, [items, value]);

  // Reset when value is cleared externally (form reset)
  useEffect(() => {
    if (!value || value.length === 0) {
      setSelectedItems([]);
      initializedRef.current = false;
    }
  }, [value]);

  // Update labels for selected items as new pages load
  useEffect(() => {
    const safeItems = Array.isArray(items) ? items : [];
    if (!safeItems.length) return;
    setSelectedItems((prev) => {
      const needsUpdate = prev.some((s) => {
        const found = safeItems.find((item) => item.value === s.value);
        return found && found.label !== s.label;
      });
      if (!needsUpdate) return prev;
      return prev.map((s) => {
        const found = safeItems.find((item) => item.value === s.value);
        return found ? { value: found.value, label: found.label } : s;
      });
    });
  }, [items]);

  const selectedValueSet = new Set(selectedItems.map((i) => i.value));
  const options = (Array.isArray(items) ? items : []).map((item) => ({
    ...item,
    selected: selectedValueSet.has(item.value),
  }));

  const open = () => {
    if (!disabled) setShow(true);
  };

  const combinedRef = (el: HTMLDivElement) => {
    trigger.current = el;
    if (typeof ref === "function") ref(el);
  };

  const handleSelect = (option: Option) => {
    if (disabled) return;

    let newSelected: Option[];
    if (selectedValueSet.has(option.value)) {
      newSelected = selectedItems.filter((i) => i.value !== option.value);
    } else {
      newSelected = [...selectedItems, option];
    }

    setSelectedItems(newSelected);
    onChange?.({ target: { name, value: newSelected.map((i) => i.value) } });
  };

  const remove = (itemValue: string, event: React.MouseEvent) => {
    if (disabled) return;
    event.stopPropagation();
    const newSelected = selectedItems.filter((i) => i.value !== itemValue);
    setSelectedItems(newSelected);
    onChange?.({ target: { name, value: newSelected.map((i) => i.value) } });
  };

  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    const q = e.target.value;
    setSearchQuery(q);
    onSearch?.(q);
  };

  const handleScroll = useCallback(() => {
    if (!listRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
    const { scrollTop, scrollHeight, clientHeight } = listRef.current;
    if (scrollHeight - scrollTop - clientHeight < 40) {
      onLoadMore();
    }
  }, [onLoadMore, hasMore, isLoadingMore]);

  // Close on outside click
  useEffect(() => {
    const clickHandler = ({ target }: MouseEvent) => {
      if (!dropdownRef.current) return;
      if (
        !show ||
        dropdownRef.current.contains(target as Node) ||
        trigger.current?.contains(target as Node)
      )
        return;
      setShow(false);
    };
    document?.addEventListener("click", clickHandler);
    return () => document?.removeEventListener("click", clickHandler);
  }, [show]);

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

      <div className="relative mt-3">
        <div
          ref={combinedRef}
          onClick={open}
          onBlur={onBlur}
          tabIndex={disabled ? -1 : 0}
          className={cn(
            "w-full rounded-lg border-[1.5px] border-stroke bg-transparent outline-none transition focus:border-primary disabled:cursor-default disabled:bg-gray-2 data-[active=true]:border-primary dark:border-dark-3 dark:bg-dark-2 dark:focus:border-primary dark:disabled:bg-dark dark:data-[active=true]:border-primary",
            error && "border-red focus:border-red dark:border-red",
            "flex min-h-[50px] cursor-pointer flex-wrap items-center gap-2 px-3 py-2",
            height === "sm" && "min-h-[42px]",
            disabled && "!cursor-not-allowed opacity-60",
          )}
          data-active={active}
        >
          <div className="flex flex-1 flex-wrap items-center gap-2">
            {selectedItems.map((item) => (
              <span
                key={item.value}
                className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-2.5 py-1 text-sm font-medium text-primary dark:bg-primary/20"
              >
                {item.label}
                {!disabled && (
                  <button
                    type="button"
                    onClick={(e) => remove(item.value, e)}
                    className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-xs hover:bg-primary/20"
                  >
                    ×
                  </button>
                )}
              </span>
            ))}
            {selectedItems.length === 0 && (
              <span className="text-dark-6 dark:text-dark-6">{placeholder}</span>
            )}
          </div>

          <div className="flex items-center">
            <svg
              className={cn(
                "fill-current text-dark-4 transition-transform duration-200 dark:text-dark-6",
                show && "rotate-180",
              )}
              width="20"
              height="20"
              viewBox="0 0 20 20"
              fill="none"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                fillRule="evenodd"
                clipRule="evenodd"
                d="M3.69149 7.09327C3.91613 6.83119 4.31069 6.80084 4.57277 7.02548L9.99936 11.6768L15.4259 7.02548C15.688 6.80084 16.0826 6.83119 16.3072 7.09327C16.5319 7.35535 16.5015 7.74991 16.2394 7.97455L10.4061 12.9745C10.172 13.1752 9.82667 13.1752 9.59261 12.9745L3.75928 7.97455C3.4972 7.74991 3.46685 7.35535 3.69149 7.09327Z"
                fill="currentColor"
              />
            </svg>
          </div>
        </div>

        {portalReady &&
          show &&
          createPortal(
        <div
          style={dropdownStyle}
          className={cn(
            "z-[1000000] rounded-lg border border-stroke bg-white shadow-lg dark:border-dark-3 dark:bg-dark-2",
          )}
          ref={dropdownRef}
        >
          {onSearch && (
            <div className="border-b border-stroke p-2 dark:border-dark-3">
              <input
                type="text"
                value={searchQuery}
                onChange={handleSearch}
                onClick={(e) => e.stopPropagation()}
                placeholder="Search..."
                className="w-full rounded-md border border-stroke bg-transparent px-3 py-1.5 text-sm outline-none focus:border-primary dark:border-dark-3 dark:text-white dark:focus:border-primary"
              />
            </div>
          )}
          <div
            ref={listRef}
            onScroll={handleScroll}
            className="max-h-52 overflow-y-auto py-1"
          >
            {isLoading ? (
              <div className="flex items-center justify-center py-4">
                <div className="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" />
              </div>
            ) : options.length === 0 && !isLoadingMore ? (
              <div className="px-3 py-2 text-sm text-dark-6 dark:text-dark-6">
                No items are available
              </div>
            ) : (
              <>
                {options.map((option) => (
                  <div
                    key={option.value}
                    className={cn(
                      "flex cursor-pointer items-center px-3 py-2 text-sm text-dark hover:bg-primary/5 dark:text-white dark:hover:bg-primary/5",
                      option.selected && "bg-primary/10 dark:bg-primary/20",
                    )}
                    onClick={() => handleSelect(option)}
                  >
                    <div
                      className={cn(
                        "mr-3 h-4 w-4 rounded border-2",
                        option.selected
                          ? "border-primary bg-primary"
                          : "border-stroke dark:border-dark-3",
                      )}
                    >
                      {option.selected && (
                        <svg
                          className="h-full w-full text-white"
                          fill="currentColor"
                          viewBox="0 0 20 20"
                        >
                          <path
                            fillRule="evenodd"
                            d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
                            clipRule="evenodd"
                          />
                        </svg>
                      )}
                    </div>
                    {option.label}
                  </div>
                ))}
                {isLoadingMore && (
                  <div className="flex items-center justify-center py-2">
                    <div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
                  </div>
                )}
              </>
            )}
          </div>
        </div>,
            document.body,
          )}
      </div>

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

export default MultiSelect;
