import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import * as teamsbotApi from '../../../api/teamsbotApi';
import type {
  TeamsbotSession,
  TeamsbotTranscript,
  TeamsbotBotResponse,
  TeamsbotSSEEvent,
  ScreenshotInfo,
  DirectorPrompt,
  DirectorPromptMode,
} from '../../../api/teamsbotApi';
import {
  DIRECTOR_PROMPT_TEXT_LIMIT,
  DIRECTOR_PROMPT_FILE_LIMIT,
} from '../../../api/teamsbotApi';
import { getUserDataCache } from '../../../utils/userCache';
import { UnifiedDataBar } from '../../../components/UnifiedDataBar';
import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
import { useFileContext } from '../../../contexts/FileContext';
import styles from './Teamsbot.module.css';

import { useLanguage } from '../../../providers/language/LanguageContext';

/**
 * TeamsbotSessionView - Live session view with real-time transcript and bot responses.
 */
export const TeamsbotSessionView: React.FC = () => {
    const { t } = useLanguage();

  const { instance } = useCurrentInstance();
  const instanceId = instance?.id || '';
  const [searchParams, setSearchParams] = useSearchParams();
  const sessionId = searchParams.get('sessionId') || '';

  const cachedUser = getUserDataCache();
  const _isSysAdmin = cachedUser?.isSysAdmin === true;

  const [session, setSession] = useState<TeamsbotSession | null>(null);
  const [allSessions, setAllSessions] = useState<TeamsbotSession[]>([]);
  const [transcripts, setTranscripts] = useState<TeamsbotTranscript[]>([]);
  const [botResponses, setBotResponses] = useState<TeamsbotBotResponse[]>([]);
  const [loading, setLoading] = useState(true);
  const [noSessions, setNoSessions] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [isLive, setIsLive] = useState(false);

  const [screenshots, setScreenshots] = useState<ScreenshotInfo[]>([]);
  const [screenshotsLoading, setScreenshotsLoading] = useState(false);
  const [screenshotsLoaded, setScreenshotsLoaded] = useState(false);
  const [ttsStatusEvents, setTtsStatusEvents] = useState<Array<{
    status: string;
    message?: string;
    hasWebSocket?: boolean;
    timestamp: string;
  }>>([]);

  // Director Prompt panel state
  const [directorPrompts, setDirectorPrompts] = useState<DirectorPrompt[]>([]);
  const [directorText, setDirectorText] = useState('');
  const [directorMode, setDirectorMode] = useState<DirectorPromptMode>('oneShot');
  const [directorFiles, setDirectorFiles] = useState<Array<{ id: string; name: string }>>([]);
  const [directorSubmitting, setDirectorSubmitting] = useState(false);
  const [directorError, setDirectorError] = useState<string | null>(null);
  const [directorDragOver, setDirectorDragOver] = useState(false);
  const [directorUploading, setDirectorUploading] = useState(false);
  const directorDragCounterRef = useRef(0);
  const directorFileInputRef = useRef<HTMLInputElement>(null);

  // Bot WebSocket connection state (separate from session.status: the session
  // can be 'active' before the bot has actually opened its WebSocket back to
  // the gateway. Director prompts can only be processed once botConnected=true.)
  const [botConnected, setBotConnected] = useState(false);

  // UDB Sidebar state
  const [udbCollapsed, setUdbCollapsed] = useState(false);
  const [udbTab, setUdbTab] = useState<UdbTab>('files');
  const _udbContext: UdbContext | null = instanceId
    ? { instanceId, featureInstanceId: instanceId }
    : null;

  const fileCtx = useFileContext();

  const transcriptEndRef = useRef<HTMLDivElement>(null);
  const eventSourceRef = useRef<EventSource | null>(null);

  const debugLogsRef = useRef<string[]>([]);
  const [debugVisible, setDebugVisible] = useState(false);
  const [debugSnapshot, setDebugSnapshot] = useState<string[]>([]);
  const _dlog = useCallback((tag: string, info?: string) => {
    const t = new Date();
    const ts = `${t.getMinutes()}:${String(t.getSeconds()).padStart(2, '0')}.${String(t.getMilliseconds()).padStart(3, '0')}`;
    const entry = `[${ts}] ${tag}${info ? ' ' + info : ''}`;
    debugLogsRef.current.push(entry);
    if (debugLogsRef.current.length > 120) debugLogsRef.current.shift();
  }, []);

  // Load session data - if no sessionId given, load the most recent session
  const _loadSession = useCallback(async () => {
    if (!instanceId) return;
    try {
      setLoading(true);
      setNoSessions(false);

      // Always load the full session list for the switcher
      const listResult = await teamsbotApi.listSessions(instanceId, true);
      const sessions = listResult.sessions || [];
      setAllSessions(sessions);

      let targetSessionId = sessionId;

      // No sessionId in URL -> find the most recent active or latest session
      if (!targetSessionId) {
        if (sessions.length === 0) {
          setNoSessions(true);
          setLoading(false);
          return;
        }
        // Prefer active sessions, then most recent
        const activeSession = sessions.find(s => ['active', 'joining', 'pending'].includes(s.status));
        targetSessionId = activeSession ? activeSession.id : sessions[0].id;
        setSearchParams({ sessionId: targetSessionId }, { replace: true });
      }

      const result = await teamsbotApi.getSession(instanceId, targetSessionId);
      setSession(result.session);
      setTranscripts(result.transcripts || []);
      setBotResponses(result.botResponses || []);
      setError(null);
    } catch (err: any) {
      setError(err.message || t('Fehler beim Laden der Sitzung'));
    } finally {
      setLoading(false);
    }
  }, [instanceId, sessionId, setSearchParams, t]);

  useEffect(() => {
    _loadSession();
  }, [_loadSession]);

  // Load director prompt history when session changes
  useEffect(() => {
    if (!instanceId || !sessionId) return;
    let cancelled = false;
    teamsbotApi
      .listDirectorPrompts(instanceId, sessionId)
      .then((res) => {
        if (!cancelled) setDirectorPrompts(res.prompts || []);
      })
      .catch(() => {
        if (!cancelled) setDirectorPrompts([]);
      });
    return () => {
      cancelled = true;
    };
  }, [instanceId, sessionId]);

  // SSE Live Stream - connect once per session, don't re-create on status changes.
  // We deliberately depend ONLY on (instanceId, sessionId), not on session.status,
  // so transient status transitions (pending -> joining -> active) don't tear down
  // and rebuild the EventSource (which used to flicker botConnected and spawn
  // multiple parallel /stream connections to the gateway).
  const sseSessionRef = useRef<string | null>(null);
  const sessionStatusRef = useRef<string | undefined>(session?.status);
  sessionStatusRef.current = session?.status;
  useEffect(() => {
    if (!instanceId || !sessionId) return;
    // Avoid reconnecting if already streaming this session
    if (sseSessionRef.current === sessionId && eventSourceRef.current) return;
    // Don't open a stream for sessions that are known to already be terminal.
    const initialStatus = sessionStatusRef.current;
    if (initialStatus && !['active', 'joining', 'pending'].includes(initialStatus)) return;

    eventSourceRef.current?.close();
    sseSessionRef.current = sessionId;

    const eventSource = teamsbotApi.createSessionStream(instanceId, sessionId);
    eventSourceRef.current = eventSource;
    setIsLive(true);
    _dlog('SSE', 'connected');

    eventSource.onmessage = (event) => {
      try {
        const sseEvent: TeamsbotSSEEvent = JSON.parse(event.data);
        const evType = sseEvent.type || 'unknown';

        _dlog('SSE', evType + (sseEvent.data ? ` ${JSON.stringify(sseEvent.data).substring(0, 80)}` : ''));

        switch (evType) {
          case 'sessionState':
            if (sseEvent.data) setSession(prev => prev ? { ...prev, ...sseEvent.data } : sseEvent.data);
            break;

          case 'transcript': {
            const tr = sseEvent.data as TeamsbotTranscript;
            _dlog('TRANSCRIPT', `[${tr?.speaker || '?'}] ${(tr?.text || '').substring(0, 50)}...`);
            if (tr?.isContinuation && tr?.id) {
              setTranscripts(prev => {
                const idx = prev.findIndex(x => x.id === tr.id);
                if (idx >= 0) {
                  const updated = [...prev];
                  updated[idx] = { ...updated[idx], ...tr };
                  return updated;
                }
                return [...prev, tr];
              });
            } else {
              setTranscripts(prev => [...prev, tr]);
            }
            break;
          }

          case 'botResponse':
            setBotResponses(prev => [...prev, sseEvent.data as TeamsbotBotResponse]);
            break;

          case 'statusChange':
            setSession(prev => prev ? { ...prev, status: sseEvent.data.status } : null);
            if (['ended', 'error'].includes(sseEvent.data.status)) {
              setIsLive(false);
              eventSource.close();
              eventSourceRef.current = null;
              sseSessionRef.current = null;
              teamsbotApi.getSession(instanceId, sessionId).then((result) => {
                setSession(result.session);
                if (result.transcripts) setTranscripts(result.transcripts);
                if (result.botResponses) setBotResponses(result.botResponses);
              }).catch(() => {});
            }
            break;

          case 'analysis':
            break;

          case 'ttsDeliveryStatus': {
            const payload = sseEvent.data || {};
            setTtsStatusEvents((prev) => [
              ...prev.slice(-24),
              {
                status: payload.status || 'unknown',
                message: payload.message,
                hasWebSocket: payload.hasWebSocket,
                timestamp: payload.timestamp || new Date().toISOString(),
              },
            ]);
            break;
          }

          case 'chatSendFailed': {
            const failData = sseEvent.data || {};
            const failMsg = t('Chat-Nachricht konnte nicht gesendet werden: {reason}', {
              reason: failData.reason || t('unbekannt'),
            });
            _dlog('CHAT-FAIL', failMsg);
            setTtsStatusEvents((prev) => [
              ...prev.slice(-24),
              {
                status: 'chat_failed',
                message: failMsg,
                hasWebSocket: false,
                timestamp: failData.timestamp || new Date().toISOString(),
              },
            ]);
            break;
          }

          case 'botConnectionState': {
            const data = sseEvent.data || {};
            setBotConnected(Boolean(data.connected));
            _dlog('BOT-WS', data.connected ? 'connected' : 'disconnected');
            break;
          }

          case 'directorPrompt': {
            const prompt = sseEvent.data as DirectorPrompt | undefined;
            if (!prompt || !prompt.id) break;
            setDirectorPrompts((prev) => {
              const idx = prev.findIndex((p) => p.id === prompt.id);
              if (idx >= 0) {
                const updated = [...prev];
                updated[idx] = { ...updated[idx], ...prompt };
                return updated;
              }
              return [prompt, ...prev];
            });
            break;
          }

          case 'agentRun': {
            const data = sseEvent.data || {};
            _dlog('AGENT', `${data.status || ''} ${data.reason || ''}`.trim());
            break;
          }

          case 'error': {
            const errData = sseEvent.data || {};
            const errMsg = errData.message || t('Unbekannter Fehler');
            _dlog('ERROR', errMsg);
            setError(errMsg);
            break;
          }

          case 'suggestedResponse':
            break;

          case 'ping':
            break;
        }
      } catch (err) {
        _dlog('SSE-ERR', String(err));
        console.error('SSE parse error:', err);
      }
    };

    eventSource.onerror = () => {
      setIsLive(false);
    };

    return () => {
      eventSource.close();
      eventSourceRef.current = null;
      sseSessionRef.current = null;
      setIsLive(false);
      setBotConnected(false);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [instanceId, sessionId]);

  // Polling fallback: refresh session data every 5s when SSE is not connected
  const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const isActive = useMemo(() => session && ['pending', 'joining', 'active'].includes(session.status), [session]);
  useEffect(() => {
    if (instanceId && sessionId && (isActive || !session)) {
      pollRef.current = setInterval(async () => {
        if (isLive) return;
        try {
          const result = await teamsbotApi.getSession(instanceId, sessionId);
          setSession(result.session);
          if (result.transcripts) setTranscripts(result.transcripts);
          if (result.botResponses) setBotResponses(result.botResponses);
        } catch {}
      }, 5000);
    }
    return () => { if (pollRef.current) clearInterval(pollRef.current); };
  }, [isActive, instanceId, sessionId, isLive, session]);

  // Auto-scroll transcript
  useEffect(() => {
    transcriptEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [transcripts]);

  const _handleStop = async () => {
    if (!instanceId || !sessionId) return;
    try {
      await teamsbotApi.stopSession(instanceId, sessionId);
    } catch (err: any) {
      setError(err.message);
    }
  };

  const _formatTime = (timestamp: string) => {
    try {
      const dt = new Date(timestamp);
      if (!timestamp || Number.isNaN(dt.getTime())) return '';
      return dt.toLocaleTimeString('de-CH', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
    } catch {
      return '';
    }
  };

  const _addDirectorFile = useCallback((fileId: string, fileName?: string) => {
    setDirectorFiles((prev) => {
      if (prev.some((f) => f.id === fileId)) return prev;
      if (prev.length >= DIRECTOR_PROMPT_FILE_LIMIT) {
        setDirectorError(
          t('Maximal {n} Dateien pro Regieanweisung.', { n: String(DIRECTOR_PROMPT_FILE_LIMIT) }),
        );
        return prev;
      }
      setDirectorError(null);
      return [...prev, { id: fileId, name: fileName || fileId }];
    });
  }, [t]);

  const _handleUdbFileSelect = _addDirectorFile;

  const _removeDirectorFile = (fileId: string) => {
    setDirectorFiles((prev) => prev.filter((f) => f.id !== fileId));
  };

  const _uploadAndAttachDirectorFile = useCallback(async (file: File) => {
    if (!fileCtx?.handleFileUpload) return;
    setDirectorUploading(true);
    setDirectorError(null);
    try {
      const result = await fileCtx.handleFileUpload(file);
      if (result?.success) {
        const data: any = (result.fileData as any)?.file || result.fileData;
        const id = data?.id || (result.fileData as any)?.id;
        if (id) {
          _addDirectorFile(id, data?.fileName || file.name);
        } else {
          setDirectorError(t('Upload erfolgreich, aber keine Datei-ID erhalten.'));
        }
      } else {
        setDirectorError(result?.error || t('Upload fehlgeschlagen.'));
      }
    } catch (err: any) {
      setDirectorError(err?.message || t('Upload fehlgeschlagen.'));
    } finally {
      setDirectorUploading(false);
    }
  }, [fileCtx, _addDirectorFile, t]);

  const _onDirectorDragEnter = useCallback((e: React.DragEvent) => {
    if (
      e.dataTransfer.types.includes('Files') ||
      e.dataTransfer.types.includes('application/file-id') ||
      e.dataTransfer.types.includes('application/file-ids') ||
      e.dataTransfer.types.includes('application/group-file-ids') ||
      e.dataTransfer.types.includes('application/group-id') ||
      e.dataTransfer.types.includes('application/tree-items')
    ) {
      e.preventDefault();
      e.stopPropagation();
      directorDragCounterRef.current += 1;
      setDirectorDragOver(true);
    }
  }, []);

  const _onDirectorDragOver = useCallback((e: React.DragEvent) => {
    if (
      e.dataTransfer.types.includes('Files') ||
      e.dataTransfer.types.includes('application/file-id') ||
      e.dataTransfer.types.includes('application/file-ids') ||
      e.dataTransfer.types.includes('application/group-file-ids') ||
      e.dataTransfer.types.includes('application/group-id') ||
      e.dataTransfer.types.includes('application/tree-items')
    ) {
      e.preventDefault();
      e.stopPropagation();
      e.dataTransfer.dropEffect = 'copy';
    }
  }, []);

  const _onDirectorDragLeave = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    directorDragCounterRef.current = Math.max(0, directorDragCounterRef.current - 1);
    if (directorDragCounterRef.current === 0) setDirectorDragOver(false);
  }, []);

  const _onDirectorDrop = useCallback(async (e: React.DragEvent) => {
    e.preventDefault();
    e.stopPropagation();
    directorDragCounterRef.current = 0;
    setDirectorDragOver(false);

    const groupFileIdsJson = e.dataTransfer.getData('application/group-file-ids');
    if (groupFileIdsJson) {
      try {
        const ids: unknown = JSON.parse(groupFileIdsJson);
        if (Array.isArray(ids) && ids.length > 0) {
          ids.forEach((id) => typeof id === 'string' && id && _addDirectorFile(id));
          return;
        }
      } catch { /* ignore malformed */ }
    }

    const fileIdsJson = e.dataTransfer.getData('application/file-ids');
    if (fileIdsJson) {
      try {
        const ids: string[] = JSON.parse(fileIdsJson);
        ids.forEach((id) => _addDirectorFile(id));
      } catch { /* ignore malformed */ }
      return;
    }

    const singleFileId = e.dataTransfer.getData('application/file-id');
    if (singleFileId) {
      const label = e.dataTransfer.getData('text/plain');
      _addDirectorFile(singleFileId, label || undefined);
      return;
    }

    const groupId = e.dataTransfer.getData('application/group-id');
    if (groupId) {
      _addDirectorFile(groupId);
      return;
    }

    const treeItemsJson = e.dataTransfer.getData('application/tree-items');
    if (treeItemsJson) {
      try {
        const items: Array<{ id: string; type: 'file' | 'group'; name: string }> = JSON.parse(treeItemsJson);
        items.filter((it) => it.type === 'file').forEach((it) => _addDirectorFile(it.id, it.name));
      } catch { /* ignore malformed */ }
      return;
    }

    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
      for (const file of Array.from(e.dataTransfer.files)) {
        if (directorFiles.length >= DIRECTOR_PROMPT_FILE_LIMIT) {
          setDirectorError(
            t('Maximal {n} Dateien pro Regieanweisung.', { n: String(DIRECTOR_PROMPT_FILE_LIMIT) }),
          );
          break;
        }
        await _uploadAndAttachDirectorFile(file);
      }
    }
  }, [_addDirectorFile, _uploadAndAttachDirectorFile, directorFiles.length, t]);

  const _onDirectorFileInput = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!e.target.files || e.target.files.length === 0) return;
    for (const file of Array.from(e.target.files)) {
      if (directorFiles.length >= DIRECTOR_PROMPT_FILE_LIMIT) break;
      await _uploadAndAttachDirectorFile(file);
    }
    e.target.value = '';
  }, [_uploadAndAttachDirectorFile, directorFiles.length]);

  const _submitDirectorPrompt = async () => {
    if (!instanceId || !sessionId) return;
    const trimmed = directorText.trim();
    if (!trimmed) {
      setDirectorError(t('Bitte gib eine Anweisung ein.'));
      return;
    }
    if (trimmed.length > DIRECTOR_PROMPT_TEXT_LIMIT) {
      setDirectorError(
        t('Text zu lang (max. {n} Zeichen).', { n: String(DIRECTOR_PROMPT_TEXT_LIMIT) }),
      );
      return;
    }
    setDirectorSubmitting(true);
    setDirectorError(null);
    try {
      const res = await teamsbotApi.submitDirectorPrompt(instanceId, sessionId, {
        text: trimmed,
        mode: directorMode,
        fileIds: directorFiles.map((f) => f.id),
      });
      if (res.prompt) {
        setDirectorPrompts((prev) => {
          const idx = prev.findIndex((p) => p.id === res.prompt.id);
          if (idx >= 0) {
            const next = [...prev];
            next[idx] = res.prompt;
            return next;
          }
          return [res.prompt, ...prev];
        });
      }
      setDirectorText('');
      setDirectorFiles([]);
    } catch (err: any) {
      setDirectorError(err?.response?.data?.detail || err?.message || t('Senden fehlgeschlagen.'));
    } finally {
      setDirectorSubmitting(false);
    }
  };

  const _removeDirectorPrompt = async (promptId: string) => {
    if (!instanceId || !sessionId) return;
    try {
      await teamsbotApi.deleteDirectorPrompt(instanceId, sessionId, promptId);
      setDirectorPrompts((prev) => prev.filter((p) => p.id !== promptId));
    } catch (err: any) {
      setDirectorError(err?.message || t('Entfernen fehlgeschlagen.'));
    }
  };

  const activePersistentCount = useMemo(
    () => directorPrompts.filter((p) => p.mode === 'persistent' && p.status !== 'consumed').length,
    [directorPrompts],
  );

  const _getSpeakerColor = (speaker: string) => {
    const colors = ['#4A90D9', '#D94A4A', '#4AD99A', '#D9A84A', '#9A4AD9', '#4AD9D9'];
    let hash = 0;
    for (let i = 0; i < speaker.length; i++) {
      hash = speaker.charCodeAt(i) + ((hash << 5) - hash);
    }
    return colors[Math.abs(hash) % colors.length];
  };

  if (loading) return <div className={styles.loading}>{t('Sitzung laden')}</div>;
  if (noSessions) return (
    <div className={styles.emptyState || styles.loading}>
      <p>{t('Keine Sitzungen vorhanden')}</p>
      <p>{t('Starte eine neue Sitzung im Dashboard.')}</p>
    </div>
  );
  if (!session) return <div className={styles.errorBanner}>{t('Sitzung nicht gefunden')}</div>;

  const _switchSession = (newSessionId: string) => {
    setSearchParams({ sessionId: newSessionId });
  };

  return (
    <div className={styles.sessionContainer}>
      {/* Session Switcher (if multiple sessions exist) */}
      {allSessions.length > 1 && (
        <div style={{ display: 'flex', gap: '8px', marginBottom: '12px', flexWrap: 'wrap' }}>
          {allSessions.map((s) => (
            <button
              key={s.id}
              onClick={() => _switchSession(s.id)}
              style={{
                padding: '6px 12px',
                borderRadius: '6px',
                border: s.id === sessionId ? '2px solid #4A90D9' : '1px solid #ddd',
                background: s.id === sessionId ? '#EBF3FC' : '#fff',
                cursor: 'pointer',
                fontSize: '13px',
                fontWeight: s.id === sessionId ? 600 : 400,
              }}
            >
              {s.botName}
              {['active', 'joining', 'pending'].includes(s.status) && ' (aktiv)'}
              {s.status === 'ended' && ' (beendet)'}
            </button>
          ))}
        </div>
      )}

      {/* Session Header */}
      <div className={styles.sessionViewHeader}>
        <div className={styles.sessionInfo}>
          <h3 className={styles.sessionTitle}>{session.botName}</h3>
          <span className={`${styles.statusBadge} ${styles[`status${session.status.charAt(0).toUpperCase() + session.status.slice(1)}`] || ''}`}>
            {session.status}
          </span>
          {isLive && <span className={styles.liveBadge}>LIVE</span>}
        </div>
        <div className={styles.sessionControls}>
          {['active', 'joining', 'pending'].includes(session.status) && (
            <button className={styles.stopButton} onClick={_handleStop}>{t('Sitzung beenden')}</button>
          )}
        </div>
      </div>

      {error && <div className={styles.errorBanner}>{error}</div>}

      {/* Layout: UDB Sidebar + Main */}
      <div className={styles.sessionLayout}>
        {/* UDB Sidebar (Files / Sources) */}
        {_udbContext && (
          <div className={`${styles.udbSidebar} ${udbCollapsed ? styles.udbSidebarCollapsed : ''}`}>
            <button
              className={styles.udbToggle}
              onClick={() => setUdbCollapsed((v) => !v)}
              title={udbCollapsed ? t('Seitenleiste einblenden') : t('Seitenleiste ausblenden')}
            >
              {udbCollapsed ? '\u25B6' : '\u25C0'}
            </button>
            {!udbCollapsed && (
              <UnifiedDataBar
                context={_udbContext}
                activeTab={udbTab}
                onTabChange={setUdbTab}
                hideTabs={['chats']}
                onFileSelect={_handleUdbFileSelect}
              />
            )}
          </div>
        )}

        {/* Main column */}
        <div className={styles.sessionMain}>

      {/* Director Prompt Panel (private operator instructions) */}
      {['active', 'joining', 'pending'].includes(session.status) && (
        <div
          className={`${styles.directorPanel} ${directorDragOver ? styles.directorPanelDragOver : ''}`}
          onDragEnter={_onDirectorDragEnter}
          onDragOver={_onDirectorDragOver}
          onDragLeave={_onDirectorDragLeave}
          onDrop={_onDirectorDrop}
        >
          {(() => {
            const sStatus = session?.status;
            const isSessionLaunching = !!sStatus && ['pending', 'joining'].includes(sStatus);
            const isSessionActive = sStatus === 'active';
            // Bot has joined the meeting (session active) but the WebSocket back
            // to the gateway is missing -> usually means the browser-bot service
            // can't reach this gateway (e.g. localhost gateway + remote bot, or
            // bot behind firewall). Audio + transcripts won't flow.
            const isBotUnreachable = isSessionActive && !botConnected;
            const statusLabel = botConnected
              ? t('Bot live')
              : isBotUnreachable
                ? t('Bot ist im Meeting, aber nicht mit dem Gateway verbunden')
                : isSessionLaunching
                  ? t('Bot startet ...')
                  : t('Keine aktive Session');
            const statusTitle = botConnected
              ? t('Bot ist live im Meeting verbunden und liefert Transkripte')
              : isBotUnreachable
                ? t('Der Browser-Bot hat den WebSocket nicht zurueck zum Gateway geoeffnet. Pruefe TEAMSBOT_BROWSER_BOT_URL und APP_API_URL: bei lokalem Gateway muss der Bot ebenfalls lokal laufen oder das Gateway ueber einen Tunnel erreichbar sein.')
                : isSessionLaunching
                  ? t('Bot tritt dem Meeting bei und oeffnet die WebSocket-Verbindung ...')
                  : t('Es laeuft keine aktive Bot-Session');
            return (
              <div className={styles.directorHeader}>
                <div className={styles.directorHeaderLeft}>
                  <h4 className={styles.directorTitle}>{t('Regieanweisungen')}</h4>
                  <span
                    className={`${styles.botStatusDot} ${botConnected ? styles.botStatusDotLive : styles.botStatusDotIdle}`}
                    title={statusTitle}
                  />
                  <span className={styles.directorMeta} title={statusTitle}>
                    {statusLabel}
                  </span>
                  {activePersistentCount > 0 && (
                    <span className={styles.directorBadge} title={t('Aktive persistente Anweisungen')}>
                      {activePersistentCount}
                    </span>
                  )}
                </div>
                <div className={styles.directorMeta}>
                  {t('Privat - nur fuer den Bot sichtbar')}
                </div>
              </div>
            );
          })()}

          <div className={styles.directorBody}>
            <textarea
              className={styles.directorTextarea}
              placeholder={t('Anweisung an den Bot (z. B. recherchiere ... und gib eine Empfehlung) ...')}
              value={directorText}
              maxLength={DIRECTOR_PROMPT_TEXT_LIMIT}
              onChange={(e) => setDirectorText(e.target.value)}
              onKeyDown={(e) => {
                if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                  e.preventDefault();
                  void _submitDirectorPrompt();
                }
              }}
            />

            {directorFiles.length > 0 && (
              <div className={styles.directorChips}>
                {directorFiles.map((f) => (
                  <span key={f.id} className={styles.directorChip} title={f.name}>
                    <span className={styles.directorChipName}>{f.name}</span>
                    <button
                      className={styles.directorChipRemove}
                      onClick={() => _removeDirectorFile(f.id)}
                      title={t('Entfernen')}
                    >
                      x
                    </button>
                  </span>
                ))}
              </div>
            )}

            <div className={styles.directorActions}>
              <div className={styles.directorRow}>
                <span className={styles.directorModeToggle}>
                  <button
                    className={`${styles.directorModeButton} ${directorMode === 'oneShot' ? styles.directorModeButtonActive : ''}`}
                    onClick={() => setDirectorMode('oneShot')}
                    type="button"
                  >
                    {t('Einmalig')}
                  </button>
                  <button
                    className={`${styles.directorModeButton} ${directorMode === 'persistent' ? styles.directorModeButtonActive : ''}`}
                    onClick={() => setDirectorMode('persistent')}
                    type="button"
                  >
                    {t('Persistent')}
                  </button>
                </span>
                <input
                  ref={directorFileInputRef}
                  type="file"
                  multiple
                  style={{ display: 'none' }}
                  onChange={_onDirectorFileInput}
                />
                <button
                  className={styles.directorAttachBtn}
                  onClick={() => directorFileInputRef.current?.click()}
                  disabled={directorUploading || directorFiles.length >= DIRECTOR_PROMPT_FILE_LIMIT}
                  title={t('Dateien anhaengen')}
                  type="button"
                >
                  {directorUploading ? t('Lade hoch ...') : t('Datei anhaengen')}
                </button>
                <span className={styles.directorMeta}>
                  {directorText.length}/{DIRECTOR_PROMPT_TEXT_LIMIT} {t('Zeichen')} -
                  {' '}{directorFiles.length}/{DIRECTOR_PROMPT_FILE_LIMIT} {t('Dateien')}
                </span>
              </div>
              <button
                className={styles.directorSubmit}
                onClick={_submitDirectorPrompt}
                disabled={directorSubmitting || !directorText.trim() || !botConnected}
                type="button"
                title={botConnected ? t('Strg+Enter: Senden') : t('Bot ist noch nicht live verbunden')}
              >
                {directorSubmitting ? t('Senden ...') : t('An Bot senden')}
              </button>
            </div>

            {!botConnected && (
              <div className={styles.directorHint}>
                {session?.status === 'active'
                  ? t('Der Bot ist im Meeting, hat aber den WebSocket-Kanal zum Gateway nicht geoeffnet. Pruefe TEAMSBOT_BROWSER_BOT_URL/APP_API_URL und ob der Browser-Bot-Service das Gateway erreichen kann.')
                  : t('Der Bot muss erst dem Meeting beitreten und sich verbinden, bevor Regieanweisungen ausgefuehrt werden koennen.')}
              </div>
            )}

            {directorError && (
              <div className={styles.errorBanner} style={{ margin: 0 }}>{directorError}</div>
            )}
          </div>

          {directorPrompts.length > 0 && (
            <div className={styles.directorHistory}>
              {directorPrompts.slice(0, 8).map((p) => (
                <div key={p.id} className={styles.directorHistoryItem}>
                  <div className={styles.directorHistoryHead}>
                    <span>
                      {_formatTime(p.createdAt)} - {p.mode === 'persistent' ? t('Persistent') : t('Einmalig')}
                      {p.fileIds && p.fileIds.length > 0 && ` - ${p.fileIds.length} ${t('Dateien')}`}
                    </span>
                    <span style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
                      <span className={`${styles.directorStatus} ${styles[`directorStatus${p.status.charAt(0).toUpperCase() + p.status.slice(1)}`] || ''}`}>
                        {p.status}
                      </span>
                      {p.mode === 'persistent' && p.status !== 'consumed' && (
                        <button
                          className={styles.directorRemoveBtn}
                          onClick={() => _removeDirectorPrompt(p.id)}
                          title={t('Persistente Anweisung entfernen')}
                          type="button"
                        >
                          x
                        </button>
                      )}
                    </span>
                  </div>
                  <div className={styles.directorHistoryText}>{p.text}</div>
                  {p.responseText && (
                    <div className={styles.directorHistoryText} style={{ opacity: 0.85 }}>
                      <em>{t('Antwort')}:</em> {p.responseText}
                    </div>
                  )}
                  {p.statusMessage && p.status === 'failed' && (
                    <div className={styles.directorHistoryText} style={{ color: '#b91c1c' }}>
                      {p.statusMessage}
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* Main Content: Transcript + Responses */}
      <div className={styles.sessionContent}>
        {/* Left: Transcript */}
        <div className={styles.transcriptPanel}>
          <h4 className={styles.panelTitle}>
            {t('Transkript ({count} Segmente)', { count: transcripts.length })}
          </h4>
          <div className={styles.transcriptList}>
            {transcripts.map((seg) => (
              <div key={seg.id} className={styles.transcriptItem}>
                <span className={styles.transcriptTime}>{_formatTime(seg.timestamp)}</span>
                <span
                  className={styles.transcriptSpeaker}
                  style={{ color: _getSpeakerColor(seg.speaker || t('Unbekannt')) }}
                >
                  {seg.speaker || t('Unbekannt')}:
                </span>
                <span className={styles.transcriptText}>{seg.text}</span>
              </div>
            ))}
            <div ref={transcriptEndRef} />
            {transcripts.length === 0 && (
              <div className={styles.emptyState}>{t('Noch kein Transkript vorhanden')}</div>
            )}
          </div>
        </div>

        {/* Right: Bot Responses */}
        <div className={styles.responsesPanel}>
          <h4 className={styles.panelTitle}>Bot-Antworten ({botResponses.length})</h4>
          <div className={styles.responseList}>
            {botResponses.map((r) => (
              <div key={r.id} className={styles.responseItem}>
                <div className={styles.responseHeader}>
                  <span className={styles.responseIntent}>{r.detectedIntent}</span>
                  <span className={styles.responseTime}>{_formatTime(r.timestamp || '')}</span>
                </div>
                <div className={styles.responseText}>{r.responseText}</div>
                {r.reasoning && (
                  <div className={styles.responseReasoning}>
                    <em>{t('Begründung: {text}', { text: r.reasoning })}</em>
                  </div>
                )}
                {(r.modelName || r.processingTime != null) && (
                  <div className={styles.responseMeta}>
                    <span>{r.modelName || ''}</span>
                    {r.processingTime != null && <span>{r.processingTime.toFixed(1)}s</span>}
                    {r.priceCHF != null && <span>{r.priceCHF.toFixed(4)} CHF</span>}
                  </div>
                )}
              </div>
            ))}
            {botResponses.length === 0 && (
              <div className={styles.emptyState}>{t('Noch keine Botantworten')}</div>
            )}
          </div>
        </div>
      </div>

      {/* Summary (for ended sessions) */}
      {session.summary && (
        <div className={styles.summaryCard}>
          <h4 className={styles.panelTitle}>{t('Meeting-Zusammenfassung')}</h4>
          <div className={styles.summaryText}>{session.summary}</div>
        </div>
      )}

      {/* TTS Delivery Debug */}
      <div className={styles.summaryCard}>
        <h4 className={styles.panelTitle}>{t('TTS-Lieferstatus')}</h4>
        {ttsStatusEvents.length === 0 ? (
          <div className={styles.emptyState}>{t('Noch keine TTS-Events')}</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
            {ttsStatusEvents.slice(-10).reverse().map((ev, idx) => (
              <div key={`${ev.timestamp}-${idx}`} className={styles.responseMeta}>
                <span>{_formatTime(ev.timestamp)}</span>
                <span>{ev.status}</span>
                <span>ws: {ev.hasWebSocket ? 'yes' : 'no'}</span>
                <span>{ev.message || ''}</span>
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Debug Log (SSE/Transcript/Chat) */}
      <div style={{ position: 'fixed', bottom: 0, right: 0, zIndex: 9999 }}>
        <button
          onClick={() => { setDebugSnapshot([...debugLogsRef.current]); setDebugVisible(v => !v); }}
          style={{ background: '#333', color: '#0f0', border: 'none', padding: '4px 8px', fontSize: '10px', borderRadius: '4px 0 0 0' }}
        >
          DBG ({debugLogsRef.current.length})
        </button>
        {debugVisible && (
          <div style={{ background: 'rgba(0,0,0,0.9)', color: '#0f0', fontSize: '9px', maxHeight: '40vh', overflow: 'auto', padding: '4px', fontFamily: 'monospace', whiteSpace: 'pre-wrap', width: '100vw' }}>
            {debugSnapshot.map((l, i) => <div key={i}>{l}</div>)}
          </div>
        )}
      </div>

      {/* Debug Screenshots (SysAdmin only) */}
      {_isSysAdmin && (
        <div className={styles.summaryCard}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
            <h4 className={styles.panelTitle} style={{ margin: 0 }}>{t('Debug-Screenshots')}</h4>
            <button
              className={styles.viewButton}
              onClick={async () => {
                setScreenshotsLoading(true);
                try {
                  const result = await teamsbotApi.listScreenshots(instanceId, session.id);
                  setScreenshots(result.screenshots || []);
                  setScreenshotsLoaded(true);
                } catch (err: any) {
                  setScreenshots([]);
                  setScreenshotsLoaded(true);
                } finally {
                  setScreenshotsLoading(false);
                }
              }}
              disabled={screenshotsLoading}
            >
              {screenshotsLoading ? t('Laden…') : screenshotsLoaded ? t('Aktualisieren') : t('Screenshots laden')}
            </button>
          </div>
          {screenshotsLoaded && screenshots.length === 0 && (
            <div className={styles.emptyState}>{t('Keine Screenshots für diese Sitzung')}</div>
          )}
          {screenshots.length > 0 && (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: '12px' }}>
              {screenshots.map((s) => {
                const imgUrl = teamsbotApi.getScreenshotUrl(instanceId, s.name);
                return (
                  <a
                    key={s.name}
                    href={imgUrl}
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ display: 'block', textDecoration: 'none', color: 'inherit', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden', background: '#1a1a2e' }}
                  >
                    <img
                      src={imgUrl}
                      alt={s.step}
                      style={{ width: '100%', height: '140px', objectFit: 'cover', display: 'block' }}
                      loading="lazy"
                    />
                    <div style={{ padding: '8px', fontSize: '12px' }}>
                      <div style={{ fontWeight: 600, marginBottom: '2px' }}>{s.step}</div>
                      <div style={{ color: '#888' }}>
                        {new Date(s.timestamp).toLocaleTimeString('de-CH')} — {(s.sizeBytes / 1024).toFixed(0)} KB
                      </div>
                    </div>
                  </a>
                );
              })}
            </div>
          )}
        </div>
      )}

        </div>{/* /sessionMain */}
      </div>{/* /sessionLayout */}
    </div>
  );
};

export default TeamsbotSessionView;
