import React, { useEffect, useMemo, useRef, useState } from 'react';

import type { OptionEntry } from '../../data/locales';

/** Props for {@link SearchableSelect}. */
interface SearchableSelectProps {
  /** Currently-selected option ``value`` (matches ``OptionEntry.value``). */
  value: string;
  /** Fired with the new ``value`` once the merchant picks an option. */
  onChange: (next: string) => void;
  /** Picker entries - both label and value are searchable substring-style. */
  options: ReadonlyArray<OptionEntry>;
  /** Trigger placeholder shown when no option is selected yet. */
  placeholder?: string;
  /** Search-field placeholder shown inside the open popup. */
  searchPlaceholder?: string;
  /** Forwarded onto the trigger button - useful for a stable form id. */
  id?: string;
  /** Optional extra class names appended to the trigger. */
  className?: string;
}

/**
 * Searchable dropdown for long lists (~200 countries, ~180 languages).
 * The merchant clicks the trigger to open a popup, types into the
 * search field to filter substring matches, and clicks an option to
 * commit. Click-outside / Escape closes the popup; the selected
 * option's label is mirrored back into the trigger.
 *
 * Drop-in replacement for native ``<select>`` when the option count is
 * too large to skim.
 *
 * @param props {SearchableSelectProps} Controlled-select props.
 * @returns {JSX.Element} The searchable select.
 */
const SearchableSelect = ({
  value,
  onChange,
  options,
  placeholder = 'Select...',
  searchPlaceholder = 'Search...',
  id,
  className,
}: SearchableSelectProps): JSX.Element => {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  const containerRef = useRef<HTMLDivElement | null>(null);
  const searchInputRef = useRef<HTMLInputElement | null>(null);

  const selected = options.find(option => option.value === value);

  useEffect(() => {
    if (!open) return;
    const handleClickOutside = (event: MouseEvent) => {
      if (
        containerRef.current &&
        !containerRef.current.contains(event.target as Node)
      ) {
        setOpen(false);
        setQuery('');
      }
    };
    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setOpen(false);
        setQuery('');
      }
    };
    window.addEventListener('mousedown', handleClickOutside);
    window.addEventListener('keydown', handleEscape);

    return () => {
      window.removeEventListener('mousedown', handleClickOutside);
      window.removeEventListener('keydown', handleEscape);
    };
  }, [open]);

  useEffect(() => {
    if (open) {
      const timer = window.setTimeout(() => searchInputRef.current?.focus(), 0);

      return () => window.clearTimeout(timer);
    }
  }, [open]);

  /** Case-insensitive substring filter over option label + value. */
  const filteredOptions = useMemo(() => {
    if (!query.trim()) return options;
    const needle = query.toLowerCase();

    return options.filter(
      option =>
        option.label.toLowerCase().includes(needle) ||
        option.value.toLowerCase().includes(needle)
    );
  }, [query, options]);

  const handlePick = (next: string) => {
    onChange(next);
    setOpen(false);
    setQuery('');
  };

  return (
    <div ref={containerRef} className="relative w-full">
      <button
        type="button"
        id={id}
        onClick={() => setOpen(isOpen => !isOpen)}
        aria-haspopup="listbox"
        aria-expanded={open}
        className={`flex h-10 w-full items-center justify-between gap-2 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/30 ${
          className || ''
        }`}
      >
        <span
          className={`truncate text-left ${selected ? '' : 'text-gray-400'}`}
        >
          {selected ? selected.label : placeholder}
        </span>
        <svg
          aria-hidden="true"
          className="h-4 w-4 shrink-0 text-gray-500"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          strokeWidth={2}
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            d="M19 9l-7 7-7-7"
          />
        </svg>
      </button>

      {open && (
        <div
          role="listbox"
          className="absolute z-50 mt-1 w-full overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg"
        >
          <div className="flex items-center gap-2 border-b border-gray-200 px-3 py-2">
            <svg
              aria-hidden="true"
              className="h-4 w-4 text-gray-500"
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
              strokeWidth={2}
            >
              <path
                strokeLinecap="round"
                strokeLinejoin="round"
                d="M21 21l-4.35-4.35M11 19a8 8 0 100-16 8 8 0 000 16z"
              />
            </svg>
            <input
              ref={searchInputRef}
              type="text"
              value={query}
              onChange={event => setQuery(event.target.value)}
              placeholder={searchPlaceholder}
              className="flex-1 bg-transparent text-sm outline-none placeholder:text-gray-400"
            />
          </div>
          <div className="max-h-72 overflow-y-auto py-1">
            {filteredOptions.length === 0 ? (
              <div className="px-3 py-6 text-center text-sm text-gray-500">
                No matches.
              </div>
            ) : (
              filteredOptions.map(option => {
                const isSelected = option.value === value;

                return (
                  <button
                    key={option.value || '__not_set__'}
                    type="button"
                    role="option"
                    aria-selected={isSelected}
                    onClick={() => handlePick(option.value)}
                    className={`flex w-full items-center justify-between gap-2 px-3 py-1.5 text-left text-sm transition-colors hover:bg-gray-100 ${
                      isSelected ? 'bg-gray-50 font-medium' : ''
                    }`}
                  >
                    <span className="truncate">{option.label}</span>
                    {isSelected && (
                      <svg
                        aria-hidden="true"
                        className="h-4 w-4 shrink-0 text-blue-600"
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke="currentColor"
                        strokeWidth={2}
                      >
                        <path
                          strokeLinecap="round"
                          strokeLinejoin="round"
                          d="M5 13l4 4L19 7"
                        />
                      </svg>
                    )}
                  </button>
                );
              })
            )}
          </div>
        </div>
      )}
    </div>
  );
};

export default SearchableSelect;
