import { useEffect, useMemo, useRef, useState } from 'react';
import { Search, Loader2, HardDrive, ChevronRight, ChevronDown, Trophy, Calendar, Users, RefreshCw, HelpCircle, X, SlidersHorizontal, MoreVertical, FolderOpen } from 'lucide-react';
import { toast } from 'sonner';
import { humanFileSize, basename, clearDirectory, joinPath, putFileContents, streamFetch, type FetchProgress } from '../webdav';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
import { MarqueeText } from './ui/marquee-text';
import { EntryIcon } from './MediaEntry';

// ─── API ──────────────────────────────────────────────────────────────────────

const LEET_BASE   = 'https://hackerswithstyle.se/leet';
const PAGE_SIZE   = 1000;
const DOWNLOAD_DIR = '/sd/downloads/a64';

function leetFetch(path: string, query?: Record<string, string>) {
  const url = new URL(LEET_BASE + path);
  if (query) Object.entries(query).forEach(([k, v]) => url.searchParams.set(k, v));
  return fetch(url.toString(), {
    headers: {
      'User-Agent': 'Assembly Query',
      'Client-Id':  'meatloaf',
    },
  });
}

async function leetJson<T>(path: string, query?: Record<string, string>): Promise<T> {
  const res  = await leetFetch(path, query);
  const data = await res.json();
  if (!res.ok || (data as any).errorCode) throw new Error(`Leet API error ${(data as any).errorCode ?? res.status}`);
  return data as T;
}

// ─── Types ────────────────────────────────────────────────────────────────────

interface ContentItem {
  id: string;
  name: string;
  category: number;
  group?: string;
  handle?: string;
  year?: number;
  country?: string;
  event?: string;
  rating?: number;
  siteRating?: number;
  place?: number;
  compo?: number;
  updated?: string;
  released?: string;
}

interface ContentEntry {
  id: number;
  path: string;
  size: number;
  date: number;
}

interface CategoryMapping {
  id: number;
  name?: string;
  title?: string;
  [k: string]: unknown;
}

interface PresetValue {
  aqlKey: string;
  name?: string;
  id?: number;
  [k: string]: unknown;
}

interface PresetGroup {
  type: string;
  description: string;
  values: PresetValue[];
}

const PRESET_PREFIX: Record<string, string | null> = {
  repo:     'repo',
  category: 'category',
  subcat:   'subcat',
  rating:   'rating',
  type:     'type',
  date:     'year',
  latest:   'added',
  sort:     null,
  order:    null,
};

type SortField = 'name' | 'year' | 'rating';
type SortDir   = 'asc' | 'desc';

// ─── Props ────────────────────────────────────────────────────────────────────

interface SearchAssembly64Props {
  config: any;
  setConfig: (c: any) => void;
  onClose: () => void;
}

// ─── Module-level persistence ─────────────────────────────────────────────────

const _store = {
  query:          '',
  results:        [] as ContentItem[],
  offset:         0,
  hasMore:        false,
  hasSearched:    false,
  categoryFilter: null as number | null,
  scrollTop:      0,
  showFilter:     false,
  filterText:     '',
  sortField:      'name' as SortField,
  sortDir:        'asc'  as SortDir,
};

// ─── Helpers ──────────────────────────────────────────────────────────────────

function entryFilename(e: ContentEntry): string {
  return basename(e.path) || e.path;
}

function RatingStars({ value, max = 10 }: { value?: number; max?: number }) {
  if (!value) return null;
  const pct = Math.round((value / max) * 5);
  return (
    <span className="text-amber-400 text-xs">
      {'★'.repeat(pct)}{'☆'.repeat(5 - pct)}
    </span>
  );
}

// ─── AQL reference ───────────────────────────────────────────────────────────

const AQL_TERMS = [
  { term: 'name:',       label: 'Name',        example: 'name:manic*',      description: 'Title of the release. Supports wildcards (*).' },
  { term: 'group:',      label: 'Group',        example: 'group:triad',      description: 'Group or organization name.' },
  { term: 'handle:',     label: 'Handle',       example: 'handle:jco',       description: 'Author or creator handle.' },
  { term: 'year:',       label: 'Year',         example: 'year:1983',        description: 'Release year.' },
  { term: 'event:',      label: 'Event',        example: 'event:assembly',   description: 'Party or event name.' },
  { term: 'country:',    label: 'Country',      example: 'country:SE',       description: 'Country code (ISO 3166-1 alpha-2).' },
  { term: 'category:',   label: 'Category',     example: 'category:1',       description: 'Category ID number.' },
  { term: 'compo:',      label: 'Compo',        example: 'compo:demo',       description: 'Competition or compo type.' },
  { term: 'place:',      label: 'Place',        example: 'place:1',          description: 'Placement in competition (1 = winner).' },
  { term: 'rating:',     label: 'Rating',       example: 'rating:9',         description: 'Internal rating value.' },
  { term: 'siteRating:', label: 'Site Rating',  example: 'siteRating:8',     description: 'Site (user) rating value.' },
] as const;

