// ============ TRAY BROWSER ============
// Center-stage browser for finding music, building Tonight, and loading decks.

const TRAY_SOURCES = [
  { id: 'local', label: 'My uploads', icon: 'upload', hint: 'Your analyzed files' },
  { id: 'youtube', label: 'YouTube', icon: 'youtube', hint: 'Music-only search' },
  { id: 'soundcloud', label: 'SoundCloud', icon: 'cloud', hint: 'Tracks and mixes' },
  { id: 'spotify', label: 'Spotify', icon: 'music', hint: 'Open Spotify panel' },
  { id: 'compatible', label: 'Goes well with', icon: 'sparkles', hint: 'BPM compatible' },
  { id: 'history', label: 'Played tonight', icon: 'clock', hint: 'Session history' },
  { id: 'ai', label: 'AI Suggest', icon: 'globe', hint: 'Vibe-based ideas' },
];

const TRAY_FALLBACK_TRACKS = [
  { title: 'Espresso', artist: 'Sabrina Carpenter', bpm: 104, key: '7A', genres: ['pop', 'dance'] },
  { title: 'Houdini', artist: 'Dua Lipa', bpm: 117, key: '6A', genres: ['pop', 'dance'] },
  { title: 'Training Season', artist: 'Dua Lipa', bpm: 123, key: '4A', genres: ['pop', 'dance'] },
  { title: 'Move', artist: 'Adam Port, Stryv, Keinemusik', bpm: 120, key: '8A', genres: ['house', 'afro-house'] },
  { title: 'Mwaki', artist: 'Zerb, Sofiya Nzau', bpm: 121, key: '10A', genres: ['house', 'afro-house'] },
  { title: 'Stumblin In', artist: 'Cyril', bpm: 126, key: '8B', genres: ['dance', 'house'] },
  { title: 'Lose Control', artist: 'Meduza, Becky Hill, Goodboys', bpm: 124, key: '7A', genres: ['house', 'dance'] },
  { title: 'Where You Are', artist: 'John Summit, Hayla', bpm: 126, key: '5A', genres: ['house', 'tech-house'] },
  { title: 'Rhyme Dust', artist: 'MK, Dom Dolla', bpm: 128, key: '9A', genres: ['tech-house', 'house'] },
  { title: 'Miracle', artist: 'Calvin Harris, Ellie Goulding', bpm: 143, key: '4A', genres: ['dance', 'trance'] },
  { title: 'Calm Down', artist: 'Rema, Selena Gomez', bpm: 107, key: '8A', genres: ['afrobeats', 'pop'] },
  { title: 'Water', artist: 'Tyla', bpm: 117, key: '6A', genres: ['afrobeats', 'pop'] },
  { title: 'Unavailable', artist: 'Davido, Musa Keys', bpm: 100, key: '9A', genres: ['afrobeats'] },
  { title: 'Pepas', artist: 'Farruko', bpm: 130, key: '12A', genres: ['latin', 'dance'] },
  { title: 'Tití Me Preguntó', artist: 'Bad Bunny', bpm: 107, key: '4A', genres: ['latin'] },
  { title: 'Murder She Wrote', artist: 'Chaka Demus & Pliers', bpm: 95, key: '7A', genres: ['dancehall', 'reggae'] },
  { title: 'One Dance', artist: 'Drake, Wizkid, Kyla', bpm: 104, key: '10A', genres: ['hip-hop', 'afrobeats'] },
  { title: 'Headlines', artist: 'Drake', bpm: 100, key: '12A', genres: ['hip-hop'] },
  { title: 'Levitating', artist: 'Dua Lipa', bpm: 103, key: '7A', genres: ['pop', 'disco'] },
  { title: 'Lady (Hear Me Tonight)', artist: 'Modjo', bpm: 126, key: '11A', genres: ['disco', 'house'] },
  { title: 'Music Sounds Better With You', artist: 'Stardust', bpm: 124, key: '5A', genres: ['disco', 'house'] },
  { title: 'Innerbloom', artist: 'Rufus Du Sol', bpm: 122, key: '8A', genres: ['melodic-house'] },
  { title: 'Glue', artist: 'Bicep', bpm: 130, key: '7A', genres: ['melodic-house', 'breakbeat'] },
  { title: 'Afraid To Feel', artist: 'LF System', bpm: 127, key: '4A', genres: ['disco', 'house'] },
];

