import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import {
  fetchChannels, fetchSounds, fetchAnalytics, playSound, playUrl, setVolumeLive, getVolume,
  adminStatus, adminLogin, adminLogout, adminDelete, adminRename,
  fetchCategories, partyStart, partyStop, subscribeEvents,
  getSelectedChannels, setSelectedChannel,
} from './api';
import type { VoiceChannelInfo, Sound, Category, AnalyticsResponse } from './types';
import { getCookie, setCookie } from './cookies';

const THEMES = [
  { id: 'default', color: '#5865f2', label: 'Discord' },
  { id: 'purple', color: '#9b59b6', label: 'Midnight' },
  { id: 'forest', color: '#2ecc71', label: 'Forest' },
  { id: 'sunset', color: '#e67e22', label: 'Sunset' },
  { id: 'ocean', color: '#3498db', label: 'Ocean' },
];

const CAT_PALETTE = [
  '#3b82f6', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6',
  '#f97316', '#06b6d4', '#ef4444', '#a855f7', '#84cc16',
  '#d946ef', '#0ea5e9', '#f43f5e', '#10b981',
];

type Tab = 'all' | 'favorites' | 'recent';

export default function App() {
  /* ── Data ── */
  const [sounds, setSounds] = useState<Sound[]>([]);
  const [total, setTotal] = useState(0);
  const [folders, setFolders] = useState<Array<{ key: string; name: string; count: number }>>([]);
  const [categories, setCategories] = useState<Category[]>([]);
  const [analytics, setAnalytics] = useState<AnalyticsResponse>({
    totalSounds: 0,
    totalPlays: 0,
    mostPlayed: [],
  });

  /* ── Navigation ── */
  const [activeTab, setActiveTab] = useState<Tab>('all');
  const [activeFolder, setActiveFolder] = useState('');
  const [query, setQuery] = useState('');
  const [importUrl, setImportUrl] = useState('');
  const [importBusy, setImportBusy] = useState(false);

  /* ── Channels ── */
  const [channels, setChannels] = useState<VoiceChannelInfo[]>([]);
  const [selected, setSelected] = useState('');
  const selectedRef = useRef('');
  const [channelOpen, setChannelOpen] = useState(false);

  /* ── Playback ── */
  const [volume, setVolume] = useState(1);
  const [lastPlayed, setLastPlayed] = useState('');

  /* ── Preferences ── */
  const [favs, setFavs] = useState<Record<string, boolean>>({});
  const [theme, setTheme] = useState(() => localStorage.getItem('jb-theme') || 'default');
  const [cardSize, setCardSize] = useState(() => parseInt(localStorage.getItem('jb-card-size') || '110'));

  /* ── Party ── */
  const [chaosMode, setChaosMode] = useState(false);
  const [partyActiveGuilds, setPartyActiveGuilds] = useState<string[]>([]);
  const chaosModeRef = useRef(false);

  /* ── Admin ── */
  const [isAdmin, setIsAdmin] = useState(false);
  const [showAdmin, setShowAdmin] = useState(false);
  const [adminPwd, setAdminPwd] = useState('');
  const [adminSounds, setAdminSounds] = useState<Sound[]>([]);
  const [adminLoading, setAdminLoading] = useState(false);
  const [adminQuery, setAdminQuery] = useState('');
  const [adminSelection, setAdminSelection] = useState<Record<string, boolean>>({});
  const [renameTarget, setRenameTarget] = useState('');
  const [renameValue, setRenameValue] = useState('');

  /* ── UI ── */
  const [notification, setNotification] = useState<{ msg: string; type: 'info' | 'error' } | null>(null);
  const [clock, setClock] = useState('');
  const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; sound: Sound } | null>(null);
  const [refreshKey, setRefreshKey] = useState(0);

  /* ── Refs ── */
  useEffect(() => { chaosModeRef.current = chaosMode; }, [chaosMode]);
  useEffect(() => { selectedRef.current = selected; }, [selected]);

  /* ── Helpers ── */
  const notify = useCallback((msg: string, type: 'info' | 'error' = 'info') => {
    setNotification({ msg, type });
    setTimeout(() => setNotification(null), 3000);
  }, []);
  const soundKey = useCallback((s: Sound) => s.relativePath ?? s.fileName, []);
  const isMp3Url = useCallback((value: string) => {
    try {
      const parsed = new URL(value.trim());
      return parsed.pathname.toLowerCase().endsWith('.mp3');
    } catch {
      return false;
    }
  }, []);

  const guildId = selected ? selected.split(':')[0] : '';
  const channelId = selected ? selected.split(':')[1] : '';

  const selectedChannel = useMemo(() =>
    channels.find(c => `${c.guildId}:${c.channelId}` === selected),
  [channels, selected]);

  /* ── Clock ── */
  useEffect(() => {
    const update = () => {
      const now = new Date();
      const h = String(now.getHours()).padStart(2, '0');
      const m = String(now.getMinutes()).padStart(2, '0');
      const s = String(now.getSeconds()).padStart(2, '0');
      setClock(`${h}:${m}:${s}`);
    };
    update();
    const id = setInterval(update, 1000);
    return () => clearInterval(id);
  }, []);

  /* ── Init ── */
  useEffect(() => {
    (async () => {
      try {
        const [ch, selMap] = await Promise.all([fetchChannels(), getSelectedChannels()]);
        setChannels(ch);
        if (ch.length) {
          const g = ch[0].guildId;
          const serverCid = selMap[g];
          const match = serverCid && ch.find(x => x.guildId === g && x.channelId === serverCid);
          setSelected(match ? `${g}:${serverCid}` : `${ch[0].guildId}:${ch[0].channelId}`);
        }
      } catch (e: any) { notify(e?.message || 'Channel-Fehler', 'error'); }
      try { setIsAdmin(await adminStatus()); } catch { }
      try { const c = await fetchCategories(); setCategories(c.categories || []); } catch { }
    })();
  }, []);

  /* ── Theme ── */
  useEffect(() => {
    if (theme === 'default') document.body.removeAttribute('data-theme');
    else document.body.setAttribute('data-theme', theme);
    localStorage.setItem('jb-theme', theme);
  }, [theme]);

  /* ── Card size ── */
  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty('--card-size', cardSize + 'px');
    const ratio = cardSize / 110;
    r.style.setProperty('--card-emoji', Math.round(28 * ratio) + 'px');
    r.style.setProperty('--card-font', Math.max(9, Math.round(11 * ratio)) + 'px');
    localStorage.setItem('jb-card-size', String(cardSize));
  }, [cardSize]);

  /* ── SSE ── */
  useEffect(() => {
    const unsub = subscribeEvents((msg) => {
      if (msg?.type === 'party') {
        setPartyActiveGuilds(prev => {
          const s = new Set(prev);
          if (msg.active) s.add(msg.guildId); else s.delete(msg.guildId);
          return Array.from(s);
        });
      } else if (msg?.type === 'snapshot') {
        setPartyActiveGuilds(Array.isArray(msg.party) ? msg.party : []);
        try {
          const sel = msg?.selected || {};
          const g = selectedRef.current?.split(':')[0];
          if (g && sel[g]) setSelected(`${g}:${sel[g]}`);
        } catch { }
        try {
          const vols = msg?.volumes || {};
          const g = selectedRef.current?.split(':')[0];
          if (g && typeof vols[g] === 'number') setVolume(vols[g]);
        } catch { }
        try {
          const np = msg?.nowplaying || {};
          const g = selectedRef.current?.split(':')[0];
          if (g && typeof np[g] === 'string') setLastPlayed(np[g]);
        } catch { }
      } else if (msg?.type === 'channel') {
        const g = selectedRef.current?.split(':')[0];
        if (msg.guildId === g) setSelected(`${msg.guildId}:${msg.channelId}`);
      } else if (msg?.type === 'volume') {
        const g = selectedRef.current?.split(':')[0];
        if (msg.guildId === g && typeof msg.volume === 'number') setVolume(msg.volume);
      } else if (msg?.type === 'nowplaying') {
        const g = selectedRef.current?.split(':')[0];
        if (msg.guildId === g) setLastPlayed(msg.name || '');
      }
    });
    return () => { try { unsub(); } catch { } };
  }, []);

  useEffect(() => {
    setChaosMode(guildId ? partyActiveGuilds.includes(guildId) : false);
  }, [selected, partyActiveGuilds]);

  /* ── Data Fetch ── */
  useEffect(() => {
    (async () => {
      try {
        let folderParam = '__all__';
        if (activeTab === 'recent') folderParam = '__recent__';
        else if (activeFolder) folderParam = activeFolder;
        const s = await fetchSounds(query, folderParam, undefined, false);
        setSounds(s.items);
        setTotal(s.total);
        setFolders(s.folders);
      } catch (e: any) { notify(e?.message || 'Sounds-Fehler', 'error'); }
    })();
  }, [activeTab, activeFolder, query, refreshKey]);

  useEffect(() => {
    void loadAnalytics();
  }, [refreshKey]);

  /* ── Favs persistence ── */
  useEffect(() => {
    const c = getCookie('favs');
    if (c) try { setFavs(JSON.parse(c)); } catch { }
  }, []);

  useEffect(() => {
    try { setCookie('favs', JSON.stringify(favs)); } catch { }
  }, [favs]);

  /* ── Volume sync ── */
  useEffect(() => {
    if (selected) {
      (async () => {
        try { const v = await getVolume(guildId); setVolume(v); } catch { }
      })();
    }
  }, [selected]);

  /* ── Close dropdowns on outside click ── */
  useEffect(() => {
    const handler = () => { setChannelOpen(false); setCtxMenu(null); };
    document.addEventListener('click', handler);
    return () => document.removeEventListener('click', handler);
  }, []);

  useEffect(() => {
    if (showAdmin && isAdmin) {
      void loadAdminSounds();
    }
  }, [showAdmin, isAdmin]);

  /* ── Actions ── */
  async function loadAnalytics() {
    try {
      const data = await fetchAnalytics();
      setAnalytics(data);
    } catch { }
  }

  async function handlePlay(s: Sound) {
    if (!selected) return notify('Bitte einen Voice-Channel auswählen', 'error');
    try {
      await playSound(s.name, guildId, channelId, volume, s.relativePath);
      setLastPlayed(s.name);
      void loadAnalytics();
    } catch (e: any) { notify(e?.message || 'Play fehlgeschlagen', 'error'); }
  }

  async function handleUrlImport() {
    const trimmed = importUrl.trim();
    if (!trimmed) return notify('Bitte einen MP3-Link eingeben', 'error');
    if (!selected) return notify('Bitte einen Voice-Channel auswählen', 'error');
    if (!isMp3Url(trimmed)) return notify('Nur direkte MP3-Links sind erlaubt', 'error');
    setImportBusy(true);
    try {
      await playUrl(trimmed, guildId, channelId, volume);
      setImportUrl('');
      notify('MP3 importiert und abgespielt');
      setRefreshKey(k => k + 1);
      await loadAnalytics();
    } catch (e: any) {
      notify(e?.message || 'URL-Import fehlgeschlagen', 'error');
    } finally {
      setImportBusy(false);
    }
  }

  async function handleStop() {
    if (!selected) return;
    setLastPlayed('');
    try { await fetch(`/api/stop?guildId=${encodeURIComponent(guildId)}`, { method: 'POST' }); } catch { }
  }

  async function handleRandom() {
    if (!displaySounds.length || !selected) return;
    const rnd = displaySounds[Math.floor(Math.random() * displaySounds.length)];
    handlePlay(rnd);
  }

  async function toggleParty() {
    if (chaosMode) {
      await handleStop();
      try { await partyStop(guildId); } catch { }
    } else {
      if (!selected) return notify('Bitte einen Channel auswählen', 'error');
      try { await partyStart(guildId, channelId); } catch { }
    }
  }

  async function handleChannelSelect(ch: VoiceChannelInfo) {
    const v = `${ch.guildId}:${ch.channelId}`;
    setSelected(v);
    setChannelOpen(false);
    try { await setSelectedChannel(ch.guildId, ch.channelId); } catch { }
  }

  function toggleFav(key: string) {
    setFavs(prev => ({ ...prev, [key]: !prev[key] }));
  }

  async function loadAdminSounds() {
    setAdminLoading(true);
    try {
      const data = await fetchSounds('', '__all__', undefined, false);
      setAdminSounds(data.items || []);
    } catch (e: any) {
      notify(e?.message || 'Admin-Sounds konnten nicht geladen werden', 'error');
    } finally {
      setAdminLoading(false);
    }
  }

  function toggleAdminSelection(path: string) {
    setAdminSelection(prev => ({ ...prev, [path]: !prev[path] }));
  }

  function startRename(sound: Sound) {
    setRenameTarget(soundKey(sound));
    setRenameValue(sound.name);
  }

  function cancelRename() {
    setRenameTarget('');
    setRenameValue('');
  }

  async function submitRename() {
    if (!renameTarget) return;
    const baseName = renameValue.trim().replace(/\.(mp3|wav)$/i, '');
    if (!baseName) {
      notify('Bitte einen gültigen Namen eingeben', 'error');
      return;
    }
    try {
      await adminRename(renameTarget, baseName);
      notify('Sound umbenannt');
      cancelRename();
      setRefreshKey(k => k + 1);
      if (showAdmin) await loadAdminSounds();
    } catch (e: any) {
      notify(e?.message || 'Umbenennen fehlgeschlagen', 'error');
    }
  }

  async function deleteAdminPaths(paths: string[]) {
    if (paths.length === 0) return;
    try {
      await adminDelete(paths);
      notify(paths.length === 1 ? 'Sound gelöscht' : `${paths.length} Sounds gelöscht`);
      setAdminSelection({});
      cancelRename();
      setRefreshKey(k => k + 1);
      if (showAdmin) await loadAdminSounds();
    } catch (e: any) {
      notify(e?.message || 'Löschen fehlgeschlagen', 'error');
    }
  }

  async function handleAdminLogin() {
    try {
      const ok = await adminLogin(adminPwd);
      if (ok) {
        setIsAdmin(true);
        setAdminPwd('');
        notify('Admin eingeloggt');
      }
      else notify('Falsches Passwort', 'error');
    } catch { notify('Login fehlgeschlagen', 'error'); }
  }

  async function handleAdminLogout() {
    try {
      await adminLogout();
      setIsAdmin(false);
      setAdminSelection({});
      cancelRename();
      notify('Ausgeloggt');
    } catch { }
  }

  /* ── Computed ── */
  const displaySounds = useMemo(() => {
    if (activeTab === 'favorites') return sounds.filter(s => favs[s.relativePath ?? s.fileName]);
    return sounds;
  }, [sounds, activeTab, favs]);

  const favCount = useMemo(() => Object.values(favs).filter(Boolean).length, [favs]);

  const visibleFolders = useMemo(() =>
    folders.filter(f => !['__all__', '__recent__', '__top3__'].includes(f.key)),
  [folders]);

  const folderColorMap = useMemo(() => {
    const m: Record<string, string> = {};
    visibleFolders.forEach((f, i) => { m[f.key] = CAT_PALETTE[i % CAT_PALETTE.length]; });
    return m;
  }, [visibleFolders]);

  const firstOfInitial = useMemo(() => {
    const seen = new Set<string>();
    const result = new Set<number>();
    displaySounds.forEach((s, idx) => {
      const ch = s.name.charAt(0).toUpperCase();
      if (!seen.has(ch)) { seen.add(ch); result.add(idx); }
    });
    return result;
  }, [displaySounds]);

  const channelsByGuild = useMemo(() => {
    const groups: Record<string, VoiceChannelInfo[]> = {};
    channels.forEach(c => {
      if (!groups[c.guildName]) groups[c.guildName] = [];
      groups[c.guildName].push(c);
    });
    return groups;
  }, [channels]);

  const adminFilteredSounds = useMemo(() => {
    const q = adminQuery.trim().toLowerCase();
    if (!q) return adminSounds;
    return adminSounds.filter(s => {
      const key = soundKey(s).toLowerCase();
      return s.name.toLowerCase().includes(q)
        || (s.folder || '').toLowerCase().includes(q)
        || key.includes(q);
    });
  }, [adminQuery, adminSounds, soundKey]);

  const selectedAdminPaths = useMemo(() =>
    Object.keys(adminSelection).filter(k => adminSelection[k]),
  [adminSelection]);

  const selectedVisibleCount = useMemo(() =>
    adminFilteredSounds.filter(s => !!adminSelection[soundKey(s)]).length,
  [adminFilteredSounds, adminSelection, soundKey]);

  const allVisibleSelected = adminFilteredSounds.length > 0 && selectedVisibleCount === adminFilteredSounds.length;
  const analyticsTop = analytics.mostPlayed.slice(0, 3);
  const totalSoundsDisplay = analytics.totalSounds || total;

  const clockMain = clock.slice(0, 5);
  const clockSec = clock.slice(5);

  /* ════════════════════════════════════════════
     RENDER
     ════════════════════════════════════════════ */
  return (
    <div className="app">
      {chaosMode && <div className="party-overlay active" />}

      {/* ═══ TOPBAR ═══ */}
      <header className="topbar">
        <div className="topbar-left">
          <div className="app-logo">
            <span className="material-icons" style={{ fontSize: 16, color: 'white' }}>music_note</span>
          </div>
          <span className="app-title">Soundboard</span>

          {/* Channel Dropdown */}
          <div className="channel-dropdown" onClick={e => e.stopPropagation()}>
            <button
              className={`channel-btn ${channelOpen ? 'open' : ''}`}
              onClick={() => setChannelOpen(!channelOpen)}
            >
              <span className="material-icons cb-icon">headset</span>
              {selected && <span className="channel-status" />}
              <span className="channel-label">{selectedChannel?.channelName || 'Channel...'}</span>
              <span className={`material-icons chevron`}>expand_more</span>
            </button>
            {channelOpen && (
              <div className="channel-menu visible">
                {Object.entries(channelsByGuild).map(([guild, chs]) => (
                  <React.Fragment key={guild}>
                    <div className="channel-menu-header">{guild}</div>
                    {chs.map(ch => (
                      <div
                        key={`${ch.guildId}:${ch.channelId}`}
                        className={`channel-option ${`${ch.guildId}:${ch.channelId}` === selected ? 'active' : ''}`}
                        onClick={() => handleChannelSelect(ch)}
                      >
                        <span className="material-icons co-icon">volume_up</span>
                        {ch.channelName}
                      </div>
                    ))}
                  </React.Fragment>
                ))}
                {channels.length === 0 && (
                  <div className="channel-option" style={{ color: 'var(--text-faint)', cursor: 'default' }}>
                    Keine Channels verfügbar
                  </div>
                )}
              </div>
            )}
          </div>
        </div>

        <div className="clock-wrap">
          <div className="clock">{clockMain}<span className="clock-seconds">{clockSec}</span></div>
        </div>

        <div className="topbar-right">
          {lastPlayed && (
            <div className="now-playing">
              <div className="np-waves active">
                <div className="np-wave-bar" /><div className="np-wave-bar" />
                <div className="np-wave-bar" /><div className="np-wave-bar" />
              </div>
              <span className="np-name">{lastPlayed}</span>
            </div>
          )}
          {selected && (
            <div className="connection">
              <span className="conn-dot" />
              Verbunden
            </div>
          )}
          <button
            className={`admin-btn-icon ${isAdmin ? 'active' : ''}`}
            onClick={() => setShowAdmin(true)}
            title="Admin"
          >
            <span className="material-icons">settings</span>
          </button>
        </div>
      </header>

      {/* ═══ TOOLBAR ═══ */}
      <div className="toolbar">
        <div className="cat-tabs">
          <button
            className={`cat-tab ${activeTab === 'all' ? 'active' : ''}`}
            onClick={() => { setActiveTab('all'); setActiveFolder(''); }}
          >
            Alle
            <span className="tab-count">{total}</span>
          </button>
          <button
            className={`cat-tab ${activeTab === 'recent' ? 'active' : ''}`}
            onClick={() => { setActiveTab('recent'); setActiveFolder(''); }}
          >
            Neu hinzugefügt
          </button>
          <button
            className={`cat-tab ${activeTab === 'favorites' ? 'active' : ''}`}
            onClick={() => { setActiveTab('favorites'); setActiveFolder(''); }}
          >
            Favoriten
            {favCount > 0 && <span className="tab-count">{favCount}</span>}
          </button>
        </div>

        <div className="search-wrap">
          <span className="material-icons search-icon">search</span>
          <input
            className="search-input"
            type="text"
            placeholder="Suchen..."
            value={query}
            onChange={e => setQuery(e.target.value)}
          />
          {query && (
            <button className="search-clear" onClick={() => setQuery('')}>
              <span className="material-icons" style={{ fontSize: 14 }}>close</span>
            </button>
          )}
        </div>

        <div className="url-import-wrap">
          <span className="material-icons url-import-icon">link</span>
          <input
            className="url-import-input"
            type="url"
            placeholder="MP3-URL einfügen..."
            value={importUrl}
            onChange={e => setImportUrl(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') void handleUrlImport(); }}
          />
          <button
            className="url-import-btn"
            onClick={() => { void handleUrlImport(); }}
            disabled={importBusy}
            title="MP3 importieren"
          >
            {importBusy ? 'Lädt...' : 'Download'}
          </button>
        </div>

        <div className="toolbar-spacer" />

        <div className="volume-control">
          <span
            className="material-icons vol-icon"
            onClick={() => {
              const newVol = volume > 0 ? 0 : 0.5;
              setVolume(newVol);
              if (guildId) setVolumeLive(guildId, newVol).catch(() => {});
            }}
          >
            {volume === 0 ? 'volume_off' : volume < 0.5 ? 'volume_down' : 'volume_up'}
          </span>
          <input
            type="range"
            className="vol-slider"
            min={0}
            max={1}
            step={0.01}
            value={volume}
            onChange={async e => {
              const v = parseFloat(e.target.value);
              setVolume(v);
              if (guildId) try { await setVolumeLive(guildId, v); } catch { }
            }}
            style={{ '--vol': `${Math.round(volume * 100)}%` } as React.CSSProperties}
          />
          <span className="vol-pct">{Math.round(volume * 100)}%</span>
        </div>

        <button className="tb-btn random" onClick={handleRandom} title="Zufälliger Sound">
          <span className="material-icons tb-icon">shuffle</span>
          Random
        </button>

        <button
          className={`tb-btn party ${chaosMode ? 'active' : ''}`}
          onClick={toggleParty}
          title="Party Mode"
        >
          <span className="material-icons tb-icon">{chaosMode ? 'celebration' : 'auto_awesome'}</span>
          {chaosMode ? 'Party!' : 'Party'}
        </button>

        <button className="tb-btn stop" onClick={handleStop} title="Alle stoppen">
          <span className="material-icons tb-icon">stop</span>
          Stop
        </button>

        <div className="size-control" title="Button-Größe">
          <span className="material-icons sc-icon">grid_view</span>
          <input
            type="range"
            className="size-slider"
            min={80}
            max={160}
            value={cardSize}
            onChange={e => setCardSize(parseInt(e.target.value))}
          />
        </div>

        <div className="theme-selector">
          {THEMES.map(t => (
            <div
              key={t.id}
              className={`theme-dot ${theme === t.id ? 'active' : ''}`}
              style={{ background: t.color }}
              title={t.label}
              onClick={() => setTheme(t.id)}
            />
          ))}
        </div>
      </div>

      <div className="analytics-strip">
        <div className="analytics-card">
          <span className="material-icons analytics-icon">library_music</span>
          <div className="analytics-copy">
            <span className="analytics-label">Sounds gesamt</span>
            <strong className="analytics-value">{totalSoundsDisplay}</strong>
          </div>
        </div>

        <div className="analytics-card analytics-wide">
          <span className="material-icons analytics-icon">leaderboard</span>
          <div className="analytics-copy">
            <span className="analytics-label">Most Played</span>
            <div className="analytics-top-list">
              {analyticsTop.length === 0 ? (
                <span className="analytics-muted">Noch keine Plays</span>
              ) : (
                analyticsTop.map((item, idx) => (
                  <span className="analytics-chip" key={item.relativePath}>
                    {idx + 1}. {item.name} ({item.count})
                  </span>
                ))
              )}
            </div>
          </div>
        </div>
      </div>

      {/* ═══ FOLDER CHIPS ═══ */}
      {activeTab === 'all' && visibleFolders.length > 0 && (
        <div className="category-strip">
          {visibleFolders.map(f => {
            const color = folderColorMap[f.key] || '#888';
            const isActive = activeFolder === f.key;
            return (
              <button
                key={f.key}
                className={`cat-chip ${isActive ? 'active' : ''}`}
                onClick={() => setActiveFolder(isActive ? '' : f.key)}
                style={isActive ? { borderColor: color, color } : undefined}
              >
                <span className="cat-dot" style={{ background: color }} />
                {f.name.replace(/\s*\(\d+\)\s*$/, '')}
                <span className="cat-count">{f.count}</span>
              </button>
            );
          })}
        </div>
      )}

      {/* ═══ MAIN ═══ */}
      <main className="main">
        {displaySounds.length === 0 ? (
          <div className="empty-state visible">
            <div className="empty-emoji">{activeTab === 'favorites' ? '⭐' : '🔇'}</div>
            <div className="empty-title">
              {activeTab === 'favorites'
                ? 'Noch keine Favoriten'
                : query
                  ? `Kein Sound für "${query}" gefunden`
                  : 'Keine Sounds vorhanden'}
            </div>
            <div className="empty-desc">
              {activeTab === 'favorites'
                ? 'Klick den Stern auf einem Sound!'
                : 'Hier gibt\'s noch nichts zu hören.'}
            </div>
          </div>
        ) : (
          <div className="sound-grid">
            {displaySounds.map((s, idx) => {
              const key = s.relativePath ?? s.fileName;
              const isFav = !!favs[key];
              const isPlaying = lastPlayed === s.name;
              const isNew = s.isRecent || s.badges?.includes('new');
              const initial = s.name.charAt(0).toUpperCase();
              const showInitial = firstOfInitial.has(idx);
              const folderColor = s.folder ? (folderColorMap[s.folder] || 'var(--accent)') : 'var(--accent)';

              return (
                <div
                  key={key}
                  className={`sound-card ${isPlaying ? 'playing' : ''} ${showInitial ? 'has-initial' : ''}`}
                  style={{ animationDelay: `${Math.min(idx * 20, 400)}ms` }}
                  onClick={e => {
                    const card = e.currentTarget;
                    const rect = card.getBoundingClientRect();
                    const ripple = document.createElement('div');
                    ripple.className = 'ripple';
                    const sz = Math.max(rect.width, rect.height);
                    ripple.style.width = ripple.style.height = sz + 'px';
                    ripple.style.left = (e.clientX - rect.left - sz / 2) + 'px';
                    ripple.style.top = (e.clientY - rect.top - sz / 2) + 'px';
                    card.appendChild(ripple);
                    setTimeout(() => ripple.remove(), 500);
                    handlePlay(s);
                  }}
                  onContextMenu={e => {
                    e.preventDefault();
                    e.stopPropagation();
                    setCtxMenu({
                      x: Math.min(e.clientX, window.innerWidth - 170),
                      y: Math.min(e.clientY, window.innerHeight - 140),
                      sound: s,
                    });
                  }}
                  title={`${s.name}${s.folder ? ` (${s.folder})` : ''}`}
                >
                  {isNew && <span className="new-badge">NEU</span>}
                  <span
                    className={`fav-star ${isFav ? 'active' : ''}`}
                    onClick={e => { e.stopPropagation(); toggleFav(key); }}
                  >
                    <span className="material-icons fav-icon">{isFav ? 'star' : 'star_border'}</span>
                  </span>
                  {showInitial && <span className="sound-emoji" style={{ color: folderColor }}>{initial}</span>}
                  <span className="sound-name">{s.name}</span>
                  {s.folder && <span className="sound-duration">{s.folder}</span>}
                  <div className="playing-indicator">
                    <div className="wave-bar" /><div className="wave-bar" />
                    <div className="wave-bar" /><div className="wave-bar" />
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </main>

      {/* ═══ CONTEXT MENU ═══ */}
      {ctxMenu && (
        <div
          className="ctx-menu visible"
          style={{ left: ctxMenu.x, top: ctxMenu.y }}
          onClick={e => e.stopPropagation()}
        >
          <div className="ctx-item" onClick={() => { handlePlay(ctxMenu.sound); setCtxMenu(null); }}>
            <span className="material-icons ctx-icon">play_arrow</span>
            Abspielen
          </div>
          <div className="ctx-item" onClick={() => {
            toggleFav(ctxMenu.sound.relativePath ?? ctxMenu.sound.fileName);
            setCtxMenu(null);
          }}>
            <span className="material-icons ctx-icon">
              {favs[ctxMenu.sound.relativePath ?? ctxMenu.sound.fileName] ? 'star' : 'star_border'}
            </span>
            Favorit
          </div>
          {isAdmin && (
            <>
              <div className="ctx-sep" />
              <div className="ctx-item danger" onClick={async () => {
                const path = ctxMenu.sound.relativePath ?? ctxMenu.sound.fileName;
                await deleteAdminPaths([path]);
                setCtxMenu(null);
              }}>
                <span className="material-icons ctx-icon">delete</span>
                Löschen
              </div>
            </>
          )}
        </div>
      )}

      {/* ═══ TOAST ═══ */}
      {notification && (
        <div className={`toast ${notification.type}`}>
          <span className="material-icons toast-icon">
            {notification.type === 'error' ? 'error_outline' : 'check_circle'}
          </span>
          {notification.msg}
        </div>
      )}

      {/* ═══ ADMIN PANEL ═══ */}
      {showAdmin && (
        <div className="admin-overlay" onClick={e => { if (e.target === e.currentTarget) setShowAdmin(false); }}>
          <div className="admin-panel">
            <h3>
              Admin
              <button className="admin-close" onClick={() => setShowAdmin(false)}>
                <span className="material-icons" style={{ fontSize: 18 }}>close</span>
              </button>
            </h3>
            {!isAdmin ? (
              <div>
                <div className="admin-field">
                  <label>Passwort</label>
                  <input
                    type="password"
                    value={adminPwd}
                    onChange={e => setAdminPwd(e.target.value)}
                    onKeyDown={e => e.key === 'Enter' && handleAdminLogin()}
                    placeholder="Admin-Passwort..."
                  />
                </div>
                <button className="admin-btn-action primary" onClick={handleAdminLogin}>Login</button>
              </div>
            ) : (
              <div className="admin-shell">
                <div className="admin-header-row">
                  <p className="admin-status">Eingeloggt als Admin</p>
                  <div className="admin-actions-inline">
                    <button
                      className="admin-btn-action outline"
                      onClick={() => { void loadAdminSounds(); }}
                      disabled={adminLoading}
                    >
                      Aktualisieren
                    </button>
                    <button className="admin-btn-action outline" onClick={handleAdminLogout}>Logout</button>
                  </div>
                </div>

                <div className="admin-field admin-search-field">
                  <label>Sounds verwalten</label>
                  <input
                    type="text"
                    value={adminQuery}
                    onChange={e => setAdminQuery(e.target.value)}
                    placeholder="Nach Name, Ordner oder Pfad filtern..."
                  />
                </div>

                <div className="admin-bulk-row">
                  <label className="admin-select-all">
                    <input
                      type="checkbox"
                      checked={allVisibleSelected}
                      onChange={e => {
                        const checked = e.target.checked;
                        const next = { ...adminSelection };
                        adminFilteredSounds.forEach(s => { next[soundKey(s)] = checked; });
                        setAdminSelection(next);
                      }}
                    />
                    <span>Alle sichtbaren auswählen ({selectedVisibleCount}/{adminFilteredSounds.length})</span>
                  </label>

                  <button
                    className="admin-btn-action danger"
                    disabled={selectedAdminPaths.length === 0}
                    onClick={async () => {
                      if (!window.confirm(`Wirklich ${selectedAdminPaths.length} Sound(s) löschen?`)) return;
                      await deleteAdminPaths(selectedAdminPaths);
                    }}
                  >
                    Ausgewählte löschen
                  </button>
                </div>

                <div className="admin-list-wrap">
                  {adminLoading ? (
                    <div className="admin-empty">Lade Sounds...</div>
                  ) : adminFilteredSounds.length === 0 ? (
                    <div className="admin-empty">Keine Sounds gefunden.</div>
                  ) : (
                    <div className="admin-list">
                      {adminFilteredSounds.map(sound => {
                        const key = soundKey(sound);
                        const editing = renameTarget === key;
                        return (
                          <div className="admin-item" key={key}>
                            <label className="admin-item-check">
                              <input
                                type="checkbox"
                                checked={!!adminSelection[key]}
                                onChange={() => toggleAdminSelection(key)}
                              />
                            </label>

                            <div className="admin-item-main">
                              <div className="admin-item-name">{sound.name}</div>
                              <div className="admin-item-meta">
                                {sound.folder ? `Ordner: ${sound.folder}` : 'Root'}
                                {' · '}
                                {key}
                              </div>
                              {editing && (
                                <div className="admin-rename-row">
                                  <input
                                    value={renameValue}
                                    onChange={e => setRenameValue(e.target.value)}
                                    onKeyDown={e => {
                                      if (e.key === 'Enter') void submitRename();
                                      if (e.key === 'Escape') cancelRename();
                                    }}
                                    placeholder="Neuer Name..."
                                  />
                                  <button className="admin-btn-action primary" onClick={() => { void submitRename(); }}>
                                    Speichern
                                  </button>
                                  <button className="admin-btn-action outline" onClick={cancelRename}>
                                    Abbrechen
                                  </button>
                                </div>
                              )}
                            </div>

                            {!editing && (
                              <div className="admin-item-actions">
                                <button className="admin-btn-action outline" onClick={() => startRename(sound)}>
                                  Umbenennen
                                </button>
                                <button
                                  className="admin-btn-action danger ghost"
                                  onClick={async () => {
                                    if (!window.confirm(`Sound "${sound.name}" löschen?`)) return;
                                    await deleteAdminPaths([key]);
                                  }}
                                >
                                  Löschen
                                </button>
                              </div>
                            )}
                          </div>
                        );
                      })}
                    </div>
                  )}
                </div>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}
