import { useState, useEffect, useRef, useCallback } from 'react';
import './streaming.css';

// ── Types ──

interface StreamInfo {
  id: string;
  broadcasterName: string;
  title: string;
  startedAt: string;
  viewerCount: number;
  hasPassword: boolean;
}

interface JoinModal {
  streamId: string;
  streamTitle: string;
  broadcasterName: string;
  password: string;
  error: string | null;
}

interface ViewState {
  streamId: string;
  phase: 'connecting' | 'connected' | 'error';
  error?: string;
}

const RTC_CONFIG: RTCConfiguration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'stun:stun1.l.google.com:19302' },
  ],
};

// ── Elapsed time helper ──
function formatElapsed(startedAt: string): string {
  const diff = Math.max(0, Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000));
  const h = Math.floor(diff / 3600);
  const m = Math.floor((diff % 3600) / 60);
  const s = diff % 60;
  if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
  return `${m}:${String(s).padStart(2, '0')}`;
}

// ── Component ──

export default function StreamingTab({ data }: { data: any }) {
  // ── State ──
  const [streams, setStreams] = useState<StreamInfo[]>([]);
  const [userName, setUserName] = useState(() => localStorage.getItem('streaming_name') || '');
  const [streamTitle, setStreamTitle] = useState('Screen Share');
  const [streamPassword, setStreamPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [joinModal, setJoinModal] = useState<JoinModal | null>(null);
  const [myStreamId, setMyStreamId] = useState<string | null>(null);
  const [isBroadcasting, setIsBroadcasting] = useState(false);
  const [starting, setStarting] = useState(false);
  const [viewing, setViewing] = useState<ViewState | null>(null);
  const [, setTick] = useState(0);
  const [openMenu, setOpenMenu] = useState<string | null>(null);
  const [copiedId, setCopiedId] = useState<string | null>(null);

  // ── Admin / Notification Config ──
  const [showAdmin, setShowAdmin] = useState(false);
  const [isAdmin, setIsAdmin] = useState(false);
  const [adminPwd, setAdminPwd] = useState('');
  const [adminError, setAdminError] = useState('');
  const [availableChannels, setAvailableChannels] = useState<Array<{ channelId: string; channelName: string; guildId: string; guildName: string }>>([]);
  const [notifyConfig, setNotifyConfig] = useState<Array<{ channelId: string; channelName: string; guildId: string; guildName: string; events: string[] }>>([]);
  const [configLoading, setConfigLoading] = useState(false);
  const [configSaving, setConfigSaving] = useState(false);
  const [notifyStatus, setNotifyStatus] = useState<{ online: boolean; botTag: string | null }>({ online: false, botTag: null });

  // ── Refs ──
  const wsRef = useRef<WebSocket | null>(null);
  const clientIdRef = useRef<string>('');
  const localStreamRef = useRef<MediaStream | null>(null);
  const localVideoRef = useRef<HTMLVideoElement | null>(null);
  const remoteVideoRef = useRef<HTMLVideoElement | null>(null);
  const peerConnectionsRef = useRef<Map<string, RTCPeerConnection>>(new Map());
  const viewerPcRef = useRef<RTCPeerConnection | null>(null);
  const pendingCandidatesRef = useRef<Map<string, RTCIceCandidateInit[]>>(new Map());
  const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const reconnectDelayRef = useRef(1000);

  // Refs that mirror state (avoid stale closures in WS handler)
  const isBroadcastingRef = useRef(false);
  const viewingRef = useRef<ViewState | null>(null);
  useEffect(() => { isBroadcastingRef.current = isBroadcasting; }, [isBroadcasting]);
  useEffect(() => { viewingRef.current = viewing; }, [viewing]);

  // Notify Electron about streaming status for close-warning
  useEffect(() => {
    (window as any).electronAPI?.setStreaming?.(isBroadcasting || viewing !== null);
  }, [isBroadcasting, viewing]);

  // ── Elapsed time ticker ──
  useEffect(() => {
    const hasActive = streams.length > 0 || isBroadcasting;
    if (!hasActive) return;
    const iv = setInterval(() => setTick(t => t + 1), 1000);
    return () => clearInterval(iv);
  }, [streams.length, isBroadcasting]);

  // ── SSE data → update stream list ──
  useEffect(() => {
    if (data?.streams) {
      setStreams(data.streams);
    }
  }, [data]);

  // ── Save name to localStorage ──
  useEffect(() => {
    if (userName) localStorage.setItem('streaming_name', userName);
  }, [userName]);

  // ── Close tile menu on outside click ──
  useEffect(() => {
    if (!openMenu) return;
    const handler = () => setOpenMenu(null);
    document.addEventListener('click', handler);
    return () => document.removeEventListener('click', handler);
  }, [openMenu]);

  // Check admin status on mount
  useEffect(() => {
    fetch('/api/notifications/admin/status', { credentials: 'include' })
      .then(r => r.json())
      .then(d => setIsAdmin(d.admin === true))
      .catch(() => {});
    fetch('/api/notifications/status')
      .then(r => r.json())
      .then(d => setNotifyStatus(d))
      .catch(() => {});
  }, []);

  // ── Send via WS ──
  const wsSend = useCallback((d: Record<string, any>) => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      wsRef.current.send(JSON.stringify(d));
    }
  }, []);

  // ── ICE candidate queuing ──
  const addOrQueueCandidate = useCallback((pc: RTCPeerConnection, peerId: string, candidate: RTCIceCandidateInit) => {
    if (pc.remoteDescription) {
      pc.addIceCandidate(new RTCIceCandidate(candidate)).catch(() => {});
    } else {
      let queue = pendingCandidatesRef.current.get(peerId);
      if (!queue) { queue = []; pendingCandidatesRef.current.set(peerId, queue); }
      queue.push(candidate);
    }
  }, []);

  const flushCandidates = useCallback((pc: RTCPeerConnection, peerId: string) => {
    const queue = pendingCandidatesRef.current.get(peerId);
    if (queue) {
      for (const c of queue) pc.addIceCandidate(new RTCIceCandidate(c)).catch(() => {});
      pendingCandidatesRef.current.delete(peerId);
    }
  }, []);

  // ── Viewer cleanup (only viewer PC, keeps broadcaster intact) ──
  const cleanupViewer = useCallback(() => {
    if (viewerPcRef.current) {
      viewerPcRef.current.close();
      viewerPcRef.current = null;
    }
    if (remoteVideoRef.current) remoteVideoRef.current.srcObject = null;
    // Only clear viewer-related pending candidates (not broadcaster ones)
  }, []);

  // ── WS message handler (uses refs, never stale) ──
  const handleWsMessageRef = useRef<(msg: any) => void>(() => {});
  handleWsMessageRef.current = (msg: any) => {
    switch (msg.type) {
      case 'welcome':
        clientIdRef.current = msg.clientId;
        if (msg.streams) setStreams(msg.streams);
        break;

      case 'broadcast_started':
        setMyStreamId(msg.streamId);
        setIsBroadcasting(true);
        isBroadcastingRef.current = true;
        setStarting(false);
        if ((window as any).electronAPI?.showNotification) {
          (window as any).electronAPI.showNotification('Stream gestartet', 'Dein Stream ist jetzt live!');
        }
        break;

      case 'stream_available':
        setStreams(prev => {
          if (prev.some(s => s.id === msg.streamId)) return prev;
          return [...prev, {
            id: msg.streamId,
            broadcasterName: msg.broadcasterName,
            title: msg.title,
            startedAt: new Date().toISOString(),
            viewerCount: 0,
            hasPassword: !!msg.hasPassword,
          }];
        });
        // Toast notification for new stream
        const notifBody = `${msg.broadcasterName} streamt: ${msg.title}`;
        if ((window as any).electronAPI?.showNotification) {
          (window as any).electronAPI.showNotification('Neuer Stream', notifBody);
        } else if (Notification.permission === 'granted') {
          new Notification('Neuer Stream', { body: notifBody, icon: '/assets/icon.png' });
        }
        break;

      case 'stream_ended':
        setStreams(prev => prev.filter(s => s.id !== msg.streamId));
        if (viewingRef.current?.streamId === msg.streamId) {
          cleanupViewer();
          setViewing(null);
        }
        break;

      // ── Broadcaster: viewer joined → create offer ──
      case 'viewer_joined': {
        const viewerId = msg.viewerId;
        const existingPc = peerConnectionsRef.current.get(viewerId);
        if (existingPc) {
          existingPc.close();
          peerConnectionsRef.current.delete(viewerId);
        }
        pendingCandidatesRef.current.delete(viewerId);

        const pc = new RTCPeerConnection(RTC_CONFIG);
        peerConnectionsRef.current.set(viewerId, pc);

        const stream = localStreamRef.current;
        if (stream) {
          for (const track of stream.getTracks()) pc.addTrack(track, stream);
        }

        pc.onicecandidate = (ev) => {
          if (ev.candidate) wsSend({ type: 'ice_candidate', targetId: viewerId, candidate: ev.candidate.toJSON() });
        };

        // 60 fps + high bitrate
        const videoSender = pc.getSenders().find(s => s.track?.kind === 'video');
        if (videoSender) {
          const params = videoSender.getParameters();
          if (!params.encodings || params.encodings.length === 0) params.encodings = [{}];
          params.encodings[0].maxFramerate = 60;
          params.encodings[0].maxBitrate = 8_000_000;
          videoSender.setParameters(params).catch(() => {});
        }

        pc.createOffer()
          .then(offer => pc.setLocalDescription(offer))
          .then(() => wsSend({ type: 'offer', targetId: viewerId, sdp: pc.localDescription }))
          .catch(console.error);
        break;
      }

      case 'viewer_left': {
        const pc = peerConnectionsRef.current.get(msg.viewerId);
        if (pc) { pc.close(); peerConnectionsRef.current.delete(msg.viewerId); }
        pendingCandidatesRef.current.delete(msg.viewerId);
        break;
      }

      // ── Viewer: received offer from broadcaster ──
      case 'offer': {
        const broadcasterId = msg.fromId;
        if (viewerPcRef.current) { viewerPcRef.current.close(); viewerPcRef.current = null; }
        pendingCandidatesRef.current.delete(broadcasterId);

        const pc = new RTCPeerConnection(RTC_CONFIG);
        viewerPcRef.current = pc;

        pc.ontrack = (ev) => {
          if (remoteVideoRef.current && ev.streams[0]) remoteVideoRef.current.srcObject = ev.streams[0];
          setViewing(prev => prev ? { ...prev, phase: 'connected' } : prev);
        };

        pc.onicecandidate = (ev) => {
          if (ev.candidate) wsSend({ type: 'ice_candidate', targetId: broadcasterId, candidate: ev.candidate.toJSON() });
        };

        pc.oniceconnectionstatechange = () => {
          if (pc.iceConnectionState === 'failed' || pc.iceConnectionState === 'disconnected') {
            setViewing(prev => prev ? { ...prev, phase: 'error', error: 'Verbindung verloren' } : prev);
          }
        };

        pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
          .then(() => { flushCandidates(pc, broadcasterId); return pc.createAnswer(); })
          .then(answer => pc.setLocalDescription(answer))
          .then(() => wsSend({ type: 'answer', targetId: broadcasterId, sdp: pc.localDescription }))
          .catch(console.error);
        break;
      }

      case 'answer': {
        const pc = peerConnectionsRef.current.get(msg.fromId);
        if (pc) {
          pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
            .then(() => flushCandidates(pc, msg.fromId))
            .catch(console.error);
        }
        break;
      }

      // ── ICE: try broadcaster map first, then viewer PC (supports dual role) ──
      case 'ice_candidate': {
        if (!msg.candidate) break;
        const broadcasterPc = peerConnectionsRef.current.get(msg.fromId);
        if (broadcasterPc) {
          addOrQueueCandidate(broadcasterPc, msg.fromId, msg.candidate);
        } else if (viewerPcRef.current) {
          addOrQueueCandidate(viewerPcRef.current, msg.fromId, msg.candidate);
        }
        break;
      }

      case 'error':
        if (msg.code === 'WRONG_PASSWORD') {
          setJoinModal(prev => prev ? { ...prev, error: msg.message } : prev);
        } else {
          setError(msg.message);
        }
        setStarting(false);
        break;
    }
  };

  // ── WebSocket connect ──
  const connectWs = useCallback(() => {
    if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) return;

    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    const ws = new WebSocket(`${proto}://${location.host}/ws/streaming`);
    wsRef.current = ws;

    ws.onopen = () => { reconnectDelayRef.current = 1000; };

    ws.onmessage = (ev) => {
      let msg: any;
      try { msg = JSON.parse(ev.data); } catch { return; }
      handleWsMessageRef.current(msg);
    };

    ws.onclose = () => {
      wsRef.current = null;
      // Always reconnect to keep stream list in sync
      reconnectTimerRef.current = setTimeout(() => {
        reconnectDelayRef.current = Math.min(reconnectDelayRef.current * 2, 10000);
        connectWs();
      }, reconnectDelayRef.current);
    };

    ws.onerror = () => { ws.close(); };
  }, []);

  // ── Connect WS on mount for live stream updates ──
  useEffect(() => {
    connectWs();
    return () => {
      if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
    };
  }, [connectWs]);

  // ── Start broadcasting ──
  const startBroadcast = useCallback(async () => {
    if (!userName.trim()) { setError('Bitte gib einen Namen ein.'); return; }
    if (!navigator.mediaDevices?.getDisplayMedia) {
      setError('Dein Browser unterstützt keine Bildschirmfreigabe.');
      return;
    }

    setError(null);
    setStarting(true);

    try {
      const stream = await navigator.mediaDevices.getDisplayMedia({
        video: { frameRate: { ideal: 60 }, width: { ideal: 1920 }, height: { ideal: 1080 } },
        audio: true,
      });
      localStreamRef.current = stream;
      if (localVideoRef.current) localVideoRef.current.srcObject = stream;

      stream.getVideoTracks()[0]?.addEventListener('ended', () => { stopBroadcast(); });

      connectWs();
      const waitForWs = () => {
        if (wsRef.current?.readyState === WebSocket.OPEN) {
          wsSend({ type: 'start_broadcast', name: userName.trim(), title: streamTitle.trim() || 'Screen Share', password: streamPassword.trim() || undefined });
        } else { setTimeout(waitForWs, 100); }
      };
      waitForWs();
    } catch (e: any) {
      setStarting(false);
      if (e.name === 'NotAllowedError') setError('Bildschirmfreigabe wurde abgelehnt.');
      else setError(`Fehler: ${e.message}`);
    }
  }, [userName, streamTitle, streamPassword, connectWs, wsSend]);

  // ── Stop broadcasting (keeps viewer connection intact) ──
  const stopBroadcast = useCallback(() => {
    wsSend({ type: 'stop_broadcast' });
    localStreamRef.current?.getTracks().forEach(t => t.stop());
    localStreamRef.current = null;
    if (localVideoRef.current) localVideoRef.current.srcObject = null;

    for (const pc of peerConnectionsRef.current.values()) pc.close();
    peerConnectionsRef.current.clear();

    setIsBroadcasting(false);
    isBroadcastingRef.current = false;
    setMyStreamId(null);
    setStreamPassword('');
  }, [wsSend]);

  // ── Join as viewer ──
  const joinStreamDirectly = useCallback((streamId: string) => {
    setError(null);
    setViewing({ streamId, phase: 'connecting' });
    connectWs();
    const waitForWs = () => {
      if (wsRef.current?.readyState === WebSocket.OPEN) {
        wsSend({ type: 'join_viewer', name: userName.trim() || 'Viewer', streamId });
      } else { setTimeout(waitForWs, 100); }
    };
    waitForWs();
  }, [userName, connectWs, wsSend]);

  const openJoinModal = useCallback((s: StreamInfo) => {
    if (s.hasPassword) {
      setJoinModal({ streamId: s.id, streamTitle: s.title, broadcasterName: s.broadcasterName, password: '', error: null });
    } else {
      joinStreamDirectly(s.id);
    }
  }, [joinStreamDirectly]);

  const submitJoinModal = useCallback(() => {
    if (!joinModal) return;
    if (!joinModal.password.trim()) {
      setJoinModal(prev => prev ? { ...prev, error: 'Passwort eingeben.' } : prev);
      return;
    }
    const { streamId, password } = joinModal;
    setJoinModal(null);
    setError(null);
    setViewing({ streamId, phase: 'connecting' });
    connectWs();

    const waitForWs = () => {
      if (wsRef.current?.readyState === WebSocket.OPEN) {
        wsSend({ type: 'join_viewer', name: userName.trim() || 'Viewer', streamId, password: password.trim() });
      } else { setTimeout(waitForWs, 100); }
    };
    waitForWs();
  }, [joinModal, userName, connectWs, wsSend]);

  // ── Leave viewer ──
  const leaveViewing = useCallback(() => {
    wsSend({ type: 'leave_viewer' });
    cleanupViewer();
    setViewing(null);
  }, [cleanupViewer, wsSend]);

  // ── Warn before leaving + beacon cleanup ──
  useEffect(() => {
    const beforeUnload = (e: BeforeUnloadEvent) => {
      if (isBroadcastingRef.current || viewingRef.current) e.preventDefault();
    };
    const pageHide = () => {
      // Guaranteed delivery via sendBeacon
      if (clientIdRef.current) {
        navigator.sendBeacon('/api/streaming/disconnect', JSON.stringify({ clientId: clientIdRef.current }));
      }
    };
    window.addEventListener('beforeunload', beforeUnload);
    window.addEventListener('pagehide', pageHide);
    return () => {
      window.removeEventListener('beforeunload', beforeUnload);
      window.removeEventListener('pagehide', pageHide);
    };
  }, []);

  // ── Fullscreen toggle ──
  const viewerContainerRef = useRef<HTMLDivElement | null>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);

  const toggleFullscreen = useCallback(() => {
    const el = viewerContainerRef.current;
    if (!el) return;
    if (!document.fullscreenElement) el.requestFullscreen().catch(() => {});
    else document.exitFullscreen().catch(() => {});
  }, []);

  useEffect(() => {
    const handler = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', handler);
    return () => document.removeEventListener('fullscreenchange', handler);
  }, []);

  // ── Cleanup on unmount ──
  useEffect(() => {
    return () => {
      localStreamRef.current?.getTracks().forEach(t => t.stop());
      for (const pc of peerConnectionsRef.current.values()) pc.close();
      if (viewerPcRef.current) viewerPcRef.current.close();
      if (wsRef.current) wsRef.current.close();
      if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
    };
  }, []);

  // ── Auto-join from URL ?viewStream=... ──
  const pendingViewStreamRef = useRef<string | null>(null);

  // On mount: grab the streamId from URL and clear it
  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const streamId = params.get('viewStream');
    if (streamId) {
      pendingViewStreamRef.current = streamId;
      const url = new URL(location.href);
      url.searchParams.delete('viewStream');
      window.history.replaceState({}, '', url.toString());
    }
  }, []);

  // When streams update, check if we have a pending auto-join
  useEffect(() => {
    const pending = pendingViewStreamRef.current;
    if (!pending || streams.length === 0) return;
    const s = streams.find(st => st.id === pending);
    if (s) {
      pendingViewStreamRef.current = null;
      openJoinModal(s);
    }
  }, [streams, openJoinModal]);

  // ── Helpers for 3-dot menu ──
  const buildStreamLink = useCallback((streamId: string) => {
    const url = new URL(location.href);
    url.searchParams.set('viewStream', streamId);
    // Make sure we're on the streaming tab
    url.hash = '';
    return url.toString();
  }, []);

  const copyStreamLink = useCallback((streamId: string) => {
    navigator.clipboard.writeText(buildStreamLink(streamId)).then(() => {
      setCopiedId(streamId);
      setTimeout(() => setCopiedId(null), 2000);
    }).catch(() => {});
  }, [buildStreamLink]);

  const openInNewWindow = useCallback((streamId: string) => {
    window.open(buildStreamLink(streamId), '_blank', 'noopener');
    setOpenMenu(null);
  }, [buildStreamLink]);

  // ── Admin functions ──
  const adminLogin = useCallback(async () => {
    setAdminError('');
    try {
      const resp = await fetch('/api/notifications/admin/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ password: adminPwd }),
        credentials: 'include',
      });
      if (resp.ok) {
        setIsAdmin(true);
        setAdminPwd('');
        loadNotifyConfig();
      } else {
        const d = await resp.json();
        setAdminError(d.error || 'Fehler');
      }
    } catch {
      setAdminError('Verbindung fehlgeschlagen');
    }
  }, [adminPwd]);

  const adminLogout = useCallback(async () => {
    await fetch('/api/notifications/admin/logout', { method: 'POST', credentials: 'include' });
    setIsAdmin(false);
    setShowAdmin(false);
  }, []);

  const loadNotifyConfig = useCallback(async () => {
    setConfigLoading(true);
    try {
      const [chResp, cfgResp] = await Promise.all([
        fetch('/api/notifications/channels', { credentials: 'include' }),
        fetch('/api/notifications/config', { credentials: 'include' }),
      ]);
      if (chResp.ok) {
        const chData = await chResp.json();
        setAvailableChannels(chData.channels || []);
      }
      if (cfgResp.ok) {
        const cfgData = await cfgResp.json();
        setNotifyConfig(cfgData.channels || []);
      }
    } catch { /* silent */ }
    finally { setConfigLoading(false); }
  }, []);

  const openAdmin = useCallback(() => {
    setShowAdmin(true);
    if (isAdmin) loadNotifyConfig();
  }, [isAdmin, loadNotifyConfig]);

  const toggleChannelEvent = useCallback((channelId: string, channelName: string, guildId: string, guildName: string, event: string) => {
    setNotifyConfig(prev => {
      const existing = prev.find(c => c.channelId === channelId);
      if (existing) {
        const hasEvent = existing.events.includes(event);
        const newEvents = hasEvent
          ? existing.events.filter(e => e !== event)
          : [...existing.events, event];
        if (newEvents.length === 0) {
          return prev.filter(c => c.channelId !== channelId);
        }
        return prev.map(c => c.channelId === channelId ? { ...c, events: newEvents } : c);
      } else {
        return [...prev, { channelId, channelName, guildId, guildName, events: [event] }];
      }
    });
  }, []);

  const saveNotifyConfig = useCallback(async () => {
    setConfigSaving(true);
    try {
      const resp = await fetch('/api/notifications/config', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ channels: notifyConfig }),
        credentials: 'include',
      });
      if (resp.ok) {
        // brief visual feedback handled by configSaving state
      }
    } catch { /* silent */ }
    finally { setConfigSaving(false); }
  }, [notifyConfig]);

  const isChannelEventEnabled = useCallback((channelId: string, event: string): boolean => {
    const ch = notifyConfig.find(c => c.channelId === channelId);
    return ch?.events.includes(event) ?? false;
  }, [notifyConfig]);

  // ── Render ──

  // Fullscreen viewer overlay
  if (viewing) {
    const stream = streams.find(s => s.id === viewing.streamId);
    return (
      <div className="stream-viewer-overlay" ref={viewerContainerRef}>
        <div className="stream-viewer-header">
          <div className="stream-viewer-header-left">
            <span className="stream-live-badge"><span className="stream-live-dot" /> LIVE</span>
            <div>
              <div className="stream-viewer-title">{stream?.title || 'Stream'}</div>
              <div className="stream-viewer-subtitle">
                {stream?.broadcasterName || '...'} {stream ? ` · ${stream.viewerCount} Zuschauer` : ''}
              </div>
            </div>
          </div>
          <div className="stream-viewer-header-right">
            <button className="stream-viewer-fullscreen" onClick={toggleFullscreen} title={isFullscreen ? 'Vollbild verlassen' : 'Vollbild'}>
              {isFullscreen ? '\u2716' : '\u26F6'}
            </button>
            <button className="stream-viewer-close" onClick={leaveViewing}>Verlassen</button>
          </div>
        </div>
        <div className="stream-viewer-video">
          {viewing.phase === 'connecting' ? (
            <div className="stream-viewer-connecting">
              <div className="stream-viewer-spinner" />
              Verbindung wird hergestellt...
            </div>
          ) : viewing.phase === 'error' ? (
            <div className="stream-viewer-connecting">
              {viewing.error || 'Verbindungsfehler'}
              <button className="stream-btn" onClick={leaveViewing}>Zurück</button>
            </div>
          ) : null}
          <video ref={remoteVideoRef} autoPlay playsInline style={viewing.phase === 'connected' ? {} : { display: 'none' }} />
        </div>
      </div>
    );
  }

  return (
    <div className="stream-container">
      {error && (
        <div className="stream-error">
          {error}
          <button className="stream-error-dismiss" onClick={() => setError(null)}>{'\u00D7'}</button>
        </div>
      )}

      <div className="stream-topbar">
        <input
          className="stream-input stream-input-name"
          placeholder="Dein Name"
          value={userName}
          onChange={e => setUserName(e.target.value)}
          disabled={isBroadcasting}
        />
        <input
          className="stream-input stream-input-title"
          placeholder="Stream-Titel"
          value={streamTitle}
          onChange={e => setStreamTitle(e.target.value)}
          disabled={isBroadcasting}
        />
        <input
          className="stream-input stream-input-password"
          type="password"
          placeholder="Passwort (optional)"
          value={streamPassword}
          onChange={e => setStreamPassword(e.target.value)}
          disabled={isBroadcasting}
        />
        {isBroadcasting ? (
          <button className="stream-btn stream-btn-stop" onClick={stopBroadcast}>
            {'\u23F9'} Stream beenden
          </button>
        ) : (
          <button className="stream-btn" onClick={startBroadcast} disabled={starting}>
            {starting ? 'Starte...' : '\u{1F5A5}\uFE0F Stream starten'}
          </button>
        )}
        <button className="stream-admin-btn" onClick={openAdmin} title="Notification Einstellungen">
          {'\u2699\uFE0F'}
        </button>
      </div>

      {streams.length === 0 && !isBroadcasting ? (
        <div className="stream-empty">
          <div className="stream-empty-icon">{'\u{1F4FA}'}</div>
          <h3>Keine aktiven Streams</h3>
          <p>Starte einen Stream, um deinen Bildschirm zu teilen.</p>
        </div>
      ) : (
        <div className="stream-grid">
          {isBroadcasting && (
            <div className="stream-tile own broadcasting">
              <div className="stream-tile-preview">
                <video ref={localVideoRef} autoPlay playsInline muted />
                <span className="stream-live-badge"><span className="stream-live-dot" /> LIVE</span>
                <span className="stream-tile-viewers">
                  {'\u{1F465}'} {streams.find(s => s.id === myStreamId)?.viewerCount ?? 0}
                </span>
              </div>
              <div className="stream-tile-info">
                <div className="stream-tile-meta">
                  <div className="stream-tile-name">{userName} (Du)</div>
                  <div className="stream-tile-title">{streamTitle}</div>
                </div>
                <span className="stream-tile-time">
                  {myStreamId && streams.find(s => s.id === myStreamId)?.startedAt
                    ? formatElapsed(streams.find(s => s.id === myStreamId)!.startedAt)
                    : '0:00'}
                </span>
              </div>
            </div>
          )}

          {streams
            .filter(s => s.id !== myStreamId)
            .map(s => (
              <div key={s.id} className="stream-tile" onClick={() => openJoinModal(s)}>
                <div className="stream-tile-preview">
                  <span className="stream-tile-icon">{'\u{1F5A5}\uFE0F'}</span>
                  <span className="stream-live-badge"><span className="stream-live-dot" /> LIVE</span>
                  <span className="stream-tile-viewers">{'\u{1F465}'} {s.viewerCount}</span>
                  {s.hasPassword && <span className="stream-tile-lock">{'\u{1F512}'}</span>}
                </div>
                <div className="stream-tile-info">
                  <div className="stream-tile-meta">
                    <div className="stream-tile-name">{s.broadcasterName}</div>
                    <div className="stream-tile-title">{s.title}</div>
                  </div>
                  <span className="stream-tile-time">{formatElapsed(s.startedAt)}</span>
                  <div className="stream-tile-menu-wrap">
                    <button
                      className="stream-tile-menu"
                      onClick={e => { e.stopPropagation(); setOpenMenu(openMenu === s.id ? null : s.id); }}
                    >
                      {'\u22EE'}
                    </button>
                    {openMenu === s.id && (
                      <div className="stream-tile-dropdown" onClick={e => e.stopPropagation()}>
                        <div className="stream-tile-dropdown-header">
                          <div className="stream-tile-dropdown-name">{s.broadcasterName}</div>
                          <div className="stream-tile-dropdown-title">{s.title}</div>
                          <div className="stream-tile-dropdown-detail">
                            {'\u{1F465}'} {s.viewerCount} Zuschauer · {formatElapsed(s.startedAt)}
                          </div>
                        </div>
                        <div className="stream-tile-dropdown-divider" />
                        <button className="stream-tile-dropdown-item" onClick={() => openInNewWindow(s.id)}>
                          {'\u{1F5D7}'} In neuem Fenster öffnen
                        </button>
                        <button className="stream-tile-dropdown-item" onClick={() => { copyStreamLink(s.id); setOpenMenu(null); }}>
                          {copiedId === s.id ? '\u2705 Kopiert!' : '\u{1F517} Link teilen'}
                        </button>
                      </div>
                    )}
                  </div>
                </div>
              </div>
            ))}
        </div>
      )}

      {joinModal && (
        <div className="stream-pw-overlay" onClick={() => setJoinModal(null)}>
          <div className="stream-pw-modal" onClick={e => e.stopPropagation()}>
            <h3>{joinModal.broadcasterName}</h3>
            <p>{joinModal.streamTitle}</p>
            {joinModal.error && <div className="stream-pw-modal-error">{joinModal.error}</div>}
            <input
              className="stream-input"
              type="password"
              placeholder="Stream-Passwort"
              value={joinModal.password}
              onChange={e => setJoinModal(prev => prev ? { ...prev, password: e.target.value, error: null } : prev)}
              onKeyDown={e => { if (e.key === 'Enter') submitJoinModal(); }}
              autoFocus
            />
            <div className="stream-pw-actions">
              <button className="stream-pw-cancel" onClick={() => setJoinModal(null)}>Abbrechen</button>
              <button className="stream-btn" onClick={submitJoinModal}>Beitreten</button>
            </div>
          </div>
        </div>
      )}

      {/* ── Notification Admin Modal ── */}
      {showAdmin && (
        <div className="stream-admin-overlay" onClick={() => setShowAdmin(false)}>
          <div className="stream-admin-panel" onClick={e => e.stopPropagation()}>
            <div className="stream-admin-header">
              <h3>{'\uD83D\uDD14'} Benachrichtigungen</h3>
              <button className="stream-admin-close" onClick={() => setShowAdmin(false)}>{'\u2715'}</button>
            </div>

            {!isAdmin ? (
              <div className="stream-admin-login">
                <p>Admin-Passwort eingeben:</p>
                <div className="stream-admin-login-row">
                  <input
                    type="password"
                    className="stream-input"
                    placeholder="Passwort"
                    value={adminPwd}
                    onChange={e => setAdminPwd(e.target.value)}
                    onKeyDown={e => { if (e.key === 'Enter') adminLogin(); }}
                    autoFocus
                  />
                  <button className="stream-btn" onClick={adminLogin}>Login</button>
                </div>
                {adminError && <p className="stream-admin-error">{adminError}</p>}
              </div>
            ) : (
              <div className="stream-admin-content">
                <div className="stream-admin-toolbar">
                  <span className="stream-admin-status">
                    {notifyStatus.online
                      ? <>{'\u2705'} Bot online: <b>{notifyStatus.botTag}</b></>
                      : <>{'\u26A0\uFE0F'} Bot offline — <code>DISCORD_TOKEN_NOTIFICATIONS</code> setzen</>}
                  </span>
                  <button className="stream-admin-logout" onClick={adminLogout}>Logout</button>
                </div>

                {configLoading ? (
                  <div className="stream-admin-loading">Lade Kan{'\u00E4'}le...</div>
                ) : availableChannels.length === 0 ? (
                  <div className="stream-admin-empty">
                    {notifyStatus.online
                      ? 'Keine Text-Kan\u00E4le gefunden. Bot hat m\u00F6glicherweise keinen Zugriff.'
                      : 'Bot ist nicht verbunden. Bitte DISCORD_TOKEN_NOTIFICATIONS konfigurieren.'}
                  </div>
                ) : (
                  <>
                    <p className="stream-admin-hint">
                      W{'\u00E4'}hle die Kan{'\u00E4'}le, in die Benachrichtigungen gesendet werden sollen:
                    </p>
                    <div className="stream-admin-channel-list">
                      {availableChannels.map(ch => (
                        <div key={ch.channelId} className="stream-admin-channel">
                          <div className="stream-admin-channel-info">
                            <span className="stream-admin-channel-name">#{ch.channelName}</span>
                            <span className="stream-admin-channel-guild">{ch.guildName}</span>
                          </div>
                          <div className="stream-admin-channel-events">
                            <label className={`stream-admin-event-toggle${isChannelEventEnabled(ch.channelId, 'stream_start') ? ' active' : ''}`}>
                              <input
                                type="checkbox"
                                checked={isChannelEventEnabled(ch.channelId, 'stream_start')}
                                onChange={() => toggleChannelEvent(ch.channelId, ch.channelName, ch.guildId, ch.guildName, 'stream_start')}
                              />
                              {'\uD83D\uDD34'} Stream Start
                            </label>
                            <label className={`stream-admin-event-toggle${isChannelEventEnabled(ch.channelId, 'stream_end') ? ' active' : ''}`}>
                              <input
                                type="checkbox"
                                checked={isChannelEventEnabled(ch.channelId, 'stream_end')}
                                onChange={() => toggleChannelEvent(ch.channelId, ch.channelName, ch.guildId, ch.guildName, 'stream_end')}
                              />
                              {'\u23F9\uFE0F'} Stream Ende
                            </label>
                          </div>
                        </div>
                      ))}
                    </div>
                    <div className="stream-admin-actions">
                      <button
                        className="stream-btn stream-admin-save"
                        onClick={saveNotifyConfig}
                        disabled={configSaving}
                      >
                        {configSaving ? 'Speichern...' : '\uD83D\uDCBE Speichern'}
                      </button>
                    </div>
                  </>
                )}
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}
