import { useRef } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import type React from 'react';
import type { SearchResult } from '../../hooks/useLocationSearch';
import { useDropdownPosition } from '../../hooks/useDropdownPosition';
import { SearchIcon } from './icons/SearchIcon';
import { MapPinIcon } from './icons/MapPinIcon';
import { HouseIcon } from './icons/HouseIcon';

interface SearchHook {
  query: string;
  results: SearchResult[];
  activeIndex: number;
  setActiveIndex: (idx: number) => void;
  open: boolean;
  searching?: boolean;
  handleInputChange: (value: string) => void;
  handleKeyDown: (e: React.KeyboardEvent, onSelect: (result: SearchResult) => void) => void;
  showEmptySearches: () => void;
  close?: () => void;
}

/** Addresses arrive in raw ALL-CAPS Land Registry casing; title-case for display. */
function titleCaseAddress(address: string): string {
  return address
    .toLowerCase()
    .replace(/(^|[\s\-/(])([a-z])/g, (_, sep, c) => sep + c.toUpperCase());
}

interface PlaceSearchInputProps {
  search: SearchHook;
  onSelect: (result: SearchResult) => void;
  loading?: boolean;
  placeholder?: string;
  ariaLabel?: string;
  name?: string;
  size?: 'sm' | 'xs';
  inputClassName?: string;
  inputRef?: React.Ref<HTMLInputElement>;
  onInputChange?: () => void;
  portal?: boolean;
}

export function PlaceSearchInput({
  search,
  onSelect,
  loading,
  placeholder,
  ariaLabel,
  name,
  size = 'sm',
  inputClassName,
  inputRef,
  onInputChange,
  portal,
}: PlaceSearchInputProps) {
  const { t } = useTranslation();
  const sm = size === 'sm';
  const iconSize = sm ? 'w-4 h-4' : 'w-3 h-3';
  const spinnerSize = sm ? 'w-4 h-4' : 'w-3 h-3';
  const wrapperRef = useRef<HTMLDivElement>(null);
  const dropdownPos = useDropdownPosition(wrapperRef, portal ? search.open : false);

  const showEmptyResults =
    search.open &&
    !search.searching &&
    search.query.trim().length >= 2 &&
    search.results.length === 0;
  const showDropdown = search.open && (search.results.length > 0 || showEmptyResults);
  const showSpinner = loading || search.searching;

  const dropdown = showDropdown && (
    <div
      className={`bg-white dark:bg-warm-800 rounded shadow-lg border border-warm-200 dark:border-warm-700 ${sm ? 'max-h-64' : 'max-h-48'} overflow-y-auto`}
      // Keep focus on the input while interacting with the list (incl. its
      // scrollbar) so the blur-close below doesn't fire mid-click.
      onMouseDown={(e) => e.preventDefault()}
      style={
        portal && dropdownPos
          ? {
              position: 'fixed',
              top: dropdownPos.top,
              left: dropdownPos.left,
              width: dropdownPos.width,
              zIndex: 50,
            }
          : undefined
      }
    >
      {showEmptyResults ? (
        <div
          className={`text-warm-500 dark:text-warm-400 ${sm ? 'px-3 py-2 text-sm' : 'px-2 py-1.5 text-xs'}`}
          role="status"
        >
          {t('locationSearch.noResults')}
        </div>
      ) : (
        search.results.map((result, idx) => (
          <button
            key={
              result.type === 'postcode'
                ? `pc-${result.label}`
                : result.type === 'address'
                  ? `addr-${result.postcode}-${result.address}-${result.lat}`
                  : `pl-${result.name}-${result.lat}`
            }
            type="button"
            className={`w-full text-left flex items-center cursor-pointer ${
              sm ? 'px-3 py-2 gap-2 text-sm' : 'px-2 py-1.5 gap-1.5 text-xs'
            } ${
              idx === search.activeIndex
                ? 'bg-teal-50 dark:bg-teal-900/30'
                : 'hover:bg-warm-50 dark:hover:bg-warm-700'
            }`}
            onMouseEnter={() => search.setActiveIndex(idx)}
            onMouseDown={(e) => {
              e.preventDefault();
              onSelect(result);
            }}
          >
            {result.type === 'postcode' ? (
              <>
                <SearchIcon className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`} />
                <span className="text-warm-700 dark:text-warm-200">{result.label}</span>
              </>
            ) : result.type === 'address' ? (
              <>
                <HouseIcon className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`} />
                <span
                  className="min-w-0 text-warm-700 dark:text-warm-200"
                  title={`${titleCaseAddress(result.address)}, ${result.postcode}`}
                >
                  <span className="block truncate">{titleCaseAddress(result.address)}</span>
                  <span className="block truncate text-warm-400 dark:text-warm-500">
                    {result.postcode}
                  </span>
                </span>
              </>
            ) : (
              <>
                <MapPinIcon className={`${iconSize} text-warm-400 dark:text-warm-500 shrink-0`} />
                <span
                  className="text-warm-700 dark:text-warm-200"
                  title={result.city ? `${result.name} (${result.city})` : result.name}
                >
                  {result.name}
                  {result.city && (
                    <span className="text-warm-400 dark:text-warm-500"> ({result.city})</span>
                  )}
                </span>
              </>
            )}
          </button>
        ))
      )}
    </div>
  );

  return (
    <div ref={wrapperRef} className="relative flex-1 min-w-0">
      <input
        ref={inputRef}
        type="text"
        name={name}
        value={search.query}
        onChange={(e) => {
          search.handleInputChange(e.target.value);
          onInputChange?.();
        }}
        onFocus={() => {
          search.showEmptySearches();
        }}
        // Without this, instances whose parents lack outside-click handling
        // leave a detached dropdown floating over the map.
        onBlur={() => search.close?.()}
        onKeyDown={(e) => search.handleKeyDown(e, onSelect)}
        aria-label={ariaLabel ?? placeholder}
        placeholder={placeholder}
        className={inputClassName}
      />

      {showSpinner && (
        <div
          className={`absolute right-2 top-1/2 -translate-y-1/2 ${spinnerSize} border-2 border-warm-300 dark:border-warm-600 border-t-teal-500 rounded-full animate-spin`}
        />
      )}

      {showDropdown &&
        (portal ? (
          createPortal(dropdown, document.body)
        ) : (
          <div className="absolute top-full left-0 right-0 mt-1 z-20">{dropdown}</div>
        ))}
    </div>
  );
}
