import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { CatalogSearchResult } from '../types';
import { api, imageUrl, portraitThumbUrl } from '../api/client';
import './CatalogSearchBar.css';

interface Props {
  onSelectArtist: (artistId: number) => void;
  onSelectMovement: (movementId: number) => void;
  onSelectPainting: (paintingId: number) => void;
}

const TYPE_ORDER: CatalogSearchResult['type'][] = ['artist', 'movement', 'painting'];

function resultKey(item: CatalogSearchResult): string {
  return `${item.type}-${item.id}`;
}

function paintingThumbSrc(item: Extract<CatalogSearchResult, { type: 'painting' }>): string {
  const path = item.thumbnail_path || item.image_path;
  return path ? imageUrl(path) : '/placeholder-art.svg';
}

export default function CatalogSearchBar({
  onSelectArtist,
  onSelectMovement,
  onSelectPainting,
}: Props) {
  const { t } = useTranslation('search');
  const typeLabels: Record<CatalogSearchResult['type'], string> = {
    artist: t('groupArtist'),
    movement: t('groupMovement'),
    painting: t('groupPainting'),
  };
  const listboxId = useId();
  const rootRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const [query, setQuery] = useState('');
  const [results, setResults] = useState<CatalogSearchResult[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [open, setOpen] = useState(false);
  const [activeIndex, setActiveIndex] = useState(-1);

  const grouped = useMemo(() => {
    const map = new Map<CatalogSearchResult['type'], CatalogSearchResult[]>();
    for (const type of TYPE_ORDER) map.set(type, []);
    for (const item of results) {
      map.get(item.type)?.push(item);
    }
    return TYPE_ORDER.map((type) => ({ type, items: map.get(type) ?? [] })).filter((g) => g.items.length > 0);
  }, [results]);

  const flatResults = useMemo(() => grouped.flatMap((g) => g.items), [grouped]);

  const activate = useCallback(
    (item: CatalogSearchResult) => {
      setOpen(false);
      setQuery('');
      setResults([]);
      setActiveIndex(-1);
      inputRef.current?.blur();
      if (item.type === 'artist') onSelectArtist(item.id);
      else if (item.type === 'movement') onSelectMovement(item.id);
      else onSelectPainting(item.id);
    },
    [onSelectArtist, onSelectMovement, onSelectPainting]
  );

  useEffect(() => {
    const trimmed = query.trim();
    if (trimmed.length < 2) {
      setResults([]);
      setLoading(false);
      setError(null);
      setActiveIndex(-1);
      return;
    }

    let cancelled = false;
    setLoading(true);
    setError(null);

    const timer = window.setTimeout(() => {
      api
        .search(trimmed)
        .then((data) => {
          if (cancelled) return;
          setResults(data.results);
          setOpen(true);
          setActiveIndex(data.results.length > 0 ? 0 : -1);
        })
        .catch(() => {
          if (cancelled) return;
          setResults([]);
          setError(t('searchFailed'));
          setOpen(true);
          setActiveIndex(-1);
        })
        .finally(() => {
          if (!cancelled) setLoading(false);
        });
    }, 300);

    return () => {
      cancelled = true;
      window.clearTimeout(timer);
    };
  }, [query]);

  useEffect(() => {
    const onPointerDown = (e: MouseEvent) => {
      if (!rootRef.current?.contains(e.target as Node)) {
        setOpen(false);
      }
    };
    document.addEventListener('mousedown', onPointerDown);
    return () => document.removeEventListener('mousedown', onPointerDown);
  }, []);

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Escape') {
      setOpen(false);
      setActiveIndex(-1);
      return;
    }
    if (!open || flatResults.length === 0) return;

    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setActiveIndex((i) => (i + 1) % flatResults.length);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setActiveIndex((i) => (i <= 0 ? flatResults.length - 1 : i - 1));
    } else if (e.key === 'Enter' && activeIndex >= 0) {
      e.preventDefault();
      activate(flatResults[activeIndex]);
    }
  };

  const showPanel = open && query.trim().length >= 2;

  return (
    <div className="catalog-search" ref={rootRef}>
      <label className="catalog-search-label" htmlFor={`${listboxId}-input`}>
        {t('label')}
      </label>
      <div className="catalog-search-field">
        <input
          ref={inputRef}
          id={`${listboxId}-input`}
          className="catalog-search-input"
          type="search"
          role="combobox"
          aria-expanded={showPanel}
          aria-controls={showPanel ? `${listboxId}-listbox` : undefined}
          aria-autocomplete="list"
          aria-activedescendant={
            showPanel && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined
          }
          placeholder={t('placeholder')}
          value={query}
          autoComplete="off"
          spellCheck={false}
          onChange={(e) => {
            setQuery(e.target.value);
            setOpen(true);
          }}
          onFocus={() => {
            if (query.trim().length >= 2) setOpen(true);
          }}
          onKeyDown={handleKeyDown}
        />
        {loading && <span className="catalog-search-spinner" aria-hidden />}
      </div>

      {showPanel && (
        <div
          id={`${listboxId}-listbox`}
          className="catalog-search-panel"
          role="listbox"
          aria-label={t('resultsLabel')}
        >
          {error && <p className="catalog-search-message catalog-search-error">{error}</p>}
          {!error && !loading && flatResults.length === 0 && (
            <p className="catalog-search-message">{t('noMatches')}</p>
          )}
          {grouped.map((group) => (
            <div key={group.type} className="catalog-search-group">
              <p className="catalog-search-group-label">{typeLabels[group.type]}</p>
              <ul className="catalog-search-list">
                {group.items.map((item) => {
                  const flatIndex = flatResults.findIndex((r) => resultKey(r) === resultKey(item));
                  const active = flatIndex === activeIndex;
                  return (
                    <li key={resultKey(item)}>
                      <button
                        type="button"
                        id={`${listboxId}-opt-${flatIndex}`}
                        role="option"
                        aria-selected={active}
                        className={`catalog-search-option${active ? ' catalog-search-option-active' : ''}`}
                        onMouseEnter={() => setActiveIndex(flatIndex)}
                        onMouseDown={(e) => e.preventDefault()}
                        onClick={() => activate(item)}
                      >
                        {item.type === 'artist' && (
                          <>
                            <img
                              className="catalog-search-thumb"
                              src={portraitThumbUrl(item)}
                              alt=""
                              loading="lazy"
                            />
                            <span className="catalog-search-option-text">
                              <span className="catalog-search-option-title">{item.name}</span>
                              <span className="catalog-search-option-meta">
                                {[item.movement_name, formatYears(item.birth_year, item.death_year)]
                                  .filter(Boolean)
                                  .join(' · ')}
                              </span>
                            </span>
                          </>
                        )}
                        {item.type === 'movement' && (
                          <>
                            <span
                              className="catalog-search-movement-swatch"
                              style={{ background: item.color || '#c9a96e' }}
                              aria-hidden
                            />
                            <span className="catalog-search-option-text">
                              <span className="catalog-search-option-title">{item.name}</span>
                              <span className="catalog-search-option-meta">
                                {[item.era_name, formatYears(item.start_year, item.end_year)]
                                  .filter(Boolean)
                                  .join(' · ')}
                              </span>
                            </span>
                          </>
                        )}
                        {item.type === 'painting' && (
                          <>
                            <img
                              className="catalog-search-thumb"
                              src={paintingThumbSrc(item)}
                              alt=""
                              loading="lazy"
                            />
                            <span className="catalog-search-option-text">
                              <span className="catalog-search-option-title">{item.title}</span>
                              <span className="catalog-search-option-meta">
                                {[item.artist_name, item.year != null ? String(item.year) : null, item.movement_name]
                                  .filter(Boolean)
                                  .join(' · ')}
                              </span>
                            </span>
                          </>
                        )}
                      </button>
                    </li>
                  );
                })}
              </ul>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function formatYears(start: number | null | undefined, end: number | null | undefined): string | null {
  if (start == null && end == null) return null;
  if (start != null && end != null) return `${start}–${end}`;
  if (start != null) return String(start);
  return String(end);
}
