import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react';
import './soundboard.css';

/* ══════════════════════════════════════════════════════════════════
   INLINED TYPES (from original types.ts)
   ══════════════════════════════════════════════════════════════════ */

type Sound = {
  fileName: string;
  name: string;
  folder?: string;
  relativePath?: string;
  isRecent?: boolean;
  badges?: string[];
};

type SoundsResponse = {
  items: Sound[];
  total: number;
  folders: Array<{ key: string; name: string; count: number }>;
  categories?: Category[];
  fileCategories?: Record<string, string[]>;
};

type VoiceChannelInfo = {
  guildId: string;
  guildName: string;
  channelId: string;
  channelName: string;
  members?: number;
  selected?: boolean;
};

type Category = { id: string; name: string; color?: string; sort?: number };

type AnalyticsItem = {
  name: string;
  relativePath: string;
  count: number;
};

type AnalyticsResponse = {
  totalSounds: number;
  totalPlays: number;
  mostPlayed: AnalyticsItem[];
};

/* ══════════════════════════════════════════════════════════════════
   INLINED COOKIE HELPERS (from original cookies.ts)
   ══════════════════════════════════════════════════════════════════ */

function setCookie(name: string, value: string, days = 365): void {
  const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; expires=${expires}; path=/; SameSite=Lax`;
}

function getCookie(name: string): string | null {
  const key = `${encodeURIComponent(name)}=`;
  const parts = document.cookie.split(';');
  for (const part of parts) {
    const trimmed = part.trim();
    if (trimmed.startsWith(key)) {
      return decodeURIComponent(trimmed.slice(key.length));
    }
  }
  return null;
}

/* ══════════════════════════════════════════════════════════════════
   INLINED API FUNCTIONS (from original api.ts)
   All endpoints prefixed with /api/soundboard/ instead of /api/
   ══════════════════════════════════════════════════════════════════ */

const API_BASE = '/api/soundboard';

async function fetchSounds(q?: string, folderKey?: string, categoryId?: string, fuzzy?: boolean): Promise<SoundsResponse> {
  const url = new URL(`${API_BASE}/sounds`, window.location.origin);
  if (q) url.searchParams.set('q', q);
  if (folderKey !== undefined) url.searchParams.set('folder', folderKey);
  if (categoryId) url.searchParams.set('categoryId', categoryId);
  if (typeof fuzzy === 'boolean') url.searchParams.set('fuzzy', fuzzy ? '1' : '0');
  const res = await fetch(url.toString());
  if (!res.ok) throw new Error('Fehler beim Laden der Sounds');
  return res.json();
}

async function fetchAnalytics(): Promise<AnalyticsResponse> {
  const res = await fetch(`${API_BASE}/analytics`);
  if (!res.ok) throw new Error('Fehler beim Laden der Analytics');
  return res.json();
}

async function fetchCategories() {
  const res = await fetch(`${API_BASE}/categories`, { credentials: 'include' });
  if (!res.ok) throw new Error('Fehler beim Laden der Kategorien');
  return res.json();
}

async function fetchChannels(): Promise<VoiceChannelInfo[]> {
  const res = await fetch(`${API_BASE}/channels`);
  if (!res.ok) throw new Error('Fehler beim Laden der Channels');
  return res.json();
}

async function getSelectedChannels(): Promise<Record<string, string>> {
  const res = await fetch(`${API_BASE}/selected-channels`);
  if (!res.ok) throw new Error('Fehler beim Laden der Channel-Auswahl');
  const data = await res.json();
  return data?.selected || {};
}

async function apiSetSelectedChannel(guildId: string, channelId: string): Promise<void> {
  const res = await fetch(`${API_BASE}/selected-channel`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ guildId, channelId })
  });
  if (!res.ok) throw new Error('Channel-Auswahl setzen fehlgeschlagen');
}

async function apiPlaySound(soundName: string, guildId: string, channelId: string, volume: number, relativePath?: string): Promise<void> {
  const res = await fetch(`${API_BASE}/play`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ soundName, guildId, channelId, volume, relativePath })
  });
  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data?.error || 'Play fehlgeschlagen');
  }
}

async function apiPlayUrl(url: string, guildId: string, channelId: string, volume: number, filename?: string): Promise<{ saved?: string }> {
  const res = await fetch(`${API_BASE}/play-url`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ url, guildId, channelId, volume, filename })
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data?.error || 'Play-URL fehlgeschlagen');
  return data;
}

async function apiDownloadUrl(url: string, filename?: string): Promise<{ saved?: string }> {
  const res = await fetch(`${API_BASE}/download-url`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ url, filename })
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data?.error || 'Download fehlgeschlagen');
  return data;
}

async function apiPartyStart(guildId: string, channelId: string) {
  const res = await fetch(`${API_BASE}/party/start`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ guildId, channelId })
  });
  if (!res.ok) throw new Error('Partymode Start fehlgeschlagen');
}

async function apiPartyStop(guildId: string) {
  const res = await fetch(`${API_BASE}/party/stop`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ guildId })
  });
  if (!res.ok) throw new Error('Partymode Stop fehlgeschlagen');
}

async function apiSetVolumeLive(guildId: string, volume: number): Promise<void> {
  const res = await fetch(`${API_BASE}/volume`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ guildId, volume })
  });
  if (!res.ok) {
    const data = await res.json().catch(() => ({}));
    throw new Error(data?.error || 'Volume aendern fehlgeschlagen');
  }
}

async function apiGetVolume(guildId: string): Promise<number> {
  const url = new URL(`${API_BASE}/volume`, window.location.origin);
  url.searchParams.set('guildId', guildId);
  const res = await fetch(url.toString());
  if (!res.ok) throw new Error('Fehler beim Laden der Lautstaerke');
  const data = await res.json();
  return typeof data?.volume === 'number' ? data.volume : 1;
}

async function apiAdminDelete(paths: string[]): Promise<void> {
  const res = await fetch(`${API_BASE}/admin/sounds/delete`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
    body: JSON.stringify({ paths })
  });
  if (!res.ok) throw new Error('Loeschen fehlgeschlagen');
}

async function apiAdminRename(from: string, to: string): Promise<string> {
  const res = await fetch(`${API_BASE}/admin/sounds/rename`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
    body: JSON.stringify({ from, to })
  });
  if (!res.ok) throw new Error('Umbenennen fehlgeschlagen');
  const data = await res.json();
  return data?.to as string;
}

function apiUploadFile(
  file: File,
  onProgress: (pct: number) => void,
): Promise<string> {
  return new Promise((resolve, reject) => {
    const form = new FormData();
    form.append('files', file);
    const xhr = new XMLHttpRequest();
    xhr.open('POST', `${API_BASE}/upload`);
    xhr.upload.onprogress = e => {
      if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
    };
    xhr.onload = () => {
      if (xhr.status === 200) {
        try {
          const data = JSON.parse(xhr.responseText);
          resolve(data.files?.[0]?.name ?? file.name);
        } catch { resolve(file.name); }
      } else {
        try { reject(new Error(JSON.parse(xhr.responseText).error)); }
        catch { reject(new Error(`HTTP ${xhr.status}`)); }
      }
    };
    xhr.onerror = () => reject(new Error('Netzwerkfehler'));
    xhr.send(form);
  });
}

function apiUploadFileWithName(
  file: File,
  customName: string,
  onProgress: (pct: number) => void,
): Promise<string> {
  return new Promise((resolve, reject) => {
    const form = new FormData();
    form.append('files', file);
    if (customName) form.append('customName', customName);
    const xhr = new XMLHttpRequest();
    xhr.open('POST', `${API_BASE}/upload`);
    xhr.upload.onprogress = e => {
      if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
    };
    xhr.onload = () => {
      if (xhr.status === 200) {
        try {
          const data = JSON.parse(xhr.responseText);
          resolve(data.files?.[0]?.name ?? file.name);
        } catch { resolve(file.name); }
      } else {
        try { reject(new Error(JSON.parse(xhr.responseText).error)); }
        catch { reject(new Error(`HTTP ${xhr.status}`)); }
      }
    };
    xhr.onerror = () => reject(new Error('Netzwerkfehler'));
    xhr.send(form);
  });
}

/* ══════════════════════════════════════════════════════════════════
   CONSTANTS
   ══════════════════════════════════════════════════════════════════ */


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

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

type UploadItem = {
  id: string;
  file: File;
  status: 'waiting' | 'uploading' | 'done' | 'error';
  progress: number;
  savedName?: string;
  error?: string;
};

interface VoiceStats {
  voicePing: number | null;
  gatewayPing: number;
  status: string;
  channelName: string | null;
  connectedSince: string | null;
}

/* ══════════════════════════════════════════════════════════════════
   PROPS — receives SSE data from the Hub
   ══════════════════════════════════════════════════════════════════ */

interface SoundboardTabProps {
  data: any;
  isAdmin?: boolean;
}

/* ══════════════════════════════════════════════════════════════════
   COMPONENT
   ══════════════════════════════════════════════════════════════════ */

export default function SoundboardTab({ data, isAdmin: isAdminProp }: SoundboardTabProps) {
  /* ── 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);

  // Download modal state
  const [dlModal, setDlModal] = useState<{
    url: string; type: 'youtube' | 'instagram' | 'mp3' | null;
    filename: string; phase: 'input' | 'downloading' | 'done' | 'error';
    savedName?: string; error?: string;
  } | null>(null);

  /* ── 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>>({});

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

  /* ── Admin ── */
  const isAdmin = isAdminProp ?? false;

  /* ── Drag & Drop Upload ── */
  const [isDragging, setIsDragging] = useState(false);
  const [uploads, setUploads] = useState<UploadItem[]>([]);
  const [showUploads, setShowUploads] = useState(false);
  const dragCounterRef = useRef(0);
  const uploadDismissRef = useRef<ReturnType<typeof setTimeout>>(undefined);

  /* ── Drop Rename Modal ── */
  const [dropFiles, setDropFiles] = useState<File[]>([]);
  const [dropIndex, setDropIndex] = useState(0);
  const [dropName, setDropName] = useState('');
  const [dropPhase, setDropPhase] = useState<'naming' | 'uploading' | 'done'>('naming');
  const [dropProgress, setDropProgress] = useState(0);

  /* ── Voice Stats ── */
  const [voiceStats, setVoiceStats] = useState<VoiceStats | null>(null);
  const [showConnModal, setShowConnModal] = useState(false);

  /* ── UI ── */
  const [notification, setNotification] = useState<{ msg: string; type: 'info' | 'error' } | null>(null);
  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]);

  /* ── Drag & Drop: global window listeners ── */
  useEffect(() => {
    const onDragEnter = (e: DragEvent) => {
      if (Array.from(e.dataTransfer?.items ?? []).some(i => i.kind === 'file')) {
        dragCounterRef.current++;
        setIsDragging(true);
      }
    };
    const onDragLeave = () => {
      dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
      if (dragCounterRef.current === 0) setIsDragging(false);
    };
    const onDragOver = (e: DragEvent) => e.preventDefault();
    const onDrop = (e: DragEvent) => {
      e.preventDefault();
      dragCounterRef.current = 0;
      setIsDragging(false);
      const files = Array.from(e.dataTransfer?.files ?? []).filter(f =>
        /\.(mp3|wav)$/i.test(f.name)
      );
      if (files.length) handleFileDrop(files);
    };
    window.addEventListener('dragenter', onDragEnter);
    window.addEventListener('dragleave', onDragLeave);
    window.addEventListener('dragover', onDragOver);
    window.addEventListener('drop', onDrop);
    return () => {
      window.removeEventListener('dragenter', onDragEnter);
      window.removeEventListener('dragleave', onDragLeave);
      window.removeEventListener('dragover', onDragOver);
      window.removeEventListener('drop', onDrop);
    };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isAdmin]);

  /* ── 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 YTDLP_HOSTS = ['youtube.com', 'www.youtube.com', 'm.youtube.com', 'youtu.be', 'music.youtube.com', 'instagram.com', 'www.instagram.com'];
  /** Auto-prepend https:// if missing */
  const normalizeUrl = useCallback((value: string): string => {
    const v = value.trim();
    if (!v) return v;
    if (/^https?:\/\//i.test(v)) return v;
    return 'https://' + v;
  }, []);
  const isSupportedUrl = useCallback((value: string) => {
    try {
      const parsed = new URL(normalizeUrl(value));
      const host = parsed.hostname.toLowerCase();
      if (parsed.pathname.toLowerCase().endsWith('.mp3')) return true;
      if (YTDLP_HOSTS.some(h => host === h || host.endsWith('.' + h))) return true;
      return false;
    } catch {
      return false;
    }
  }, [normalizeUrl]);
  const getUrlType = useCallback((value: string): 'youtube' | 'instagram' | 'mp3' | null => {
    try {
      const parsed = new URL(normalizeUrl(value));
      const host = parsed.hostname.toLowerCase();
      if (host.includes('youtube') || host === 'youtu.be') return 'youtube';
      if (host.includes('instagram')) return 'instagram';
      if (parsed.pathname.toLowerCase().endsWith('.mp3')) return 'mp3';
      return null;
    } catch { return null; }
  }, [normalizeUrl]);

  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]);

  /* ── 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 { const c = await fetchCategories(); setCategories(c.categories || []); } catch { }
    })();
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /* ── SSE via props.data instead of own EventSource ── */
  useEffect(() => {
    if (!data) return;

    // Handle snapshot data (initial load from hub SSE)
    if (data.soundboard) {
      const sb = data.soundboard;
      if (Array.isArray(sb.party)) {
        setPartyActiveGuilds(sb.party);
      }
      try {
        const sel = sb.selected || {};
        const g = selectedRef.current?.split(':')[0];
        if (g && sel[g]) setSelected(`${g}:${sel[g]}`);
      } catch { }
      try {
        const vols = sb.volumes || {};
        const g = selectedRef.current?.split(':')[0];
        if (g && typeof vols[g] === 'number') setVolume(vols[g]);
      } catch { }
      try {
        const np = sb.nowplaying || {};
        const g = selectedRef.current?.split(':')[0];
        if (g && typeof np[g] === 'string') setLastPlayed(np[g]);
      } catch { }
      try {
        const vs = sb.voicestats || {};
        const g = selectedRef.current?.split(':')[0];
        if (g && vs[g]) setVoiceStats(vs[g]);
      } catch { }
    }

    // Handle individual SSE event types
    if (data.type === 'soundboard_party') {
      setPartyActiveGuilds(prev => {
        const s = new Set(prev);
        if (data.active) s.add(data.guildId); else s.delete(data.guildId);
        return Array.from(s);
      });
    } else if (data.type === 'soundboard_channel') {
      const g = selectedRef.current?.split(':')[0];
      if (data.guildId === g) setSelected(`${data.guildId}:${data.channelId}`);
    } else if (data.type === 'soundboard_volume') {
      const g = selectedRef.current?.split(':')[0];
      if (data.guildId === g && typeof data.volume === 'number') setVolume(data.volume);
    } else if (data.type === 'soundboard_nowplaying') {
      const g = selectedRef.current?.split(':')[0];
      if (data.guildId === g) setLastPlayed(data.name || '');
    } else if (data.type === 'soundboard_voicestats') {
      const g = selectedRef.current?.split(':')[0];
      if (data.guildId === g) {
        setVoiceStats({
          voicePing: data.voicePing,
          gatewayPing: data.gatewayPing,
          status: data.status,
          channelName: data.channelName,
          connectedSince: data.connectedSince,
        });
      }
    }
  }, [data]);

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

  /* ── 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, notify]);

  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 apiGetVolume(guildId); setVolume(v); } catch { }
      })();
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selected]);

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

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

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

  // Open download modal instead of downloading directly
  function handleUrlImport() {
    const trimmed = normalizeUrl(importUrl);
    if (!trimmed) return notify('Bitte einen Link eingeben', 'error');
    if (!isSupportedUrl(trimmed)) return notify('Nur YouTube, Instagram oder direkte MP3-Links', 'error');
    const urlType = getUrlType(trimmed);
    // Pre-fill filename for MP3 links (basename without .mp3), empty for YT/IG
    let defaultName = '';
    if (urlType === 'mp3') {
      try { defaultName = new URL(trimmed).pathname.split('/').pop()?.replace(/\.mp3$/i, '') ?? ''; } catch {}
    }
    setDlModal({ url: trimmed, type: urlType, filename: defaultName, phase: 'input' });
  }

  // Actual download triggered from modal
  async function handleModalDownload() {
    if (!dlModal) return;
    setDlModal(prev => prev ? { ...prev, phase: 'downloading' } : null);
    try {
      let savedName: string | undefined;
      const fn = dlModal.filename.trim() || undefined;
      if (selected && guildId && channelId) {
        const result = await apiPlayUrl(dlModal.url, guildId, channelId, volume, fn);
        savedName = result.saved;
      } else {
        const result = await apiDownloadUrl(dlModal.url, fn);
        savedName = result.saved;
      }
      setDlModal(prev => prev ? { ...prev, phase: 'done', savedName } : null);
      setImportUrl('');
      setRefreshKey(k => k + 1);
      void loadAnalytics();
      // Auto-close after 2.5s
      setTimeout(() => setDlModal(null), 2500);
    } catch (e: any) {
      setDlModal(prev => prev ? { ...prev, phase: 'error', error: e?.message || 'Fehler' } : null);
    }
  }

  async function handleFileDrop(files: File[]) {
    if (!isAdmin) {
      notify('Admin-Login erforderlich zum Hochladen', 'error');
      return;
    }
    if (files.length === 0) return;
    // Open rename modal for first file
    setDropFiles(files);
    setDropIndex(0);
    const baseName = files[0].name.replace(/\.(mp3|wav)$/i, '');
    setDropName(baseName);
    setDropPhase('naming');
    setDropProgress(0);
  }

  async function handleDropConfirm() {
    if (dropFiles.length === 0) return;
    const file = dropFiles[dropIndex];
    const name = dropName.trim() || file.name.replace(/\.(mp3|wav)$/i, '');

    setDropPhase('uploading');
    setDropProgress(0);

    try {
      await apiUploadFileWithName(file, name, pct => setDropProgress(pct));
      setDropPhase('done');

      // After short delay, move to next file or close
      setTimeout(() => {
        const nextIdx = dropIndex + 1;
        if (nextIdx < dropFiles.length) {
          setDropIndex(nextIdx);
          const nextBase = dropFiles[nextIdx].name.replace(/\.(mp3|wav)$/i, '');
          setDropName(nextBase);
          setDropPhase('naming');
          setDropProgress(0);
        } else {
          // All done - close modal and refresh
          setDropFiles([]);
          setDropIndex(0);
          setDropName('');
          setRefreshKey(k => k + 1);
          void loadAnalytics();
          notify(`${dropFiles.length} Sound${dropFiles.length > 1 ? 's' : ''} hochgeladen`, 'info');
        }
      }, 800);
    } catch (e: any) {
      notify(e?.message || 'Upload fehlgeschlagen', 'error');
      setDropFiles([]);
    }
  }

  function handleDropSkip() {
    const nextIdx = dropIndex + 1;
    if (nextIdx < dropFiles.length) {
      setDropIndex(nextIdx);
      const nextBase = dropFiles[nextIdx].name.replace(/\.(mp3|wav)$/i, '');
      setDropName(nextBase);
      setDropPhase('naming');
      setDropProgress(0);
    } else {
      setDropFiles([]);
      if (dropIndex > 0) {
        setRefreshKey(k => k + 1);
        void loadAnalytics();
      }
    }
  }

  async function handleStop() {
    if (!selected) return;
    setLastPlayed('');
    try { await fetch(`${API_BASE}/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 apiPartyStop(guildId); } catch { }
    } else {
      if (!selected) return notify('Bitte einen Channel auswaehlen', 'error');
      try { await apiPartyStart(guildId, channelId); } catch { }
    }
  }

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

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

  /* ── 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 analyticsTop = analytics.mostPlayed.slice(0, 10);
  const totalSoundsDisplay = analytics.totalSounds || total;

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

      {/* ═══ TOPBAR ═══ */}
      <header className="topbar">
        <div className="topbar-left">
          <div className="sb-app-logo">
            <span className="material-icons" style={{ fontSize: 16, color: 'white' }}>music_note</span>
          </div>
          <span className="sb-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 ? `${selectedChannel.channelName}${selectedChannel.members ? ` (${selectedChannel.members})` : ''}` : '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}{ch.members ? ` (${ch.members})` : ''}
                      </div>
                    ))}
                  </React.Fragment>
                ))}
                {channels.length === 0 && (
                  <div className="channel-option" style={{ color: 'var(--text-faint)', cursor: 'default' }}>
                    Keine Channels verfuegbar
                  </div>
                )}
              </div>
            )}
          </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-label">Last Played:</span> <span className="np-name">{lastPlayed}</span>
            </div>
          )}
        </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 hinzugefuegt
          </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">
            {getUrlType(importUrl) === 'youtube' ? 'smart_display'
              : getUrlType(importUrl) === 'instagram' ? 'photo_camera'
              : 'link'}
          </span>
          <input
            className="url-import-input"
            type="text"
            placeholder="YouTube / Instagram / MP3-Link..."
            value={importUrl}
            onChange={e => setImportUrl(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') void handleUrlImport(); }}
          />
          {importUrl && (
            <span className={`url-import-tag ${isSupportedUrl(importUrl) ? 'valid' : 'invalid'}`}>
              {getUrlType(importUrl) === 'youtube' ? 'YT'
                : getUrlType(importUrl) === 'instagram' ? 'IG'
                : getUrlType(importUrl) === 'mp3' ? 'MP3'
                : '?'}
            </span>
          )}
          <button
            className="url-import-btn"
            onClick={() => { void handleUrlImport(); }}
            disabled={importBusy || (!!importUrl && !isSupportedUrl(importUrl))}
            title="Sound herunterladen"
          >
            {importBusy ? 'Laedt...' : '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) apiSetVolumeLive(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={e => {
              const v = parseFloat(e.target.value);
              setVolume(v);
              if (guildId) {
                if (volDebounceRef.current) clearTimeout(volDebounceRef.current);
                volDebounceRef.current = setTimeout(() => {
                  apiSetVolumeLive(guildId, v).catch(() => {});
                }, 50);
              }
            }}
            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="Zufaelliger 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>

        {selected && (
          <div className="connection" onClick={() => setShowConnModal(true)} style={{cursor:'pointer'}} title="Verbindungsdetails">
            <span className="conn-dot" />
            Verbunden
            {voiceStats?.voicePing != null && (
              <span className="conn-ping">{voiceStats.voicePing}ms</span>
            )}
          </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' ? '\u2B50' : '\uD83D\uDD07'}</div>
            <div className="empty-title">
              {activeTab === 'favorites'
                ? 'Noch keine Favoriten'
                : query
                  ? `Kein Sound fuer "${query}" gefunden`
                  : 'Keine Sounds vorhanden'}
            </div>
            <div className="empty-desc">
              {activeTab === 'favorites'
                ? 'Klick den Stern auf einem Sound!'
                : 'Hier gibt\'s noch nichts zu hoeren.'}
            </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;
                if (!window.confirm(`Sound "${ctxMenu.sound.name}" loeschen?`)) { setCtxMenu(null); return; }
                try {
                  await apiAdminDelete([path]);
                  notify('Sound geloescht');
                  setRefreshKey(k => k + 1);
                } catch (e: any) {
                  notify(e?.message || 'Loeschen fehlgeschlagen', 'error');
                }
                setCtxMenu(null);
              }}>
                <span className="material-icons ctx-icon">delete</span>
                Loeschen
              </div>
            </>
          )}
        </div>
      )}

      {/* ═══ CONNECTION MODAL ═══ */}
      {showConnModal && (() => {
        const uptimeSec = voiceStats?.connectedSince
          ? Math.floor((Date.now() - new Date(voiceStats.connectedSince).getTime()) / 1000)
          : 0;
        const h = Math.floor(uptimeSec / 3600);
        const m = Math.floor((uptimeSec % 3600) / 60);
        const s = uptimeSec % 60;
        const uptimeStr = h > 0
          ? `${h}h ${String(m).padStart(2,'0')}m ${String(s).padStart(2,'0')}s`
          : m > 0
            ? `${m}m ${String(s).padStart(2,'0')}s`
            : `${s}s`;
        const pingColor = (ms: number | null) =>
          ms == null ? 'var(--muted)' : ms < 80 ? 'var(--green)' : ms < 150 ? '#f0a830' : '#e04040';
        return (
          <div className="conn-modal-overlay" onClick={() => setShowConnModal(false)}>
            <div className="conn-modal" onClick={e => e.stopPropagation()}>
              <div className="conn-modal-header">
                <span className="material-icons" style={{fontSize:20,color:'var(--green)'}}>cell_tower</span>
                <span>Verbindungsdetails</span>
                <button className="conn-modal-close" onClick={() => setShowConnModal(false)}>
                  <span className="material-icons">close</span>
                </button>
              </div>
              <div className="conn-modal-body">
                <div className="conn-stat">
                  <span className="conn-stat-label">Voice Ping</span>
                  <span className="conn-stat-value">
                    <span className="conn-ping-dot" style={{background: pingColor(voiceStats?.voicePing ?? null)}} />
                    {voiceStats?.voicePing != null ? `${voiceStats.voicePing} ms` : '---'}
                  </span>
                </div>
                <div className="conn-stat">
                  <span className="conn-stat-label">Gateway Ping</span>
                  <span className="conn-stat-value">
                    <span className="conn-ping-dot" style={{background: pingColor(voiceStats?.gatewayPing ?? null)}} />
                    {voiceStats && voiceStats.gatewayPing >= 0 ? `${voiceStats.gatewayPing} ms` : '---'}
                  </span>
                </div>
                <div className="conn-stat">
                  <span className="conn-stat-label">Status</span>
                  <span className="conn-stat-value" style={{color: voiceStats?.status === 'ready' ? 'var(--green)' : '#f0a830'}}>
                    {voiceStats?.status === 'ready' ? 'Verbunden' : voiceStats?.status ?? 'Warte auf Verbindung'}
                  </span>
                </div>
                <div className="conn-stat">
                  <span className="conn-stat-label">Kanal</span>
                  <span className="conn-stat-value">{voiceStats?.channelName || '---'}</span>
                </div>
                <div className="conn-stat">
                  <span className="conn-stat-label">Verbunden seit</span>
                  <span className="conn-stat-value">{uptimeStr || '---'}</span>
                </div>
              </div>
            </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>
      )}

      {/* ── Drag & Drop Overlay ── */}
      {isDragging && (
        <div className="drop-overlay">
          <div className="drop-zone">
            <span className="material-icons drop-icon">cloud_upload</span>
            <div className="drop-title">MP3 & WAV hier ablegen</div>
            <div className="drop-sub">Mehrere Dateien gleichzeitig moeglich</div>
          </div>
        </div>
      )}

      {/* ── Upload-Queue ── */}
      {showUploads && uploads.length > 0 && (
        <div className="upload-queue">
          <div className="uq-header">
            <span className="material-icons" style={{ fontSize: 16 }}>upload</span>
            <span>
              {uploads.every(u => u.status === 'done' || u.status === 'error')
                ? `${uploads.filter(u => u.status === 'done').length} von ${uploads.length} hochgeladen`
                : `Lade hoch\u2026 (${uploads.filter(u => u.status === 'done').length}/${uploads.length})`}
            </span>
            <button className="uq-close" onClick={() => { setShowUploads(false); setUploads([]); }}>
              <span className="material-icons" style={{ fontSize: 14 }}>close</span>
            </button>
          </div>
          <div className="uq-list">
            {uploads.map(u => (
              <div key={u.id} className={`uq-item uq-${u.status}`}>
                <span className="material-icons uq-file-icon">audio_file</span>
                <div className="uq-info">
                  <div className="uq-name" title={u.savedName ?? u.file.name}>
                    {u.savedName ?? u.file.name}
                  </div>
                  <div className="uq-size">{(u.file.size / 1024).toFixed(0)} KB</div>
                </div>
                {(u.status === 'waiting' || u.status === 'uploading') && (
                  <div className="uq-progress-wrap">
                    <div className="uq-progress-bar" style={{ width: `${u.progress}%` }} />
                  </div>
                )}
                <span className={`material-icons uq-status-icon uq-status-${u.status}`}>
                  {u.status === 'done' ? 'check_circle' :
                   u.status === 'error' ? 'error' :
                   u.status === 'uploading' ? 'sync' : 'schedule'}
                </span>
                {u.status === 'error' && <div className="uq-error">{u.error}</div>}
              </div>
            ))}
          </div>
        </div>
      )}

      {/* ── Drop Rename Modal ── */}
      {dropFiles.length > 0 && (
        <div className="dl-modal-overlay" onClick={() => dropPhase === 'naming' && handleDropSkip()}>
          <div className="dl-modal" onClick={e => e.stopPropagation()}>
            <div className="dl-modal-header">
              <span className="material-icons" style={{ fontSize: 20 }}>upload_file</span>
              <span>
                {dropPhase === 'naming' ? 'Sound benennen'
                  : dropPhase === 'uploading' ? 'Wird hochgeladen...'
                  : 'Gespeichert!'}
                {dropFiles.length > 1 && ` (${dropIndex + 1}/${dropFiles.length})`}
              </span>
              {dropPhase === 'naming' && (
                <button className="dl-modal-close" onClick={handleDropSkip}>
                  <span className="material-icons" style={{ fontSize: 16 }}>close</span>
                </button>
              )}
            </div>

            <div className="dl-modal-body">
              {/* File info badge */}
              <div className="dl-modal-url">
                <span className="dl-modal-tag mp3">Datei</span>
                <span className="dl-modal-url-text" title={dropFiles[dropIndex]?.name}>
                  {dropFiles[dropIndex]?.name}
                </span>
                <span style={{ marginLeft: 'auto', opacity: .5, fontSize: 12 }}>
                  {((dropFiles[dropIndex]?.size ?? 0) / 1024).toFixed(0)} KB
                </span>
              </div>

              {/* Naming phase */}
              {dropPhase === 'naming' && (
                <div className="dl-modal-field">
                  <label className="dl-modal-label">Dateiname</label>
                  <div className="dl-modal-input-wrap">
                    <input
                      className="dl-modal-input"
                      type="text"
                      placeholder="Dateiname eingeben..."
                      value={dropName}
                      onChange={e => setDropName(e.target.value)}
                      onKeyDown={e => { if (e.key === 'Enter') void handleDropConfirm(); }}
                      autoFocus
                    />
                    <span className="dl-modal-ext">.mp3</span>
                  </div>
                </div>
              )}

              {/* Uploading phase */}
              {dropPhase === 'uploading' && (
                <div className="dl-modal-progress">
                  <div className="dl-modal-spinner" />
                  <span>Upload: {dropProgress}%</span>
                </div>
              )}

              {/* Done phase */}
              {dropPhase === 'done' && (
                <div className="dl-modal-success">
                  <span className="material-icons dl-modal-check">check_circle</span>
                  <span>Erfolgreich hochgeladen!</span>
                </div>
              )}
            </div>

            {/* Actions */}
            {dropPhase === 'naming' && (
              <div className="dl-modal-actions">
                <button className="dl-modal-cancel" onClick={handleDropSkip}>
                  {dropFiles.length > 1 ? 'Überspringen' : 'Abbrechen'}
                </button>
                <button className="dl-modal-submit" onClick={() => void handleDropConfirm()}>
                  <span className="material-icons" style={{ fontSize: 16 }}>upload</span>
                  Hochladen
                </button>
              </div>
            )}
          </div>
        </div>
      )}

      {/* ── Download Modal ── */}
      {dlModal && (
        <div className="dl-modal-overlay" onClick={() => dlModal.phase !== 'downloading' && setDlModal(null)}>
          <div className="dl-modal" onClick={e => e.stopPropagation()}>
            <div className="dl-modal-header">
              <span className="material-icons" style={{ fontSize: 20 }}>
                {dlModal.type === 'youtube' ? 'smart_display' : dlModal.type === 'instagram' ? 'photo_camera' : 'audio_file'}
              </span>
              <span>
                {dlModal.phase === 'input' ? 'Sound herunterladen'
                  : dlModal.phase === 'downloading' ? 'Wird heruntergeladen...'
                  : dlModal.phase === 'done' ? 'Fertig!'
                  : 'Fehler'}
              </span>
              {dlModal.phase !== 'downloading' && (
                <button className="dl-modal-close" onClick={() => setDlModal(null)}>
                  <span className="material-icons" style={{ fontSize: 16 }}>close</span>
                </button>
              )}
            </div>

            <div className="dl-modal-body">
              {/* URL badge */}
              <div className="dl-modal-url">
                <span className={`dl-modal-tag ${dlModal.type ?? ''}`}>
                  {dlModal.type === 'youtube' ? 'YouTube' : dlModal.type === 'instagram' ? 'Instagram' : 'MP3'}
                </span>
                <span className="dl-modal-url-text" title={dlModal.url}>
                  {dlModal.url.length > 60 ? dlModal.url.slice(0, 57) + '...' : dlModal.url}
                </span>
              </div>

              {/* Filename input (input phase only) */}
              {dlModal.phase === 'input' && (
                <div className="dl-modal-field">
                  <label className="dl-modal-label">Dateiname</label>
                  <div className="dl-modal-input-wrap">
                    <input
                      className="dl-modal-input"
                      type="text"
                      placeholder={dlModal.type === 'mp3' ? 'Dateiname...' : 'Wird automatisch erkannt...'}
                      value={dlModal.filename}
                      onChange={e => setDlModal(prev => prev ? { ...prev, filename: e.target.value } : null)}
                      onKeyDown={e => { if (e.key === 'Enter') void handleModalDownload(); }}
                      autoFocus
                    />
                    <span className="dl-modal-ext">.mp3</span>
                  </div>
                  <span className="dl-modal-hint">Leer lassen = automatischer Name</span>
                </div>
              )}

              {/* Progress (downloading phase) */}
              {dlModal.phase === 'downloading' && (
                <div className="dl-modal-progress">
                  <div className="dl-modal-spinner" />
                  <span>
                    {dlModal.type === 'youtube' || dlModal.type === 'instagram'
                      ? 'Audio wird extrahiert...'
                      : 'MP3 wird heruntergeladen...'}
                  </span>
                </div>
              )}

              {/* Success */}
              {dlModal.phase === 'done' && (
                <div className="dl-modal-success">
                  <span className="material-icons dl-modal-check">check_circle</span>
                  <span>Gespeichert als <b>{dlModal.savedName}</b></span>
                </div>
              )}

              {/* Error */}
              {dlModal.phase === 'error' && (
                <div className="dl-modal-error">
                  <span className="material-icons" style={{ color: '#e74c3c' }}>error</span>
                  <span>{dlModal.error}</span>
                </div>
              )}
            </div>

            {/* Actions */}
            {dlModal.phase === 'input' && (
              <div className="dl-modal-actions">
                <button className="dl-modal-cancel" onClick={() => setDlModal(null)}>Abbrechen</button>
                <button className="dl-modal-submit" onClick={() => void handleModalDownload()}>
                  <span className="material-icons" style={{ fontSize: 16 }}>download</span>
                  Herunterladen
                </button>
              </div>
            )}
            {dlModal.phase === 'error' && (
              <div className="dl-modal-actions">
                <button className="dl-modal-cancel" onClick={() => setDlModal(null)}>Schliessen</button>
                <button className="dl-modal-submit" onClick={() => setDlModal(prev => prev ? { ...prev, phase: 'input', error: undefined } : null)}>
                  <span className="material-icons" style={{ fontSize: 16 }}>refresh</span>
                  Nochmal
                </button>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}