function trayDuration(seconds) {
  const n = Number(seconds);
  if (!Number.isFinite(n) || n <= 0) return '-';
  return `${Math.floor(n / 60)}:${String(Math.floor(n % 60)).padStart(2, '0')}`;
}

function trayRowKey(row) {
  return String(row?.url || row?.sourceKey || `${row?.source || 'track'}:${row?.artist || ''}:${row?.title || ''}`).toLowerCase();
}

function trayNormalize(row, source = 'local') {
  if (!row) return null;
  return {
    id: row.id || row.ytId || row.url || `tray-${Date.now()}-${Math.random().toString(16).slice(2)}`,
    title: row.title || 'Untitled track',
    artist: row.artist || row.channel || row.uploader || '-',
    bpm: Number.isFinite(Number(row.bpm)) ? Number(row.bpm) : null,
    key: row.key || '-',
    duration: row.duration || null,
    thumb: row.thumb || row.thumbnail || '',
    url: row.url || '',
    source: row.source || source,
    sourceKey: row.sourceKey || row.url || row.ytId || '',
    hint: row.hint || '',
    ref: row.ref === null ? null : (row.ref || row),
  };
}

function trayPersonaGenres(tasteProfile) {
  const genres = Array.isArray(tasteProfile?.genres) ? tasteProfile.genres : [];
  return genres.map(g => String(g).toLowerCase()).slice(0, 5);
}

function buildSmartSuggestions(vibe, tasteProfile) {
  const text = String(vibe || '').toLowerCase();
  const tasteGenres = trayPersonaGenres(tasteProfile);
  const matched = TRAY_FALLBACK_TRACKS
    .map((track, index) => {
      const genres = track.genres || [];
      let score = 0;
      genres.forEach((genre) => {
        if (tasteGenres.includes(genre)) score += 4;
        if (text.includes(genre)) score += 5;
      });
      if (/house|club|party|dance|weekend|peak/i.test(text) && genres.some(g => ['house', 'dance', 'tech-house'].includes(g))) score += 2;
      if (/warm|sunset|rooftop|chill|smooth/i.test(text) && (track.bpm || 0) <= 122) score += 2;
      if (/energy|banger|peak|festival/i.test(text) && (track.bpm || 0) >= 120) score += 2;
      return { ...track, score, originalIndex: index };
    })
    .sort((a, b) => (b.score - a.score) || (a.originalIndex - b.originalIndex));
  const primary = matched.filter(t => t.score > 0).slice(0, 7);
  const surprises = matched.filter(t => t.score === 0).slice(0, 5);
  return [...primary, ...surprises].slice(0, 12).map((track, index) => ({
    ...trayNormalize({
      ...track,
      id: `ai-${track.artist}-${track.title}`.replace(/\W+/g, '-').toLowerCase(),
      source: 'ai',
      hint: index < primary.length ? 'Matched to your taste' : 'Smart surprise',
      ref: null,
    }, 'ai'),
    searchQuery: `${track.title} ${track.artist} official audio`,
  }));
}

function parseAiRows(raw, tasteProfile, vibe) {
  const cleaned = String(raw || '').replace(/```json\s*|\s*```/g, '').trim();
  const parsed = JSON.parse(cleaned);
  if (!Array.isArray(parsed)) throw new Error('AI did not return a list');
  return parsed.slice(0, 12).map((item, index) => trayNormalize({
    id: `ai-live-${Date.now()}-${index}`,
    title: item.title,
    artist: item.artist,
    bpm: item.bpm,
    key: item.key,
    source: 'ai',
    hint: item.hint || 'AI suggestion',
    ref: null,
    searchQuery: `${item.title || ''} ${item.artist || ''} official audio`.trim(),
  }, 'ai')).filter(Boolean);
}

