"use client";

import {
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
  type KeyboardEvent,
} from "react";
import { searchableSelectDict } from "@/components/searchable-select/dict";
import type {
  SearchableSelectOption,
  SearchableSelectProps,
} from "@/components/searchable-select/types";

function normalizeSearchText(value: string) {
  return value.trim().toLowerCase();
}

function filterOptions(options: SearchableSelectOption[], query: string) {
  const normalizedQuery = normalizeSearchText(query);

  if (!normalizedQuery) {
    return options;
  }

  return options.filter((option) => {
    const haystack = normalizeSearchText(
      [option.label, option.searchText].filter(Boolean).join(" "),
    );
    return haystack.includes(normalizedQuery);
  });
}

export function SearchableSelect({
  id,
  value,
  onChange,
  options,
  placeholder = searchableSelectDict.placeholder,
  searchPlaceholder = searchableSelectDict.searchPlaceholder,
  emptyMessage = searchableSelectDict.noResults,
  disabled = false,
  className = "",
}: SearchableSelectProps) {
  const generatedId = useId();
  const inputId = id ?? generatedId;
  const listboxId = `${inputId}-listbox`;
  const containerRef = useRef<HTMLDivElement>(null);
  const searchInputRef = useRef<HTMLInputElement>(null);

  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const [highlightedIndex, setHighlightedIndex] = useState(0);

  const selectedOption = options.find((option) => option.value === value);
  const filteredOptions = useMemo(
    () => filterOptions(options, query),
    [options, query],
  );

  useEffect(() => {
    if (!open) {
      return;
    }

    setHighlightedIndex(0);
    searchInputRef.current?.focus();

    const handlePointerDown = (event: MouseEvent) => {
      if (!containerRef.current?.contains(event.target as Node)) {
        setOpen(false);
        setQuery("");
      }
    };

    document.addEventListener("mousedown", handlePointerDown);

    return () => {
      document.removeEventListener("mousedown", handlePointerDown);
    };
  }, [open]);

  useEffect(() => {
    if (highlightedIndex >= filteredOptions.length) {
      setHighlightedIndex(Math.max(filteredOptions.length - 1, 0));
    }
  }, [filteredOptions.length, highlightedIndex]);

  const closeDropdown = () => {
    setOpen(false);
    setQuery("");
  };

  const openDropdown = () => {
    if (disabled) {
      return;
    }

    setOpen(true);
  };

  const selectOption = (option: SearchableSelectOption) => {
    onChange(option.value);
    closeDropdown();
  };

  const handleTriggerKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {
    if (disabled) {
      return;
    }

    if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
      event.preventDefault();
      setOpen(true);
    }
  };

  const handleSearchKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
    if (event.key === "Escape") {
      event.preventDefault();
      event.stopPropagation();
      closeDropdown();
      return;
    }

    if (event.key === "ArrowDown") {
      event.preventDefault();
      setHighlightedIndex((current) =>
        Math.min(current + 1, filteredOptions.length - 1),
      );
      return;
    }

    if (event.key === "ArrowUp") {
      event.preventDefault();
      setHighlightedIndex((current) => Math.max(current - 1, 0));
      return;
    }

    if (event.key === "Enter" && filteredOptions[highlightedIndex]) {
      event.preventDefault();
      selectOption(filteredOptions[highlightedIndex]);
    }
  };

  return (
    <div ref={containerRef} className={`relative ${className}`}>
      <button
        id={inputId}
        type="button"
        disabled={disabled}
        aria-haspopup="listbox"
        aria-expanded={open}
        aria-controls={listboxId}
        onClick={() => (open ? closeDropdown() : openDropdown())}
        onKeyDown={handleTriggerKeyDown}
        className="flex h-12 w-full items-center justify-between gap-3 rounded-2xl bg-input px-4 text-sm shadow-soft-sm ring-1 ring-border outline-none transition-shadow focus:ring-2 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-60"
      >
        <span
          className={`truncate text-start ${
            selectedOption ? "text-foreground" : "text-muted-foreground"
          }`}
        >
          {selectedOption?.label ?? placeholder}
        </span>
        <svg
          aria-hidden="true"
          className={`size-4 shrink-0 text-muted-foreground transition-transform ${
            open ? "rotate-180" : ""
          }`}
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          strokeWidth={2}
        >
          <path d="M6 9l6 6 6-6" />
        </svg>
      </button>

      {open ? (
        <div className="absolute inset-x-0 top-[calc(100%+0.5rem)] z-20 overflow-hidden rounded-2xl border border-border bg-popover shadow-soft">
          <div className="border-b border-border p-2">
            <input
              ref={searchInputRef}
              type="search"
              value={query}
              onChange={(event) => setQuery(event.target.value)}
              onKeyDown={handleSearchKeyDown}
              placeholder={searchPlaceholder}
              className="h-10 w-full rounded-xl bg-input px-3 text-sm outline-none ring-1 ring-border focus:ring-2 focus:ring-primary"
            />
          </div>

          <ul
            id={listboxId}
            role="listbox"
            aria-labelledby={inputId}
            className="max-h-56 overflow-y-auto p-1"
          >
            {filteredOptions.length === 0 ? (
              <li className="px-3 py-4 text-center text-sm text-muted-foreground">
                {emptyMessage}
              </li>
            ) : (
              filteredOptions.map((option, index) => {
                const selected = option.value === value;
                const highlighted = index === highlightedIndex;

                return (
                  <li key={option.value} role="presentation">
                    <button
                      type="button"
                      role="option"
                      aria-selected={selected}
                      onMouseEnter={() => setHighlightedIndex(index)}
                      onClick={() => selectOption(option)}
                      className={`flex w-full rounded-xl px-3 py-2.5 text-start text-sm transition-colors ${
                        selected
                          ? "bg-primary/15 text-primary"
                          : highlighted
                            ? "bg-accent text-accent-foreground"
                            : "text-popover-foreground hover:bg-accent hover:text-accent-foreground"
                      }`}
                    >
                      <span className="truncate">{option.label}</span>
                    </button>
                  </li>
                );
              })
            )}
          </ul>
        </div>
      ) : null}
    </div>
  );
}

export type { SearchableSelectOption, SearchableSelectProps } from "@/components/searchable-select/types";