// ─── Component ────────────────────────────────────────────────────────────────

export default function SearchAssembly64({ config, setConfig, onClose }: SearchAssembly64Props) {
  const scrollRef = useRef<HTMLDivElement>(null);
  const inputRef  = useRef<HTMLInputElement>(null);
  const [showAqlHelp, setShowAqlHelp] = useState(false);

  const [query, setQuery]               = useState(() => _store.query);
  const [results, setResults]           = useState<ContentItem[]>(() => _store.results);
  const [offset, setOffset]             = useState(() => _store.offset);
  const [hasMore, setHasMore]           = useState(() => _store.hasMore);
  const [hasSearched, setHasSearched]   = useState(() => _store.hasSearched);
  const [isSearching, setIsSearching]   = useState(false);
  const [isLoadingMore, setIsLoadingMore] = useState(false);
  const [searchError, setSearchError]   = useState<string | null>(null);
  const [categoryFilter, setCategoryFilter] = useState<number | null>(() => _store.categoryFilter);

  const [categories, setCategories]     = useState<CategoryMapping[]>([]);
  const [presets, setPresets]           = useState<PresetGroup[]>([]);
  const [activePreset, setActivePreset] = useState<PresetGroup | null>(null);

  const [selectedItem, setSelectedItem] = useState<ContentItem | null>(null);
  const [entries, setEntries]           = useState<ContentEntry[] | null>(null);
  const [loadingEntries, setLoadingEntries] = useState(false);

  const [actionItem, setActionItem]     = useState<ContentItem | null>(null);
  const [mountEntry, setMountEntry]     = useState<{ item: ContentItem; entry: ContentEntry } | null>(null);
  const [isMounting, setIsMounting]     = useState(false);
  const [mountProgress, setMountProgress] = useState<FetchProgress & { phase: 'fetching' | 'saving' | 'image' } | null>(null);

  const [showFilter, setShowFilter]     = useState(() => _store.showFilter);
  const [filterText, setFilterText]     = useState(() => _store.filterText);
  const [sortField, setSortField]       = useState<SortField>(() => _store.sortField);
  const [sortDir, setSortDir]           = useState<SortDir>(() => _store.sortDir);

  useEffect(() => {
    _store.query          = query;
    _store.results        = results;
    _store.offset         = offset;
    _store.hasMore        = hasMore;
    _store.hasSearched    = hasSearched;
    _store.categoryFilter = categoryFilter;
    _store.showFilter     = showFilter;
    _store.filterText     = filterText;
    _store.sortField      = sortField;
    _store.sortDir        = sortDir;
  }, [query, results, offset, hasMore, hasSearched, categoryFilter, showFilter, filterText, sortField, sortDir]);

  useEffect(() => {
    if (_store.scrollTop > 0 && scrollRef.current)
      scrollRef.current.scrollTop = _store.scrollTop;
  }, []);

  useEffect(() => {
    leetJson<CategoryMapping[]>('/search/categories').then(setCategories).catch(() => {});
    leetJson<PresetGroup[]>('/search/aql/presets').then(setPresets).catch(() => {});
  }, []);

  const categoryName = useMemo(() => {
    const map: Record<number, string> = {};
    for (const c of categories) map[c.id] = (c.name ?? c.title ?? String(c.id)) as string;
    return map;
  }, [categories]);

  // ── Search ──────────────────────────────────────────────────────────────────

  const doSearch = async (q: string, cat: number | null, fromOffset: number, append = false) => {
    if (!append) setIsSearching(true);
    else setIsLoadingMore(true);
    setSearchError(null);
    try {
      let aql = q.trim();
      if (cat !== null) aql = aql ? `${aql} category:${cat}` : `category:${cat}`;
      const data = await leetJson<ContentItem[]>(
        `/search/aql/${fromOffset}/${PAGE_SIZE}`,
        aql ? { query: aql } : undefined,
      );
      if (append) setResults(prev => [...prev, ...data]);
      else setResults(data);
      setOffset(fromOffset + data.length);
      setHasMore(data.length === PAGE_SIZE);
      setHasSearched(true);
    } catch (e: any) {
      setSearchError(e?.message ?? 'Search failed');
    } finally {
      setIsSearching(false);
      setIsLoadingMore(false);
    }
  };

  const handleSearch = () => {
    setFilterText('');
    const trimmed = query.trim();
    const q = trimmed && !trimmed.includes(':') ? `name:${trimmed}` : trimmed;
    if (q !== query) setQuery(q);
    doSearch(q, categoryFilter, 0);
  };
  const handleLoadMore = () => doSearch(query, categoryFilter, offset, true);

  const applyPreset = (group: PresetGroup, value: PresetValue) => {
    const prefix = PRESET_PREFIX[group.type];
    setActivePreset(null);
    if (prefix === null || prefix === undefined) return;
    const token   = value.aqlKey.includes(':') ? value.aqlKey : `${prefix}:${value.aqlKey}`;
    const trimmed = query.trim();
    const next    = trimmed ? `${trimmed} ${token}` : token;
    setQuery(next);
    doSearch(next, categoryFilter, 0);
  };

  // ── Filter / sort ────────────────────────────────────────────────────────────

  const toggleSort = (field: SortField) => {
    if (sortField === field) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
    else { setSortField(field); setSortDir('asc'); }
  };

  const visibleResults = useMemo(() => {
    const needle = filterText.trim().toLowerCase();
    let list = needle
      ? results.filter(r =>
          (r.name ?? '').toLowerCase().includes(needle) ||
          (r.group?.toLowerCase().includes(needle)) ||
          (r.handle?.toLowerCase().includes(needle))
        )
      : results;
    list = [...list].sort((a, b) => {
      let cmp: number;
      if (sortField === 'year')        cmp = (a.year ?? 0) - (b.year ?? 0);
      else if (sortField === 'rating') cmp = (a.siteRating ?? 0) - (b.siteRating ?? 0);
      else cmp = (a.name ?? '').localeCompare(b.name ?? '');
      return sortDir === 'asc' ? cmp : -cmp;
    });
    return list;
  }, [results, filterText, sortField, sortDir]);

  const activeFilters = filterText ? 1 : 0;

  // ── Item entries ─────────────────────────────────────────────────────────────

  const openItem = async (item: ContentItem) => {
    inputRef.current?.blur();
    setSelectedItem(item);
    setEntries(null);
    setLoadingEntries(true);
    try {
      const data = await leetJson<ContentEntry[] | { contentEntry: ContentEntry[] }>(
        `/search/entries/${item.id}/${item.category}`,
      );
      const list = Array.isArray(data) ? data : (data.contentEntry ?? []);
      setEntries(list);
    } catch (e: any) {
      toast.error(`Failed to load entries: ${e?.message ?? e}`);
      setEntries([]);
    } finally {
      setLoadingEntries(false);
    }
  };

  // ── Mount ────────────────────────────────────────────────────────────────────

  const mountOnDevice = async (deviceType: 'drive' | 'meatloaf', key: string) => {
    if (!mountEntry) return;
    const { item, entry } = mountEntry;
    setIsMounting(true);
    setMountProgress({ received: 0, total: 0, phase: 'fetching' });
    try {
      await clearDirectory(DOWNLOAD_DIR);
      const res = await leetFetch(`/search/bin/${item.id}/${item.category}/${entry.id}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await streamFetch(res, p => setMountProgress({ ...p, phase: 'fetching' }));
      const mainFname = entryFilename(entry);
      const dest = joinPath(DOWNLOAD_DIR, mainFname);
      setMountProgress(p => p && { ...p, phase: 'saving' });
      await putFileContents(dest, data);
      const imgEntry = (entries ?? []).find(e => e.id !== entry.id && /\.(png|jpg|jpeg|gif|bmp)$/i.test(e.path));
      if (imgEntry) {
        setMountProgress(p => p && { ...p, phase: 'image' });
        try {
          const imgRes = await leetFetch(`/search/bin/${item.id}/${item.category}/${imgEntry.id}`);
          if (imgRes.ok) {
            const imgData = await imgRes.arrayBuffer();
            const mainBase = mainFname.replace(/\.[^.]+$/, '');
            const imgExt = imgEntry.path.split('.').pop()?.toLowerCase() ?? 'png';
            await putFileContents(joinPath(DOWNLOAD_DIR, `${mainBase}.${imgExt}`), imgData);
          }
        } catch { /* non-fatal */ }
      }
      const newConfig = JSON.parse(JSON.stringify(config));
      if (!newConfig.devices) newConfig.devices = {};
      if (!newConfig.devices.iec) newConfig.devices.iec = {};
      if (!newConfig.devices.iec[key]) newConfig.devices.iec[key] = { type: deviceType };
      const dev = newConfig.devices.iec[key];
      dev.url = dest;
      delete dev.media_set;
      if (!dev.enabled) dev.enabled = 1;
      setConfig(newConfig);
      setMountEntry(null);
      setSelectedItem(null);
      toast.success(`Downloaded and mounted "${entryFilename(entry)}" on device #${key}`);
    } catch (e: any) {
      toast.error(`Mount failed: ${e?.message ?? e}`);
    } finally {
      setIsMounting(false);
      setMountProgress(null);
    }
  };

  const mountedUrls = useMemo(() => {
    const s = new Set<string>();
    for (const d of Object.values(config?.devices?.iec ?? {})) {
      const dev = d as any;
      if (dev?.url) s.add(dev.url);
    }
    return s;
  }, [config]);

  // ── Render ────────────────────────────────────────────────────────────────────

  return (
    <>
      <div className="flex flex-col h-full overflow-hidden">
        {/* Panel header — X on the left so it's never behind a top-right camera */}
        <div className="flex-shrink-0 flex items-center gap-2 border-b border-neutral-200/70 px-4">
          <button onClick={onClose} className="p-1.5 my-1.5 rounded-lg hover:bg-neutral-100 text-neutral-500 transition-colors flex-shrink-0" aria-label="Close search">
            <X className="w-5 h-5" />
          </button>
          <div className="flex items-center gap-2 flex-1 py-3 min-w-0">
            <img src={`${import.meta.env.BASE_URL}assets/favicon.a64.png`} className="w-5 h-5 flex-shrink-0 object-contain" alt="" aria-hidden="true" />
            <span className="text-sm font-semibold text-neutral-700">Assembly64</span>
          </div>
        </div>

        {/* Search input + presets + categories */}
        <div className="flex-shrink-0 px-4 pt-3 pb-3 border-b border-neutral-200/70">
          <div className="flex items-center gap-1.5 mb-2">
            <div className="relative flex-1">
              {isSearching
                ? <Loader2 className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-blue-500 animate-spin pointer-events-none" />
                : <button onClick={handleSearch} className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-blue-500 transition-colors" tabIndex={-1} title="Search"><Search className="w-4 h-4" /></button>
              }
              <input
                ref={inputRef}
                type="text"
                value={query}
                onChange={e => setQuery(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && !isSearching && handleSearch()}
                placeholder="name:manic* group:ultimate year:1983…"
                inputMode="search"
                enterKeyHint="search"
                className="w-full pl-9 pr-9 py-2.5 bg-neutral-100 border-0 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-colors"
                disabled={isSearching}
              />
              {query && (
                <button
                  onClick={() => { setQuery(''); setResults([]); setHasSearched(false); setSearchError(null); setOffset(0); setHasMore(false); setCategoryFilter(null); }}
                  className="absolute right-2.5 top-1/2 -translate-y-1/2 text-neutral-400 hover:text-neutral-600 transition-colors"
                  title="Clear"
                  tabIndex={-1}
                >
                  <X className="w-4 h-4" />
                </button>
              )}
            </div>
            <button
              onClick={() => setShowAqlHelp(true)}
              className="p-1.5 rounded-lg hover:bg-neutral-100 transition-colors flex-shrink-0 text-neutral-400 hover:text-neutral-600"
              title="AQL search help"
            >
              <HelpCircle className="w-4 h-4" />
            </button>
            {hasSearched && (
              <button
                onClick={() => setShowFilter(v => !v)}
                className={`relative p-1.5 rounded-lg transition-colors flex-shrink-0 ${showFilter ? 'bg-blue-100 text-blue-600' : 'hover:bg-neutral-100 text-neutral-400 hover:text-neutral-600'}`}
                title="Filter & sort results"
              >
                <SlidersHorizontal className="w-4 h-4" />
                {activeFilters > 0 && (
                  <span className="absolute -top-0.5 -right-0.5 w-3.5 h-3.5 rounded-full bg-blue-600 text-white text-[9px] flex items-center justify-center font-bold">
                    {activeFilters}
                  </span>
                )}
              </button>
            )}
          </div>

          {presets.length > 0 && (
            <div className="flex gap-1.5 mb-2 overflow-x-auto pb-0.5" style={{ scrollbarWidth: 'none' }}>
              {presets
                .filter(group => PRESET_PREFIX[group.type] !== null)
                .map((group, i) => (
                  <button
                    key={`${group.type}-${i}`}
                    onClick={() => setActivePreset(group)}
                    className="flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 transition-colors inline-flex items-center gap-1"
                  >
                    {group.description}
                    <ChevronDown className="w-3 h-3 opacity-60" />
                  </button>
                ))}
            </div>
          )}

          {categories.length > 0 && (
            <div className="flex gap-1.5 overflow-x-auto pb-0.5" style={{ scrollbarWidth: 'none' }}>
              <button
                onClick={() => { setCategoryFilter(null); if (hasSearched) doSearch(query, null, 0); }}
                className={`flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs font-medium transition-colors ${categoryFilter === null ? 'bg-blue-600 text-white' : 'bg-neutral-100 text-neutral-600 hover:bg-neutral-200'}`}
              >
                All
              </button>
              {categories.map(c => {
                const name = (c.name ?? c.title ?? String(c.id)) as string;
                return (
                  <button
                    key={c.id}
                    onClick={() => { setCategoryFilter(c.id); if (hasSearched) doSearch(query, c.id, 0); }}
                    className={`flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs font-medium transition-colors ${categoryFilter === c.id ? 'bg-blue-600 text-white' : 'bg-neutral-100 text-neutral-600 hover:bg-neutral-200'}`}
                  >
                    {name}
                  </button>
                );
              })}
            </div>
          )}
        </div>

        {/* Filter + sort bar */}
        <div className={`overflow-hidden flex-shrink-0 transition-all duration-200 ease-in-out ${showFilter ? 'max-h-24 opacity-100' : 'max-h-0 opacity-0'}`}>
          <div className="bg-neutral-50 border-b border-neutral-200 px-4 py-2 flex items-center gap-2">
            <div className="relative flex-1 min-w-0">
              <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-neutral-400 pointer-events-none" />
              <input
                type="text"
                value={filterText}
                onChange={e => setFilterText(e.target.value)}
                placeholder="Filter results…"
                className="w-full pl-7 pr-6 py-1 text-sm border border-neutral-300 rounded bg-white"
              />
              {filterText && (
                <button onClick={() => setFilterText('')} className="absolute right-2 top-1/2 -translate-y-1/2">
                  <X className="w-3.5 h-3.5 text-neutral-400" />
                </button>
              )}
            </div>
            {(['name', 'year', 'rating'] as SortField[]).map(f => (
              <button
                key={f}
                onClick={() => toggleSort(f)}
                className={`text-xs px-2 py-1 rounded border flex-shrink-0 ${sortField === f ? 'border-blue-400 bg-blue-50 text-blue-700' : 'border-neutral-300 bg-white text-neutral-600'}`}
              >
                {f === 'name' ? 'Name' : f === 'year' ? 'Year' : 'Rating'}
                {sortField === f ? (sortDir === 'asc' ? ' ↑' : ' ↓') : ''}
              </button>
            ))}
          </div>
        </div>

        {/* Body */}
        <div
          ref={scrollRef}
          className="flex-1 overflow-y-auto"
          onScroll={e => { _store.scrollTop = (e.currentTarget as HTMLDivElement).scrollTop; }}
        >
          {isSearching && !hasSearched && (
            <div className="flex flex-col items-center justify-center py-16 gap-3">
              <Loader2 className="w-8 h-8 text-blue-500 animate-spin" />
              <p className="text-sm text-neutral-500">Searching…</p>
            </div>
          )}

          {searchError && (
            <div className="p-6 text-center">
              <p className="text-sm text-red-500">{searchError}</p>
              <button onClick={handleSearch} className="mt-2 inline-flex items-center gap-1 text-xs text-blue-600">
                <RefreshCw className="w-3 h-3" /> Retry
              </button>
            </div>
          )}

          {!searchError && hasSearched && (
            <>
              {visibleResults.length > 0 ? (
                <>
                  <p className="text-xs text-neutral-400 px-4 py-2 border-b border-neutral-100 flex items-center gap-1.5">
                    {isSearching && <Loader2 className="w-3 h-3 animate-spin" />}
                    {visibleResults.length}{results.length !== visibleResults.length ? ` of ${results.length}` : ''} result{visibleResults.length !== 1 ? 's' : ''}{hasMore ? '+' : ''}
                  </p>
                  {visibleResults.map((item, idx) => {
                    const catLabel = categoryName[item.category] ?? `Cat ${item.category}`;
                    return (
                      <div
                        key={`${item.id}-${idx}`}
                        className="flex items-stretch border-b border-neutral-100 border-l-2 border-l-transparent hover:bg-blue-50 hover:border-l-blue-400 transition-colors"
                      >
                        <button
                          onClick={() => openItem(item)}
                          className="flex-1 pl-4 pr-2 py-3 flex items-center gap-3 text-left min-w-0"
                        >
                          <div className="flex-1 min-w-0">
                            <div className="flex items-center gap-2 flex-wrap">
                              <span className="text-sm font-medium text-neutral-900 truncate">{item.name}</span>
                              <span className="text-xs px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 font-mono flex-shrink-0">{catLabel}</span>
                              {item.place === 1 && <Trophy className="w-3.5 h-3.5 text-amber-500 flex-shrink-0" />}
                            </div>
                            <div className="flex items-center gap-2 mt-0.5 flex-wrap">
                              {item.group && (
                                <span className="text-xs text-neutral-400 flex items-center gap-0.5">
                                  <Users className="w-3 h-3" />{item.group}
                                </span>
                              )}
                              {item.handle && !item.group && (
                                <span className="text-xs text-neutral-400">{item.handle}</span>
                              )}
                              {!!item.year && (
                                <span className="text-xs text-neutral-400 flex items-center gap-0.5">
                                  <Calendar className="w-3 h-3" />{item.year}
                                </span>
                              )}
                              {item.event && (
                                <span className="text-xs text-neutral-400 truncate">{item.event}</span>
                              )}
                              {!!item.place && (
                                <span className="text-xs text-neutral-400">#{item.place}</span>
                              )}
                            </div>
                          </div>
                          <div className="flex-shrink-0 flex flex-col items-end gap-0.5">
                            {item.siteRating != null && <RatingStars value={item.siteRating} />}
                            <ChevronRight className="w-4 h-4 text-neutral-300" />
                          </div>
                        </button>
                        <button
                          onClick={() => { inputRef.current?.blur(); setActionItem(item); }}
                          className="px-3 text-neutral-300 hover:text-neutral-600 flex-shrink-0 transition-colors"
                          aria-label="Actions"
                        >
                          <MoreVertical className="w-4 h-4" />
                        </button>
                      </div>
                    );
                  })}

                  {hasMore && (
                    <div className="p-4 flex justify-center">
                      <button
                        onClick={handleLoadMore}
                        disabled={isLoadingMore}
                        className="px-6 py-2 bg-neutral-100 rounded-xl text-sm text-neutral-600 hover:bg-neutral-200 disabled:opacity-40 inline-flex items-center gap-2"
                      >
                        {isLoadingMore ? <Loader2 className="w-4 h-4 animate-spin" /> : null}
                        Load more
                      </button>
                    </div>
                  )}
                </>
              ) : (
                <div className="py-16 text-center">
                  <Search className="w-10 h-10 mx-auto mb-3 text-neutral-300" />
                  <p className="text-sm text-neutral-500">
                    {results.length > 0 ? 'No results match the current filter' : 'No results'}
                  </p>
                  {results.length > 0 && filterText && (
                    <button onClick={() => setFilterText('')} className="mt-2 text-xs text-blue-600 hover:underline">
                      Clear filter
                    </button>
                  )}
                </div>
              )}
            </>
          )}

          {!hasSearched && !isSearching && (
            <div className="py-16 text-center px-6">
              <Search className="w-10 h-10 mx-auto mb-3 text-neutral-300" />
              <p className="text-sm font-medium text-neutral-600 mb-1">Search the Assembly64 database</p>
              <p className="text-xs text-neutral-400">
                Use AQL syntax: <code className="bg-neutral-100 px-1 rounded">name:manic*</code>,{' '}
                <code className="bg-neutral-100 px-1 rounded">group:triad</code>,{' '}
                <code className="bg-neutral-100 px-1 rounded">year:1983</code>
              </p>
            </div>
          )}
        </div>
      </div>

      {/* Actions dialog */}
      <Dialog open={actionItem !== null} onOpenChange={open => !open && setActionItem(null)}>
        <DialogContent className="max-w-sm">
          <DialogHeader className="overflow-hidden min-w-0">
            <DialogTitle className="sr-only">{actionItem?.name}</DialogTitle>
            <p className="text-lg font-semibold leading-none pr-6 overflow-hidden min-w-0">
              <MarqueeText>{actionItem?.name}</MarqueeText>
            </p>
            <DialogDescription>
              {[actionItem?.group, actionItem?.year, actionItem?.event].filter(Boolean).join(' · ')}
            </DialogDescription>
          </DialogHeader>
          <div className="flex flex-col gap-2">
            <button
              onClick={() => { openItem(actionItem!); setActionItem(null); }}
              className="w-full text-left px-4 py-3 rounded border border-neutral-200 hover:bg-blue-50 hover:border-blue-300 inline-flex items-center gap-3"
            >
              <FolderOpen className="w-4 h-4 text-blue-600" /> <span>Browse Files</span>
            </button>
          </div>
        </DialogContent>
      </Dialog>

      {/* Item entries dialog */}
      <Dialog open={selectedItem !== null} onOpenChange={open => !open && setSelectedItem(null)}>
        <DialogContent className="max-w-sm flex flex-col max-h-[90dvh]">
          <DialogHeader className="flex-shrink-0 overflow-hidden min-w-0">
            <DialogTitle className="sr-only">{selectedItem?.name}</DialogTitle>
            <p className="text-lg font-semibold leading-none pr-6 overflow-hidden min-w-0">
              <MarqueeText>{selectedItem?.name}</MarqueeText>
            </p>
            <DialogDescription>
              {[selectedItem?.group, selectedItem?.year, selectedItem?.event].filter(Boolean).join(' · ')}
            </DialogDescription>
          </DialogHeader>

          <div className="overflow-y-auto flex-1 min-h-0">
            {loadingEntries && (
              <div className="flex justify-center py-8">
                <Loader2 className="w-6 h-6 animate-spin text-blue-500" />
              </div>
            )}
            {!loadingEntries && entries?.length === 0 && (
              <p className="text-sm text-neutral-400 text-center py-8">No files found</p>
            )}
            {!loadingEntries && entries && entries.length > 0 && (
              <div className="flex flex-col gap-2 py-1">
                {entries.map(entry => {
                  const fname     = entryFilename(entry);
                  const localPath = joinPath(DOWNLOAD_DIR, fname);
                  const isMounted = mountedUrls.has(localPath);
                  return (
                    <button
                      key={entry.id}
                      onClick={() => setMountEntry({ item: selectedItem!, entry })}
                      className={`px-4 py-3 rounded-lg border flex items-center gap-3 text-left w-full transition-colors ${isMounted ? 'border-blue-300 bg-blue-50' : 'border-neutral-200 hover:bg-blue-50 hover:border-blue-300'}`}
                    >
                      <EntryIcon entry={{ name: fname, type: 'file', path: entry.path, size: entry.size, lastModified: new Date(entry.date * 1000), contentType: '' }} />
                      <div className="flex-1 min-w-0">
                        <div className="text-sm font-medium text-neutral-800 truncate">{fname}</div>
                        <div className="text-xs text-neutral-400">{humanFileSize(entry.size)}</div>
                      </div>
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        </DialogContent>
      </Dialog>

      {/* Mount device picker */}
      <Dialog open={mountEntry !== null} onOpenChange={open => !open && setMountEntry(null)}>
        <DialogContent className="max-w-sm flex flex-col max-h-[90dvh]">
          <DialogHeader className="flex-shrink-0">
            <DialogTitle>Mount on Virtual Drive</DialogTitle>
            <DialogDescription className="truncate">
              {mountEntry ? entryFilename(mountEntry.entry) : ''}
            </DialogDescription>
          </DialogHeader>
          <div className="overflow-y-auto flex-1 min-h-0">
            {isMounting && (
              <div className="flex flex-col items-center justify-center py-10 gap-4">
                {mountProgress?.phase === 'saving' || mountProgress?.phase === 'image' ? (
                  <>
                    <Loader2 className="w-7 h-7 text-blue-500 animate-spin" />
                    <p className="text-sm text-neutral-500">{mountProgress.phase === 'image' ? 'Saving cover image…' : 'Saving to device…'}</p>
                  </>
                ) : (
                  <>
                    <p className="text-sm font-medium text-neutral-600">
                      {mountProgress?.total
                        ? `${humanFileSize(mountProgress.received)} / ${humanFileSize(mountProgress.total)}`
                        : mountProgress?.received ? humanFileSize(mountProgress.received) : 'Downloading…'}
                    </p>
                    <div className="w-56 h-1.5 bg-neutral-200 rounded-full overflow-hidden">
                      <div
                        className="h-full bg-blue-500 rounded-full transition-all duration-75"
                        style={{ width: mountProgress?.total ? `${Math.min(100, mountProgress.received / mountProgress.total * 100)}%` : '0%' }}
                      />
                    </div>
                  </>
                )}
              </div>
            )}
            {!isMounting && (() => {
              const allDevices = Object.entries(config?.devices?.iec ?? {});
              const drives = allDevices
                .filter(([, v]: [string, any]) => (v as any)?.type === 'drive')
                .map(([k, v]: [string, any]) => ({ type: 'drive' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
              const meatloafs = allDevices
                .filter(([, v]: [string, any]) => (v as any)?.type === 'meatloaf')
                .map(([k, v]: [string, any]) => ({ type: 'meatloaf' as const, key: k, base_url: v?.base_url as string | undefined, url: v?.url as string | undefined, enabled: !!v?.enabled }));
              const devices = [...drives, ...meatloafs];
              if (!devices.length)
                return <p className="text-sm text-neutral-500 text-center py-4">No drive devices found in config.</p>;
              return (
                <div className="flex flex-col gap-2">
                  {devices.map(dev => (
                    <button
                      key={`${dev.type}-${dev.key}`}
                      onClick={() => mountOnDevice(dev.type, dev.key)}
                      className="w-full text-left px-4 py-3 rounded border border-neutral-200 hover:bg-blue-50 hover:border-blue-300 flex items-center gap-3"
                    >
                      <HardDrive className={`w-5 h-5 flex-shrink-0 ${dev.enabled ? 'text-blue-500' : 'text-neutral-400'}`} />
                      <div className="min-w-0 flex-1">
                        <div className="font-medium text-sm">Device #{dev.key}</div>
                        {(dev.base_url || dev.url) && (
                          <div
                            className="text-xs text-neutral-500 overflow-hidden whitespace-nowrap"
                            style={{ direction: 'rtl', textOverflow: 'ellipsis' }}
                            title={[dev.base_url, dev.url].filter(Boolean).join('')}
                          >
                            <span style={{ direction: 'ltr', unicodeBidi: 'embed' }}>
                              {[dev.base_url, dev.url].filter(Boolean).join('')}
                            </span>
                          </div>
                        )}
                      </div>
                      {!dev.enabled && <span className="text-xs text-neutral-400 flex-shrink-0">disabled</span>}
                    </button>
                  ))}
                </div>
              );
            })()}
          </div>
        </DialogContent>
      </Dialog>

      {/* AQL help dialog */}
      <Dialog open={showAqlHelp} onOpenChange={setShowAqlHelp}>
        <DialogContent className="max-w-sm flex flex-col max-h-[90dvh]">
          <DialogHeader className="flex-shrink-0">
            <DialogTitle>AQL Search Terms</DialogTitle>
            <DialogDescription>Tap a term to insert it into the search field.</DialogDescription>
          </DialogHeader>
          <div className="overflow-y-auto flex-1 min-h-0 flex flex-col gap-1 py-1">
            {AQL_TERMS.map(({ term, label, example, description }) => (
              <button
                key={term}
                onClick={() => {
                  const trimmed = query.trimEnd();
                  const next = trimmed ? `${trimmed} ${term}` : term;
                  setQuery(next);
                  setShowAqlHelp(false);
                  setTimeout(() => {
                    const el = inputRef.current;
                    if (!el) return;
                    el.focus();
                    el.setSelectionRange(next.length, next.length);
                  }, 50);
                }}
                className="w-full text-left px-4 py-3 rounded-lg border border-neutral-200 hover:bg-blue-50 hover:border-blue-300 transition-colors"
              >
                <div className="flex items-baseline gap-2">
                  <span className="font-mono text-sm font-semibold text-blue-700">{term}</span>
                  <span className="text-xs text-neutral-400">{label}</span>
                  <span className="ml-auto font-mono text-xs text-neutral-400">{example}</span>
                </div>
                <p className="text-xs text-neutral-500 mt-0.5">{description}</p>
              </button>
            ))}
          </div>
        </DialogContent>
      </Dialog>

      {/* Preset values dialog */}
      <Dialog open={activePreset !== null} onOpenChange={open => !open && setActivePreset(null)}>
        <DialogContent className="max-w-sm flex flex-col max-h-[90dvh]">
          <DialogHeader className="flex-shrink-0">
            <DialogTitle>{activePreset?.description ?? 'Preset'}</DialogTitle>
            <DialogDescription>Tap a value to add it to your search.</DialogDescription>
          </DialogHeader>
          <div className="overflow-y-auto flex-1 min-h-0 flex flex-col gap-1 py-1">
            {activePreset?.values.map((v, i) => {
              const label  = v.name ?? v.aqlKey;
              const prefix = PRESET_PREFIX[activePreset.type];
              const token  = v.aqlKey.includes(':') ? v.aqlKey : `${prefix}:${v.aqlKey}`;
              return (
                <button
                  key={`${v.aqlKey}-${i}`}
                  onClick={() => applyPreset(activePreset, v)}
                  className="w-full text-left px-4 py-2.5 rounded-lg border border-neutral-200 hover:bg-blue-50 hover:border-blue-300 transition-colors flex items-center gap-2"
                >
                  <span className="text-sm text-neutral-800 flex-1 truncate">{label}</span>
                  <span className="font-mono text-xs text-neutral-400 flex-shrink-0">{token}</span>
                </button>
              );
            })}
          </div>
        </DialogContent>
      </Dialog>
    </>
  );
}