function TrayBrowser({
  open,
  onClose,
  queue = [],
  history = [],
  deckA,
  deckB,
  onLoadTo,
  onAddUrl,
  onSpotifyOpen,
  tonightSet = [],
  onTonightChange,
  onUploadClick,
  initialSource,
  tasteProfile,
  onTasteSignal,
}) {
  const [activeSource, setActiveSource] = useState('local');
  const [searchQuery, setSearchQuery] = useState('');
  const [externalResults, setExternalResults] = useState([]);
  const [externalLoading, setExternalLoading] = useState(false);
  const [externalError, setExternalError] = useState('');
  const [addingKey, setAddingKey] = useState('');
  const [vibe, setVibe] = useState('');
  const [aiBusy, setAiBusy] = useState(false);
  const [aiResults, setAiResults] = useState([]);
  const [aiError, setAiError] = useState('');
  const [dragIdx, setDragIdx] = useState(null);
  const [dragOverIdx, setDragOverIdx] = useState(null);
  const searchSeqRef = useRef(0);
  const vibeRef = useRef(null);
  const wasOpen = useRef(false);

  const isExternal = activeSource === 'youtube' || activeSource === 'soundcloud';
  const cleanSearch = searchQuery.trim();
  const looksLikeUrl = /^https?:\/\//i.test(cleanSearch) || /^www\./i.test(cleanSearch);

  useEffect(() => {
    if (open && !wasOpen.current) {
      if (initialSource) setActiveSource(initialSource);
      setExternalError('');
      setAiError('');
      const h = setTimeout(() => vibeRef.current?.focus(), 120);
      wasOpen.current = true;
      return () => clearTimeout(h);
    }
    if (!open) wasOpen.current = false;
  }, [open, initialSource]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => {
      if (e.key === 'Escape') {
        e.preventDefault();
        onClose?.();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  const runExternalSearch = useCallback(async (query = searchQuery, source = activeSource) => {
    const q = String(query || '').trim();
    if (!q || /^https?:\/\//i.test(q) || /^www\./i.test(q)) return [];
    const seq = ++searchSeqRef.current;
    setExternalLoading(true);
    setExternalError('');
    try {
      onTasteSignal?.(q);
      const raw = source === 'soundcloud'
        ? await BackendClient.searchSoundCloud(q, 10, { musicOnly: true })
        : await BackendClient.searchYouTube(q, 10, { tracksOnly: true });
      const rows = (raw || []).map(item => trayNormalize(item, source)).filter(Boolean);
      if (seq === searchSeqRef.current) setExternalResults(rows);
      return rows;
    } catch {
      if (seq === searchSeqRef.current) {
        setExternalResults([]);
        setExternalError(source === 'soundcloud'
          ? 'SoundCloud search is warming up. Paste a SoundCloud link or try YouTube.'
          : 'YouTube search is warming up. Paste a YouTube link or try again.');
      }
      return [];
    } finally {
      if (seq === searchSeqRef.current) setExternalLoading(false);
    }
  }, [activeSource, onTasteSignal, searchQuery]);

  useEffect(() => {
    if (!open || !isExternal) return;
    if (!cleanSearch || cleanSearch.length < 2 || looksLikeUrl) {
      searchSeqRef.current += 1;
      setExternalResults([]);
      setExternalError('');
      setExternalLoading(false);
      return;
    }
    const h = setTimeout(() => runExternalSearch(cleanSearch, activeSource), 320);
    return () => clearTimeout(h);
  }, [open, isExternal, cleanSearch, looksLikeUrl, activeSource, runExternalSearch]);

  const localRows = useMemo(() => {
    const query = searchQuery.trim().toLowerCase();
    return (queue || [])
      .filter(t => !t.loading && !t.analyzing && t.buffer)
      .filter(t => !query || `${t.title || ''} ${t.artist || ''}`.toLowerCase().includes(query))
      .map(t => trayNormalize({ ...t, ref: t }, t.source || 'local'));
  }, [queue, searchQuery]);

  const historyRows = useMemo(() => {
    const query = searchQuery.trim().toLowerCase();
    return (history || [])
      .filter(Boolean)
      .filter(t => !query || `${t.title || ''} ${t.artist || ''}`.toLowerCase().includes(query))
      .map(t => ({ ...trayNormalize({ ...t, ref: t }, t.source || 'local'), played: true }));
  }, [history, searchQuery]);

  const compatibleRows = useMemo(() => {
    const targets = [deckA, deckB].filter(d => d?.track && d.playing && d.track.bpm);
    if (!targets.length) return [];
    const target = targets[0].track;
    const min = target.bpm * 0.94;
    const max = target.bpm * 1.06;
    return (queue || [])
      .filter(t => t.buffer && t.bpm >= min && t.bpm <= max && t.id !== target.id)
      .map(t => ({ ...trayNormalize({ ...t, ref: t }, t.source || 'local'), compat: true }));
  }, [deckA, deckB, queue]);

  const rows = useMemo(() => {
    if (activeSource === 'ai') return aiResults;
    if (activeSource === 'local') return localRows;
    if (activeSource === 'history') return historyRows;
    if (activeSource === 'compatible') return compatibleRows;
    if (isExternal) return externalResults;
    return [];
  }, [activeSource, aiResults, localRows, historyRows, compatibleRows, isExternal, externalResults]);

  const addToTonight = (row) => {
    const normalized = trayNormalize(row, row.source || activeSource);
    if (!normalized) return;
    const key = trayRowKey(normalized);
    if ((tonightSet || []).some(t => trayRowKey(t) === key)) return;
    onTonightChange?.([...(tonightSet || []), {
      ...normalized,
      id: normalized.id || key,
      ref: normalized.ref?.buffer ? normalized.ref : null,
    }]);
  };

  const removeFromTonight = (idOrKey) => {
    onTonightChange?.((tonightSet || []).filter(t => t.id !== idOrKey && trayRowKey(t) !== idOrKey));
  };

  const moveTonight = (from, to) => {
    if (from === to) return;
    const next = [...(tonightSet || [])];
    const [moved] = next.splice(from, 1);
    next.splice(to, 0, moved);
    onTonightChange?.(next);
  };

  const askAi = async () => {
    const promptVibe = vibe.trim() || 'action packed party set for tonight';
    setAiBusy(true);
    setAiError('');
    setAiResults([]);
    try {
      let rows = [];
      if (window.claude?.complete) {
        const prompt = [
          `You are helping a DJ build a party playlist for: "${promptVibe}".`,
          'Suggest 10 real, recognizable music tracks from real artists.',
          'Return only JSON: [{"title":"","artist":"","bpm":120,"key":"8A","hint":""}].',
        ].join('\n');
        const raw = await window.claude.complete(prompt);
        rows = parseAiRows(raw, tasteProfile, promptVibe);
      }
      if (!rows.length) rows = buildSmartSuggestions(promptVibe, tasteProfile);
      setAiResults(rows);
      setActiveSource('ai');
      onTasteSignal?.(promptVibe);
    } catch {
      const fallback = buildSmartSuggestions(promptVibe, tasteProfile);
      setAiResults(fallback);
      setActiveSource('ai');
      setAiError('Using smart local suggestions while the AI helper warms up.');
    } finally {
      setAiBusy(false);
    }
  };

  const clearAi = () => {
    setAiResults([]);
    setAiError('');
    setActiveSource('youtube');
    setSearchQuery('');
  };

  const resolveAiRow = async (row) => {
    const query = row.searchQuery || `${row.title} ${row.artist} official audio`;
    setActiveSource('youtube');
    setSearchQuery(query);
    const found = await runExternalSearch(query, 'youtube');
    return found?.[0] || null;
  };

  const loadRow = async (side, row) => {
    if (!row) return;
    const key = trayRowKey(row);
    setAddingKey(`${side}:${key}`);
    try {
      if (row.ref?.buffer) {
        await onLoadTo?.(side, row.ref);
        onClose?.();
        return;
      }
      if (row.url) {
        await onAddUrl?.(row.url, side);
        onClose?.();
        return;
      }
      if (row.source === 'ai') {
        const resolved = await resolveAiRow(row);
        if (resolved?.url) {
          await onAddUrl?.(resolved.url, side);
          onClose?.();
        }
      }
    } finally {
      setAddingKey('');
    }
  };

  const submitExternal = async () => {
    if (!cleanSearch) return;
    if (looksLikeUrl) {
      setAddingKey(cleanSearch);
      try {
        const url = /^www\./i.test(cleanSearch) ? `https://${cleanSearch}` : cleanSearch;
        await onAddUrl?.(url);
        setSearchQuery('');
        setExternalResults([]);
      } finally {
        setAddingKey('');
      }
      return;
    }
    await runExternalSearch(cleanSearch, activeSource);
  };

  const selectSource = (source) => {
    if (source === 'spotify') {
      onSpotifyOpen?.();
      onClose?.();
      return;
    }
    setActiveSource(source);
    setExternalError('');
    setSearchQuery('');
    if (source !== 'ai') setAiError('');
  };

  if (!open) return null;

  const renderEmpty = () => {
    const source = TRAY_SOURCES.find(s => s.id === activeSource);
    let title = 'No tracks here yet';
    let sub = 'Search or upload music to start building Tonight.';
    let icon = source?.icon || 'music';
    if (activeSource === 'local') {
      title = 'Your uploads will appear here';
      sub = 'Upload MP3/WAV files for full beatmatch, loops, EQ and FX.';
      icon = 'upload';
    } else if (activeSource === 'compatible') {
      title = deckA?.track || deckB?.track ? 'No compatible uploads yet' : 'Start a deck to see matches';
      sub = 'This finds uploaded tracks close to the playing BPM.';
      icon = 'sparkles';
    } else if (activeSource === 'history') {
      title = 'Nothing played yet';
      sub = 'Tracks you load tonight will show up here.';
      icon = 'clock';
    } else if (activeSource === 'ai') {
      title = 'Describe the vibe up top';
      sub = 'We will suggest familiar hits plus a few smart surprises.';
      icon = 'globe';
    } else if (isExternal) {
      title = `Search ${activeSource === 'soundcloud' ? 'SoundCloud' : 'YouTube'} music`;
      sub = 'Only music-like results are shown so the deck stays clean.';
      icon = activeSource === 'soundcloud' ? 'cloud' : 'youtube';
    }
    return (
      <div className="tb-empty">
        <div className="tb-empty-icon"><Icon name={icon} size={28} /></div>
        <div className="tb-empty-title">{title}</div>
        <div className="tb-empty-sub">{sub}</div>
        {activeSource === 'local' && <button className="tb-link" onClick={onUploadClick}>Browse files</button>}
      </div>
    );
  };

  return (
    <div className="tb-backdrop" onClick={onClose}>
      <div className="tb-tray" onClick={(e) => e.stopPropagation()} role="dialog" aria-label="Music browser">
        <div className="tb-head">
          <div className="tb-head-left">
            <div className="tb-head-title">Tonight's Playlist</div>
            <div className="tb-head-sub">Search the internet, build your set, load either deck</div>
          </div>
          <div className="tb-vibe">
            <Icon name="sparkles" size={14} />
            <input
              ref={vibeRef}
              className="tb-vibe-input"
              placeholder='Describe the vibe, e.g. "high energy house, familiar hits"'
              value={vibe}
              onChange={(e) => setVibe(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') askAi(); }}
              disabled={aiBusy}
            />
            <button className="tb-vibe-go" onClick={askAi} disabled={aiBusy}>
              {aiBusy ? 'Thinking' : 'Suggest'}
            </button>
            {aiResults.length > 0 && <button className="tb-vibe-clear" onClick={clearAi} title="Clear suggestions">x</button>}
          </div>
          <button className="tb-close" onClick={onClose} title="Close">
            <Icon name="x" size={16} />
          </button>
        </div>

        {aiError && <div className="tb-ai-error">{aiError}</div>}

        <div className="tb-body">
          <div className="tb-rail">
            <div className="tb-rail-section-label">Sources</div>
            {TRAY_SOURCES.slice(0, 4).map((source) => (
              <button
                key={source.id}
                className={`tb-rail-item ${activeSource === source.id ? 'is-active' : ''}`}
                onClick={() => selectSource(source.id)}
              >
                <Icon name={source.icon} size={14} />
                <div className="tb-rail-text">
                  <div className="tb-rail-name">{source.label}</div>
                  <div className="tb-rail-hint">{source.hint}</div>
                </div>
                {source.id === 'local' && localRows.length > 0 && <span className="tb-rail-count">{localRows.length}</span>}
              </button>
            ))}
            <div className="tb-rail-divider" />
            <div className="tb-rail-section-label">Smart</div>
            {TRAY_SOURCES.slice(4).map((source) => (
              <button
                key={source.id}
                className={`tb-rail-item ${activeSource === source.id ? 'is-active' : ''}`}
                onClick={() => selectSource(source.id)}
              >
                <Icon name={source.icon} size={14} />
                <div className="tb-rail-text">
                  <div className="tb-rail-name">{source.label}</div>
                  <div className="tb-rail-hint">{source.hint}</div>
                </div>
                {source.id === 'history' && historyRows.length > 0 && <span className="tb-rail-count">{historyRows.length}</span>}
                {source.id === 'ai' && aiResults.length > 0 && <span className="tb-rail-count">{aiResults.length}</span>}
              </button>
            ))}
            <div className="tb-rail-foot">
              <button className="tb-rail-upload" onClick={onUploadClick}>
                <Icon name="upload" size={12} /> Upload files
              </button>
            </div>
          </div>

          <div className="tb-results">
            <div className="tb-search-bar">
              <Icon name={isExternal ? (activeSource === 'soundcloud' ? 'cloud' : 'youtube') : 'search'} size={12} />
              <input
                className="tb-search-input"
                placeholder={isExternal
                  ? `Search ${activeSource === 'soundcloud' ? 'SoundCloud' : 'YouTube'} music or paste a link`
                  : activeSource === 'history'
                    ? 'Filter played tracks'
                    : activeSource === 'ai'
                      ? 'AI suggestions are below'
                      : 'Filter this list'}
                value={searchQuery}
                onChange={(e) => setSearchQuery(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter' && isExternal) submitExternal(); }}
                disabled={activeSource === 'ai'}
              />
              {isExternal && (
                <button className="tb-search-submit" onClick={submitExternal} disabled={!cleanSearch || externalLoading || !!addingKey}>
                  {externalLoading ? 'Searching' : looksLikeUrl ? 'Add' : 'Search'}
                </button>
              )}
              {searchQuery && <button className="tb-search-clear" onClick={() => setSearchQuery('')}>x</button>}
            </div>
            {isExternal && (
              <div className="tb-ext-note">
                {activeSource === 'youtube'
                  ? 'YouTube is instant-play mode: volume, crossfader, queue and automix work. Full beatmatch, loops, EQ and FX are for uploads or licensed-ready tracks.'
                  : 'SoundCloud uses stream mode when direct audio is unavailable. Uploads give full pro controls.'}
              </div>
            )}
            <div className="tb-results-scroll">
              {externalLoading && <div className="tb-search-state">Searching music...</div>}
              {externalError && <div className="tb-search-state error">{externalError}</div>}
              {!externalLoading && !externalError && rows.length === 0 ? renderEmpty() : null}
              {!externalLoading && !externalError && rows.length > 0 && (
                <table className="tb-results-table">
                  <thead>
                    <tr>
                      <th style={{ width: '38%' }}>Track</th>
                      <th style={{ width: '20%' }}>Artist</th>
                      <th style={{ width: '64px' }}>BPM</th>
                      <th style={{ width: '64px' }}>Key</th>
                      <th style={{ width: '72px' }}>Length</th>
                      <th style={{ width: '184px' }}></th>
                    </tr>
                  </thead>
                  <tbody>
                    {rows.map((row, index) => {
                      const key = trayRowKey(row);
                      const inTonight = (tonightSet || []).some(t => trayRowKey(t) === key);
                      const loadingA = addingKey === `a:${key}`;
                      const loadingB = addingKey === `b:${key}`;
                      return (
                        <tr key={`${key}-${index}`} className={`${inTonight ? 'in-tonight' : ''} ${row.played ? 'played' : ''}`}>
                          <td className="tb-cell-title">
                            <span className={`tb-src-dot src-${row.source}`} />
                            {row.thumb && <img className="tb-row-thumb" src={row.thumb} alt="" />}
                            <span className="tb-title">{row.title}</span>
                            {row.hint && <span className="tb-hint">{row.hint}</span>}
                          </td>
                          <td className="tb-cell-artist">{row.artist}</td>
                          <td className="tb-cell-num">{row.bpm ? Math.round(row.bpm) : '-'}</td>
                          <td className="tb-cell-num">{row.key || '-'}</td>
                          <td className="tb-cell-num">{trayDuration(row.duration)}</td>
                          <td className="tb-cell-actions">
                            <button className={`tb-add ${inTonight ? 'is-added' : ''}`} onClick={() => inTonight ? removeFromTonight(row.id || key) : addToTonight(row)}>
                              {inTonight ? 'Added' : '+ Add'}
                            </button>
                            <button className="tb-deck-btn a" onClick={() => loadRow('a', row)} disabled={!!addingKey}>
                              {loadingA ? '...' : 'A'}
                            </button>
                            <button className="tb-deck-btn b" onClick={() => loadRow('b', row)} disabled={!!addingKey}>
                              {loadingB ? '...' : 'B'}
                            </button>
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              )}
            </div>
          </div>

          <div className="tb-tonight">
            <div className="tb-tonight-head">
              <div className="tb-tonight-title"><Icon name="music" size={12} /> Tonight</div>
              <div className="tb-tonight-count">
                {(tonightSet || []).length === 0 ? 'empty' : `${tonightSet.length} track${tonightSet.length === 1 ? '' : 's'}`}
              </div>
            </div>
            {(tonightSet || []).length === 0 ? (
              <div className="tb-tonight-empty">
                <div className="tb-tonight-empty-icon"><Icon name="plus" size={20} /></div>
                <div>Click <b>+ Add</b> on any result to shape the night.</div>
              </div>
            ) : (
              <div className="tb-tonight-list">
                {(tonightSet || []).map((track, index) => (
                  <div
                    key={`${track.id || trayRowKey(track)}-${index}`}
                    className={`tb-tonight-item ${dragIdx === index ? 'dragging' : ''} ${dragOverIdx === index ? 'over' : ''}`}
                    draggable
                    onDragStart={() => setDragIdx(index)}
                    onDragOver={(e) => { e.preventDefault(); setDragOverIdx(index); }}
                    onDrop={() => { if (dragIdx != null) moveTonight(dragIdx, index); setDragIdx(null); setDragOverIdx(null); }}
                    onDragEnd={() => { setDragIdx(null); setDragOverIdx(null); }}
                  >
                    <div className="tb-tonight-num">{index + 1}</div>
                    <div className="tb-tonight-info">
                      <div className="tb-tonight-name">{track.title}</div>
                      <div className="tb-tonight-meta">{track.artist || '-'} · {track.bpm ? `${Math.round(track.bpm)} BPM` : '-'} · {track.key || '-'}</div>
                    </div>
                    <button className="tb-tonight-deck a" onClick={() => loadRow('a', track)} disabled={!!addingKey}>A</button>
                    <button className="tb-tonight-deck b" onClick={() => loadRow('b', track)} disabled={!!addingKey}>B</button>
                    <button className="tb-tonight-remove" onClick={() => removeFromTonight(track.id || trayRowKey(track))} title="Remove">x</button>
                  </div>
                ))}
              </div>
            )}
            {(tonightSet || []).length > 0 && (
              <div className="tb-tonight-foot">
                <button
                  className="tb-tonight-load"
                  onClick={async () => {
                    if (tonightSet[0]) await loadRow('a', tonightSet[0]);
                    if (tonightSet[1]) await loadRow('b', tonightSet[1]);
                  }}
                  disabled={!!addingKey}
                >
                  <Icon name="play" size={12} /> Load first two
                </button>
                <button className="tb-tonight-clear" onClick={() => onTonightChange?.([])}>Clear</button>
              </div>
            )}
          </div>
        </div>

        <div className="tb-foot">
          <span className="tb-kbd">SPACE</span> open / close
          <span className="tb-foot-sep">.</span>
          <span className="tb-kbd">ESC</span> close
          <span className="tb-foot-sep">.</span>
          Drag Tonight to reorder
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { TrayBrowser });
