'use client';

/**
 * LiveKit видеокомната — вариант из коробки (@livekit/components-react)
 */

import React, { Suspense, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

/** Ошибка LiveKit updatePages (placeholder/track race) — не выкидываем пользователя из занятия. */
function isLiveKitLayoutError(error: unknown): boolean {
  const msg = error instanceof Error ? error.message : String(error);
  return (
    msg.includes('Element not part of the array') ||
    msg.includes('updatePages') ||
    msg.includes('_placeholder not in')
  );
}

class LiveKitLayoutErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { error: unknown; recoverKey: number }
> {
  state = { error: null as unknown, recoverKey: 0 };

  static getDerivedStateFromError(error: unknown) {
    return { error };
  }

  componentDidCatch(error: unknown) {
    if (isLiveKitLayoutError(error)) {
      window.setTimeout(() => {
        this.setState((s) => ({ error: null, recoverKey: s.recoverKey + 1 }));
      }, 100);
    }
  }

  render() {
    if (this.state.error && !isLiveKitLayoutError(this.state.error)) {
      throw this.state.error;
    }
    if (this.state.error) {
      return (
        <div style={{ flex: 1, background: '#000', minHeight: 200 }} />
      );
    }
    return <React.Fragment key={this.state.recoverKey}>{this.props.children}</React.Fragment>;
  }
}
import { useRouter, useSearchParams } from 'next/navigation';
import { LiveKitRoom, VideoConference, RoomAudioRenderer, ConnectionStateToast, useTracks, useRemoteParticipants, ParticipantTile, useRoomContext, useStartAudio } from '@livekit/components-react';
import { ExitLessonModal } from '@/components/livekit/ExitLessonModal';
import { Track, RoomEvent, VideoPresets } from 'livekit-client';

/** 2K (1440p) — разрешение и кодирование для высокого качества при хорошем канале */
const PRESET_2K = {
  resolution: { width: 2560, height: 1440 },
  encoding: { maxBitrate: 6_000_000, maxFramerate: 30 } as const,
};
import { isTrackReference } from '@livekit/components-core';
import '@/styles/livekit-components.css';
import '@/styles/livekit-theme.css';
import { getLesson } from '@/api/schedule';
import { participantConnected } from '@/api/livekit';
import type { Lesson } from '@/api/schedule';
import { getOrCreateLessonBoard } from '@/api/board';

/** Извлечь board_id и is_mentor из metadata LiveKit JWT (без верификации). */
function getTokenMetadata(token: string | null): { board_id?: string; is_mentor?: boolean } {
  if (!token) return {};
  try {
    const parts = token.split('.');
    if (parts.length !== 3) return {};
    const payload = JSON.parse(
      atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))
    ) as { metadata?: string };
    const meta = payload.metadata;
    if (!meta || typeof meta !== 'string') return {};
    const parsed = JSON.parse(meta) as { board_id?: string; is_mentor?: boolean };
    return {
      board_id: parsed?.board_id ?? undefined,
      is_mentor: parsed?.is_mentor === true,
    };
  } catch {
    return {};
  }
}
import { WhiteboardIframe } from '@/components/board/WhiteboardIframe';
import { LoadingSpinner } from '@/components/common/LoadingSpinner';
import { getOrCreateLessonChat } from '@/api/chat';
import type { Chat } from '@/api/chat';
import { ChatWindow } from '@/components/chat/ChatWindow';
import { useAuth } from '@/contexts/AuthContext';
import { useOnboarding } from '@/contexts/OnboardingContext';
import { getAvatarUrl } from '@/api/profile';
import { BottomNavigationBar } from '@/components/navigation/BottomNavigationBar';
import { getNavBadges } from '@/api/navBadges';
import type { NavBadges } from '@/api/navBadges';

const CHAT_PANEL_WIDTH = 420;

/** Камера собеседника в углу при открытой доске; сдвигается влево, когда открыт чат */
function RemoteParticipantPiP({ chatOpen }: { chatOpen: boolean }) {
  const tracks = useTracks(
    [Track.Source.Camera, Track.Source.ScreenShare],
    { onlySubscribed: true }
  );
  const remoteRef = tracks.find(
    (ref) => isTrackReference(ref) && ref.participant && !ref.participant.isLocal
  );
  if (!remoteRef || !isTrackReference(remoteRef)) return null;
  return (
    <div
      style={{
        position: 'fixed',
        bottom: 24,
        right: chatOpen ? CHAT_PANEL_WIDTH + 24 : 24,
        width: 280,
        height: 158,
        zIndex: 10000,
        borderRadius: 12,
        overflow: 'hidden',
        boxShadow: '0 8px 32px rgba(0,0,0,0.5)',
        border: '2px solid rgba(255,255,255,0.2)',
        background: '#000',
      }}
    >
      <ParticipantTile trackRef={remoteRef} />
    </div>
  );
}

const AVATAR_IMG_CLASS = 'lk-participant-avatar-img';

/**
 * Подставляет аватары в плейсхолдер, когда камера выключена:
 * — у удалённых участников (собеседник видит их фото);
 * — у локального участника (своё фото вижу я и собеседник).
 * GET /api/users/<identity>/avatar_url/
 */
function RemoteParticipantAvatarPlaceholder() {
  const { user } = useAuth();
  const remoteParticipants = useRemoteParticipants();
  const [avatarUrls, setAvatarUrls] = useState<Map<string, string>>(new Map());
  const [localAvatarUrl, setLocalAvatarUrl] = useState<string | null>(null);
  const injectedRef = useRef<WeakSet<Element>>(new WeakSet());
  const identityKey = remoteParticipants.map((p) => p.identity).sort().join(',');

  // Аватар собеседников
  useEffect(() => {
    if (remoteParticipants.length === 0) {
      setAvatarUrls(new Map());
      return;
    }
    let cancelled = false;
    const map = new Map<string, string>();
    Promise.all(
      remoteParticipants.map(async (p) => {
        const id = p.identity;
        const url = await getAvatarUrl(id);
        return { id, url } as const;
      })
    ).then((results) => {
      if (cancelled) return;
      results.forEach(({ id, url }) => {
        if (url) map.set(id, url);
      });
      setAvatarUrls(new Map(map));
    });
    return () => { cancelled = true; };
  }, [identityKey]);

  // Свой аватар (чтобы видел и я, и собеседник, когда камера выключена)
  useEffect(() => {
    const id = user?.id;
    if (id == null) {
      setLocalAvatarUrl(null);
      return;
    }
    let cancelled = false;
    getAvatarUrl(String(id)).then((url) => {
      if (!cancelled) setLocalAvatarUrl(url);
    });
    return () => { cancelled = true; };
  }, [user?.id]);

  const runInject = React.useCallback(() => {
    const injectInto = (placeholder: Element, url: string | null) => {
      let img = placeholder.querySelector(`img.${AVATAR_IMG_CLASS}`) as HTMLImageElement | null;
      if (url) {
        if (!img) {
          img = document.createElement('img');
          img.className = AVATAR_IMG_CLASS;
          img.alt = '';
          placeholder.appendChild(img);
        }
        if (img.src !== url) img.src = url;
        injectedRef.current.add(placeholder);
      } else {
        if (img) img.remove();
      }
    };

    // Плитки собеседников
    const remotePlaceholders = document.querySelectorAll(
      '.lk-participant-tile[data-lk-local-participant="false"] .lk-participant-placeholder'
    );
    const urls = remoteParticipants.map((p) => avatarUrls.get(p.identity) ?? null);
    remotePlaceholders.forEach((placeholder, i) => {
      const url = urls[Math.min(i, urls.length - 1)] ?? null;
      injectInto(placeholder, url);
    });

    // Своя плитка — свой аватар (вижу я и собеседник)
    const localPlaceholders = document.querySelectorAll(
      '.lk-participant-tile[data-lk-local-participant="true"] .lk-participant-placeholder'
    );
    localPlaceholders.forEach((placeholder) => injectInto(placeholder, localAvatarUrl));
  }, [remoteParticipants, avatarUrls, localAvatarUrl]);

  useLayoutEffect(() => {
    runInject();
    const t1 = setTimeout(runInject, 300);
    const t2 = setTimeout(runInject, 1000);
    return () => {
      clearTimeout(t1);
      clearTimeout(t2);
    };
  }, [runInject]);

  return null;
}

const LS_AUDIO_PLAYBACK_ALLOWED = 'videoConference_audioPlaybackAllowed';
const LS_AUDIO_ENABLED = 'videoConference_audioEnabled';
const LS_VIDEO_ENABLED = 'videoConference_videoEnabled';

/**
 * Оверлей «Разрешить звук» — показываем только при первом посещении.
 * После клика сохраняем в localStorage, чтобы не спрашивать постоянно.
 */
function StartAudioOverlay() {
  const room = useRoomContext();
  const { mergedProps, canPlayAudio } = useStartAudio({ room, props: {} });
  const [dismissed, setDismissed] = useState(() => {
    try {
      return localStorage.getItem(LS_AUDIO_PLAYBACK_ALLOWED) === 'true';
    } catch {
      return false;
    }
  });
  useEffect(() => {
    if (canPlayAudio) {
      try {
        localStorage.setItem(LS_AUDIO_PLAYBACK_ALLOWED, 'true');
      } catch {}
    }
  }, [canPlayAudio]);
  if (canPlayAudio || dismissed) return null;
  const handleClick = () => {
    try {
      localStorage.setItem(LS_AUDIO_PLAYBACK_ALLOWED, 'true');
    } catch {}
    setDismissed(true);
    mergedProps.onClick?.();
  };
  return (
    <div
      style={{
        position: 'fixed',
        inset: 0,
        zIndex: 9999,
        background: 'rgba(0,0,0,0.75)',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        gap: 24,
        padding: 24,
      }}
    >
      <p style={{ color: '#fff', fontSize: 18, textAlign: 'center', margin: 0 }}>
        Чтобы слышать собеседника, разрешите воспроизведение звука
      </p>
      <button
        type="button"
        onClick={handleClick}
        style={{
          padding: '16px 32px',
          fontSize: 18,
          fontWeight: 600,
          borderRadius: 12,
          border: 'none',
          background: 'var(--md-sys-color-primary)',
          color: 'var(--md-sys-color-on-primary)',
          cursor: 'pointer',
          display: 'flex',
          alignItems: 'center',
          gap: 8,
        }}
      >
        <span className="material-symbols-outlined" style={{ fontSize: 24 }}>volume_up</span>
        Разрешить звук
      </button>
    </div>
  );
}

function PreJoinScreen({
  onJoin,
  onCancel,
}: {
  onJoin: (audio: boolean, video: boolean) => void;
  onCancel: () => void;
}) {
  const [audioEnabled, setAudioEnabled] = useState(true);
  const [videoEnabled, setVideoEnabled] = useState(true);
  const [preview, setPreview] = useState<MediaStream | null>(null);
  const videoRef = React.useRef<HTMLVideoElement>(null);

  // Подтягиваем из localStorage после монтирования (SSR не имеет доступа к LS)
  useEffect(() => {
    const saved = getSavedAudioVideo();
    setAudioEnabled(saved.audio);
    setVideoEnabled(saved.video);
  }, []);

  useEffect(() => {
    if (!videoEnabled) return;
    let stream: MediaStream | null = null;
    navigator.mediaDevices
      .getUserMedia({ video: { width: { ideal: 2560 }, height: { ideal: 1440 }, frameRate: { ideal: 30 } }, audio: false })
      .then((s) => {
        stream = s;
        setPreview(s);
        if (videoRef.current) videoRef.current.srcObject = s;
      })
      .catch(() => {});
    return () => {
      stream?.getTracks().forEach((t) => t.stop());
      setPreview(null);
    };
  }, [videoEnabled]);

  const handleJoin = () => {
    try {
      localStorage.setItem(LS_AUDIO_ENABLED, String(audioEnabled));
      localStorage.setItem(LS_VIDEO_ENABLED, String(videoEnabled));
      console.log(`[LiveKit аудио/видео] PreJoin handleJoin: audio=${audioEnabled}, video=${videoEnabled} → localStorage`);
    } catch {}
    preview?.getTracks().forEach((t) => t.stop());
    onJoin(audioEnabled, videoEnabled);
  };

  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%)' }}>
      <div style={{ background: 'var(--md-sys-color-surface)', borderRadius: 20, maxWidth: 520, width: '100%', overflow: 'hidden', boxShadow: '0 8px 32px rgba(0,0,0,0.1)' }}>
        <div style={{ background: 'linear-gradient(135deg, var(--md-sys-color-primary) 0%, #7c4dff 100%)', padding: 24, color: '#fff' }}>
          <h1 style={{ fontSize: 22, fontWeight: 700, margin: 0 }}>Настройки перед входом</h1>
          <p style={{ margin: '8px 0 0 0', opacity: 0.9 }}>Настройте камеру и микрофон</p>
        </div>
        <div style={{ padding: 24 }}>
          <div style={{ marginBottom: 24, background: '#000', borderRadius: 12, aspectRatio: '16/9', overflow: 'hidden' }}>
            {videoEnabled ? (
              <video ref={videoRef} autoPlay playsInline muted style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
            ) : (
              <div style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', color: '#666' }}>
                <span className="material-symbols-outlined" style={{ fontSize: 64 }}>videocam_off</span>
                <p style={{ margin: 8 }}>Камера выключена</p>
              </div>
            )}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 24 }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 16, background: 'var(--md-sys-color-surface-variant)', borderRadius: 12 }}>
              <span>Микрофон</span>
              <button
                onClick={() => {
                  setAudioEnabled((v) => {
                    const next = !v;
                    try {
                      localStorage.setItem(LS_AUDIO_ENABLED, String(next));
                      console.log(`[LiveKit аудио/видео] PreJoin toggle микрофон: ${next} → localStorage`);
                    } catch {}
                    return next;
                  });
                }}
                style={{ padding: '8px 20px', borderRadius: 10, border: 'none', background: audioEnabled ? 'var(--md-sys-color-primary)' : '#666', color: '#fff', cursor: 'pointer' }}
              >
                {audioEnabled ? 'Выключить' : 'Включить'}
              </button>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 16, background: 'var(--md-sys-color-surface-variant)', borderRadius: 12 }}>
              <span>Камера</span>
              <button
                onClick={() => {
                  setVideoEnabled((v) => {
                    const next = !v;
                    try {
                      localStorage.setItem(LS_VIDEO_ENABLED, String(next));
                      console.log(`[LiveKit аудио/видео] PreJoin toggle камера: ${next} → localStorage`);
                    } catch {}
                    return next;
                  });
                }}
                style={{ padding: '8px 20px', borderRadius: 10, border: 'none', background: videoEnabled ? 'var(--md-sys-color-primary)' : '#666', color: '#fff', cursor: 'pointer' }}
              >
                {videoEnabled ? 'Выключить' : 'Включить'}
              </button>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 12 }}>
            <button onClick={onCancel} style={{ flex: 1, padding: '14px 24px', borderRadius: 14, border: '1px solid var(--md-sys-color-outline)', background: 'transparent', cursor: 'pointer' }}>
              Отмена
            </button>
            <button onClick={handleJoin} style={{ flex: 1, padding: '14px 24px', borderRadius: 14, border: 'none', background: 'var(--md-sys-color-primary)', color: 'var(--md-sys-color-on-primary)', fontWeight: 600, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
              <span className="material-symbols-outlined">videocam</span>
              Войти в конференцию
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

type RoomContentProps = {
  lessonId: number | null;
  boardId: string | null;
  boardLoading: boolean;
  showBoard: boolean;
  setShowBoard: (v: boolean) => void;
  userDisplayName: string;
};

function RoomContent({ lessonId, boardId, boardLoading, showBoard, setShowBoard, userDisplayName }: RoomContentProps) {
  const room = useRoomContext();
  const router = useRouter();
  const { user } = useAuth();
  const [showPlatformChat, setShowPlatformChat] = useState(false);
  const [lessonChat, setLessonChat] = useState<Chat | null>(null);
  const [lessonChatLoading, setLessonChatLoading] = useState(false);
  const [showExitModal, setShowExitModal] = useState(false);
  const [showNavMenu, setShowNavMenu] = useState(false);
  const [navBadges, setNavBadges] = useState<NavBadges | null>(null);

  useEffect(() => {
    if (!user) return;
    getNavBadges().then(setNavBadges).catch(() => setNavBadges(null));
  }, [user]);

  // Фиксируем подключение ментора/студента для метрик (lessonId — резервный поиск комнаты)
  useEffect(() => {
    const onConnected = () => {
      if (room.name || lessonId)
        participantConnected({ roomName: room.name || '', lessonId: lessonId ?? undefined }).catch(() => {});
    };
    room.on(RoomEvent.Connected, onConnected);
    if (room.state === 'connected' && (room.name || lessonId))
      participantConnected({ roomName: room.name || '', lessonId: lessonId ?? undefined }).catch(() => {});
    return () => {
      room.off(RoomEvent.Connected, onConnected);
    };
  }, [room]);

  useEffect(() => {
    if (!showPlatformChat || !lessonId) {
      if (!showPlatformChat) setLessonChat(null);
      return;
    }
    setLessonChatLoading(true);
    getOrCreateLessonChat(lessonId)
      .then((c) => setLessonChat(c))
      .catch(() => setLessonChat(null))
      .finally(() => setLessonChatLoading(false));
  }, [showPlatformChat, lessonId]);

  // Вставляем бургер (слева от микрофона) и кнопку «Выйти» в панель LiveKit
  useEffect(() => {
    if (showBoard) return;
    const id = setTimeout(() => {
      const bar = document.querySelector('.lk-control-bar');
      if (!bar) return;
      if (!bar.querySelector('.lk-burger-button')) {
        const burger = document.createElement('button');
        burger.type = 'button';
        burger.className = 'lk-button lk-burger-button';
        burger.title = 'Меню';
        burger.innerHTML = '<span class="material-symbols-outlined" style="font-size: 20px; width: 20px; height: 20px;">menu</span>';
        burger.addEventListener('click', () => window.dispatchEvent(new CustomEvent('livekit-burger-click')));
        bar.insertBefore(burger, bar.firstChild);
      }
      if (!bar.querySelector('.lk-custom-exit-button')) {
        const btn = document.createElement('button');
        btn.type = 'button';
        btn.className = 'lk-button lk-custom-exit-button';
        btn.setAttribute('data-lk-source', 'disconnect');
        btn.title = 'Выйти';
        btn.innerHTML = '<span class="material-symbols-outlined" style="font-size: 16px; width: 16px; height: 16px;">logout</span>';
        btn.addEventListener('click', () => window.dispatchEvent(new CustomEvent('livekit-exit-click')));
        bar.appendChild(btn);
      }
    }, 800);
    return () => clearTimeout(id);
  }, [showBoard]);

  useEffect(() => {
    const handler = () => setShowNavMenu((v) => !v);
    window.addEventListener('livekit-burger-click', handler);
    return () => window.removeEventListener('livekit-burger-click', handler);
  }, []);

  useEffect(() => {
    const handler = () => {
      if (user?.role === 'mentor') {
        setShowExitModal(true);
      } else {
        room.disconnect();
      }
    };
    window.addEventListener('livekit-exit-click', handler);
    return () => window.removeEventListener('livekit-exit-click', handler);
  }, [user?.role, room]);

  // Сохраняем в localStorage при переключении микрофона/камеры + логи для отладки
  useEffect(() => {
    const lp = room.localParticipant;
    if (!lp) return;
    const log = (source: string, audio: boolean, video: boolean) => {
      console.log(`[LiveKit аудио/видео] ${source}: audio=${audio}, video=${video} → localStorage.${LS_AUDIO_ENABLED}=${audio}, ${LS_VIDEO_ENABLED}=${video}`);
    };
    const saveFromState = (source: string) => {
      const audio = lp.isMicrophoneEnabled;
      const video = lp.isCameraEnabled;
      try {
        localStorage.setItem(LS_AUDIO_ENABLED, String(audio));
        localStorage.setItem(LS_VIDEO_ENABLED, String(video));
        log(source, audio, video);
      } catch (e) {
        console.error('[LiveKit аудио/видео] Ошибка сохранения:', e);
      }
    };
    const onTrackMuted = (pub: { source?: Track.Source }, participant: { sid?: string }) => {
      if (participant?.sid !== lp.sid) return;
      try {
        if (pub?.source === Track.Source.Microphone) {
          localStorage.setItem(LS_AUDIO_ENABLED, 'false');
          log('TrackMuted(Microphone)', false, lp.isCameraEnabled);
        } else if (pub?.source === Track.Source.Camera) {
          localStorage.setItem(LS_VIDEO_ENABLED, 'false');
          log('TrackMuted(Camera)', lp.isMicrophoneEnabled, false);
        } else {
          saveFromState('TrackMuted(?)');
        }
      } catch (e) {
        console.error('[LiveKit аудио/видео] TrackMuted ошибка:', e);
      }
    };
    const onTrackUnmuted = (pub: { source?: Track.Source }, participant: { sid?: string }) => {
      if (participant?.sid !== lp.sid) return;
      try {
        if (pub?.source === Track.Source.Microphone) {
          localStorage.setItem(LS_AUDIO_ENABLED, 'true');
          log('TrackUnmuted(Microphone)', true, lp.isCameraEnabled);
        } else if (pub?.source === Track.Source.Camera) {
          localStorage.setItem(LS_VIDEO_ENABLED, 'true');
          log('TrackUnmuted(Camera)', lp.isMicrophoneEnabled, true);
        } else {
          saveFromState('TrackUnmuted(?)');
        }
      } catch (e) {
        console.error('[LiveKit аудио/видео] TrackUnmuted ошибка:', e);
      }
    };
    const onLocalTrackPublished = () => saveFromState('LocalTrackPublished');
    room.on(RoomEvent.TrackMuted, onTrackMuted);
    room.on(RoomEvent.TrackUnmuted, onTrackUnmuted);
    room.on(RoomEvent.LocalTrackPublished, onLocalTrackPublished);
    saveFromState('init');
    return () => {
      room.off(RoomEvent.TrackMuted, onTrackMuted);
      room.off(RoomEvent.TrackUnmuted, onTrackUnmuted);
      room.off(RoomEvent.LocalTrackPublished, onLocalTrackPublished);
      /* Не сохраняем при unmount/Disconnected — треки уже удалены, lp.isMicrophoneEnabled вернёт false */
    };
  }, [room]);

  return (
    <div style={{ height: '100vh', position: 'relative', display: 'flex' }}>
      <StartAudioOverlay />
      <RemoteParticipantAvatarPlaceholder />
      <div style={{ flex: 1, position: 'relative', background: '#000' }}>
        {/* Boundary только вокруг VideoConference — при reset доска не перемонтируется */}
        <div
          style={{
            position: 'absolute',
            inset: 0,
            zIndex: showBoard ? 0 : 1,
          }}
        >
          <LiveKitLayoutErrorBoundary>
            <VideoConference chatMessageFormatter={(message) => message} />
          </LiveKitLayoutErrorBoundary>
        </div>
      </div>

      {/* Сайдбар, PiP, бургер и навигация в Portal */}
      {typeof document !== 'undefined' && createPortal(
        <>
          {showBoard && <RemoteParticipantPiP chatOpen={showPlatformChat} />}
          {/* Бургер при открытой доске — панель скрыта, показываем свой бургер */}
          {showBoard && (
            <button
              onClick={() => setShowNavMenu((v) => !v)}
              style={{
                position: 'fixed',
                left: 16,
                bottom: 64,
                width: 48,
                height: 48,
                borderRadius: 12,
                border: 'none',
                background: 'rgba(0,0,0,0.7)',
                color: '#fff',
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                zIndex: 10002,
                boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
              }}
              title="Меню"
            >
              <span className="material-symbols-outlined" style={{ fontSize: 24 }}>menu</span>
            </button>
          )}
          {/* Выдвижная навигация — BottomNavigationBar слева, 3 колонки */}
          {showNavMenu && (
            <>
              <div
                onClick={() => setShowNavMenu(false)}
                style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', zIndex: 10003, backdropFilter: 'blur(4px)' }}
              />
              <Suspense fallback={null}>
                <BottomNavigationBar
                  userRole={user?.role}
                  user={user}
                  navBadges={navBadges}
                  slideout
                  onClose={() => setShowNavMenu(false)}
                />
              </Suspense>
            </>
          )}
          <div
            data-lk-sidebar="camera-board-chat"
            style={{
              position: 'fixed',
              right: showPlatformChat ? CHAT_PANEL_WIDTH + 16 : 16,
              top: '50%',
              transform: 'translateY(-50%)',
              display: 'flex',
              flexDirection: 'column',
              gap: 8,
              padding: 8,
              background: 'rgba(0,0,0,0.7)',
              borderRadius: 12,
              zIndex: 10001,
            }}
          >
            <button
              onClick={() => setShowBoard(false)}
              style={{
                width: 48,
                height: 48,
                borderRadius: 12,
                border: 'none',
                background: !showBoard ? 'var(--md-sys-color-primary)' : 'rgba(255,255,255,0.2)',
                color: '#fff',
                cursor: 'pointer',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
              }}
              title="Камера"
            >
              <span className="material-symbols-outlined" style={{ fontSize: 24 }}>videocam</span>
            </button>
            <button
              type="button"
              onClick={() => (boardId && !boardLoading ? setShowBoard(true) : undefined)}
              disabled={!boardId || boardLoading}
              aria-disabled={!boardId || boardLoading}
              style={{
                width: 48,
                height: 48,
                borderRadius: 12,
                border: 'none',
                background: showBoard ? 'var(--md-sys-color-primary)' : 'rgba(255,255,255,0.2)',
                color: '#fff',
                cursor: boardId && !boardLoading ? 'pointer' : 'not-allowed',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                opacity: !boardId || boardLoading ? 0.6 : 1,
                pointerEvents: !boardId || boardLoading ? 'none' : 'auto',
              }}
              title={
                boardLoading
                  ? 'Загрузка доски...'
                  : !boardId
                    ? 'Доска недоступна (проверяем каждые 10 с)'
                    : 'Доска'
              }
            >
              {boardLoading ? (
                <LoadingSpinner size="small" inline />
              ) : (
                <span className="material-symbols-outlined" style={{ fontSize: 24 }}>draw</span>
              )}
            </button>
            {lessonId != null && (
              <button
                onClick={() => setShowPlatformChat((v) => !v)}
                style={{
                  width: 48,
                  height: 48,
                  borderRadius: 12,
                  border: 'none',
                  background: showPlatformChat ? 'var(--md-sys-color-primary)' : 'rgba(255,255,255,0.2)',
                  color: '#fff',
                  cursor: 'pointer',
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                }}
                title="Чат"
              >
                <span className="material-symbols-outlined" style={{ fontSize: 24 }}>chat</span>
              </button>
            )}
          </div>
        </>,
        document.body
      )}

      {/* Панель чата сервиса (не LiveKit) */}
      {showPlatformChat && (
        <div
          style={{
            position: 'fixed',
            right: 0,
            top: 0,
            bottom: 0,
            width: `min(${CHAT_PANEL_WIDTH}px, 100vw)`,
            background: 'var(--md-sys-color-surface)',
            boxShadow: '-4px 0 24px rgba(0,0,0,0.3)',
            zIndex: 10001,
            display: 'flex',
            flexDirection: 'column',
            overflow: 'hidden',
          }}
        >
          {lessonChatLoading ? (
            <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
              <span className="material-symbols-outlined" style={{ fontSize: 32, opacity: 0.5 }}>progress_activity</span>
            </div>
          ) : (
            <ChatWindow
              chat={lessonChat}
              currentUserId={user?.id ?? null}
              onBack={() => setShowPlatformChat(false)}
            />
          )}
        </div>
      )}

      <ExitLessonModal
        isOpen={showExitModal}
        lessonId={lessonId}
        onClose={() => setShowExitModal(false)}
        onExit={() => room.disconnect()}
      />
    </div>
  );
}

const SS_PREJOIN_DONE = 'livekit_prejoin_done';

/** PreJoin — показываем при первом заходе. После входа в комнату или перезагрузки — пропускаем (sessionStorage). */
function getInitialShowPreJoin(searchParams: URLSearchParams): boolean {
  try {
    if (typeof window !== 'undefined' && sessionStorage.getItem(SS_PREJOIN_DONE) === '1') {
      return false;
    }
    return searchParams.get('skip_prejoin') !== '1';
  } catch {
    return true;
  }
}

function getSavedAudioVideo(): { audio: boolean; video: boolean } {
  try {
    const rawA = localStorage.getItem(LS_AUDIO_ENABLED);
    const rawV = localStorage.getItem(LS_VIDEO_ENABLED);
    const audio = rawA !== 'false';
    const video = rawV !== 'false';
    console.log(`[LiveKit аудио/видео] Чтение из localStorage: ${LS_AUDIO_ENABLED}=${rawA} → audio=${audio}, ${LS_VIDEO_ENABLED}=${rawV} → video=${video}`);
    return { audio, video };
  } catch (e) {
    console.error('[LiveKit аудио/видео] Ошибка чтения localStorage:', e);
    return { audio: true, video: true };
  }
}

export default function LiveKitRoomContent() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const accessToken = searchParams.get('token');
  const lessonIdParam = searchParams.get('lesson_id');
  const { user } = useAuth();
  const onboarding = useOnboarding();

  const [serverUrl, setServerUrl] = useState<string>('');
  const [showPreJoin, setShowPreJoin] = useState(() => getInitialShowPreJoin(searchParams));
  const [audioEnabled, setAudioEnabled] = useState(true);
  const [videoEnabled, setVideoEnabled] = useState(true);
  const [avReady, setAvReady] = useState(false);

  // Подтягиваем из localStorage после монтирования (SSR не имеет доступа к LS)
  useEffect(() => {
    const saved = getSavedAudioVideo();
    console.log('[LiveKit аудио/видео] useEffect: загрузили из localStorage, устанавливаем state:', saved);
    setAudioEnabled(saved.audio);
    setVideoEnabled(saved.video);
    setAvReady(true);
  }, []);
  const [lessonCompleted, setLessonCompleted] = useState(false);
  const [effectiveLessonId, setEffectiveLessonId] = useState<number | null>(null);
  const [boardId, setBoardId] = useState<string | null>(null);
  const [boardLoading, setBoardLoading] = useState(false);
  const boardPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const [isMentor, setIsMentor] = useState(false);
  const [showBoard, setShowBoard] = useState(false);
  const [userDisplayName, setUserDisplayName] = useState('Пользователь');

  // Доска и is_mentor из metadata LiveKit токена или getOrCreateLessonBoard; при отсутствии доски — опрос раз в 10 с
  useEffect(() => {
    const meta = getTokenMetadata(accessToken);
    if (meta.is_mentor === true) setIsMentor(true);
    if (meta.board_id) {
      setBoardId(meta.board_id);
      setBoardLoading(false);
      return;
    }
    if (!effectiveLessonId) {
      setBoardLoading(false);
      return;
    }

    let cancelled = false;
    const stopPoll = () => {
      if (boardPollRef.current) {
        clearInterval(boardPollRef.current);
        boardPollRef.current = null;
      }
    };
    setBoardLoading(true);
    getOrCreateLessonBoard(effectiveLessonId)
      .then((b) => {
        if (!cancelled) {
          setBoardId(b.board_id);
          stopPoll();
        }
      })
      .catch(() => {})
      .finally(() => { if (!cancelled) setBoardLoading(false); });

    boardPollRef.current = setInterval(() => {
      if (cancelled) return;
      getOrCreateLessonBoard(effectiveLessonId)
        .then((b) => {
          if (!cancelled) {
            setBoardId(b.board_id);
            stopPoll();
          }
        })
        .catch(() => {});
    }, 10_000);

    return () => {
      cancelled = true;
      stopPoll();
    };
  }, [accessToken, effectiveLessonId]);

  // Fallback: is_mentor из API урока (lesson.mentor.id === user.id), если токен старый или без metadata
  useEffect(() => {
    if (!effectiveLessonId || !user) return;
    const userId = user.id ?? (user as { pk?: number }).pk;
    if (!userId) return;
    getLesson(String(effectiveLessonId))
      .then((lesson) => {
        const mentorId = typeof lesson.mentor === 'object' && lesson.mentor
          ? Number(lesson.mentor.id)
          : Number(lesson.mentor);
        if (mentorId && Number(userId) === mentorId) {
          setIsMentor(true);
        }
      })
      .catch(() => {});
  }, [effectiveLessonId, user]);

  useEffect(() => {
    const token = localStorage.getItem('access_token');
    if (token) {
      const base = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8123/api';
      fetch(`${base}/profile/me/`, { headers: { Authorization: `Bearer ${token}` } })
        .then((r) => r.json())
        .then((u: { first_name?: string; last_name?: string; email?: string }) => {
          const raw = `${u.first_name || ''} ${u.last_name || ''}`.trim() || u.email || 'Пользователь';
          const name = /%[0-9A-Fa-f]{2}/.test(raw) ? decodeURIComponent(raw) : raw;
          setUserDisplayName(name);
        })
        .catch(() => {});
    }
  }, []);

  // Подсказка по видеозвонку для студента (один раз, после входа в комнату)
  useEffect(() => {
    if (user?.role !== 'client' || !onboarding || showPreJoin) return;
    const t = setTimeout(() => {
      onboarding.runTourManually('livekit');
    }, 3500);
    return () => clearTimeout(t);
  }, [user?.role, onboarding, showPreJoin]);

  useEffect(() => {
    const id = lessonIdParam ? parseInt(lessonIdParam, 10) : null;
    if (id && !isNaN(id)) {
      setEffectiveLessonId(id);
      try {
        sessionStorage.setItem('livekit_lesson_id', String(id));
      } catch {}
    } else {
      try {
        const stored = sessionStorage.getItem('livekit_lesson_id');
        if (stored) setEffectiveLessonId(parseInt(stored, 10));
      } catch {}
    }
  }, [lessonIdParam]);

  useEffect(() => {
    const load = async () => {
      if (lessonIdParam) {
        try {
          const l = await getLesson(lessonIdParam);
          if (l.status === 'completed') {
            const now = new Date();
            const end = l.completed_at ? new Date(l.completed_at) : new Date(l.end_time);
            if (now > new Date(end.getTime() + 10 * 60000)) setLessonCompleted(true);
          }
        } catch {}
      }
      const token = localStorage.getItem('access_token');
      if (token) {
        try {
          const base = process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8123/api';
          const res = await fetch(`${base}/video/livekit/config/`, { headers: { Authorization: `Bearer ${token}` } });
          if (res.ok) {
            const config = await res.json();
            setServerUrl(config.server_url || 'ws://127.0.0.1:7880');
          }
        } catch {}
      }
    };
    load();
  }, [lessonIdParam]);

  if (lessonCompleted) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: 'var(--md-sys-color-surface)' }}>
        <div style={{ textAlign: 'center', padding: 24 }}>
          <p style={{ fontSize: 18, marginBottom: 16 }}>Урок завершён. Видеоконференция недоступна.</p>
          <button onClick={() => router.push('/dashboard')} style={{ padding: '12px 24px', borderRadius: 12, border: 'none', background: 'var(--md-sys-color-primary)', color: 'var(--md-sys-color-on-primary)', cursor: 'pointer' }}>
            На главную
          </button>
        </div>
      </div>
    );
  }

  if (!accessToken || !serverUrl) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
        <p>Загрузка...</p>
      </div>
    );
  }

  if (showPreJoin) {
    return (
      <PreJoinScreen
        onJoin={(audio, video) => {
          try {
            sessionStorage.setItem(SS_PREJOIN_DONE, '1');
          } catch {}
          setAudioEnabled(audio);
          setVideoEnabled(video);
          setShowPreJoin(false);
        }}
        onCancel={() => router.push('/dashboard')}
      />
    );
  }

  if (!avReady) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh' }}>
        <p>Загрузка...</p>
      </div>
    );
  }

  console.log('[LiveKit аудио/видео] Передача в LiveKitRoom:', { audio: audioEnabled, video: videoEnabled });
  return (
    <>
      {/* Доска — сиблинг LiveKitRoom, iframe создаётся один раз, не перемонтируется */}
      {boardId && (
        <div
          key="board-layer"
          style={{
            position: 'fixed',
            inset: 0,
            zIndex: showBoard ? 9999 : 0,
            pointerEvents: showBoard ? 'auto' : 'none',
          }}
        >
          <WhiteboardIframe key={boardId} boardId={boardId} username={userDisplayName} showingBoard={showBoard} isMentor={isMentor} />
        </div>
      )}
      <LiveKitRoom
        token={accessToken}
        serverUrl={serverUrl}
        connect={true}
        audio={audioEnabled}
        video={videoEnabled}
        onDisconnected={() => router.push('/dashboard')}
        style={{ height: '100vh' }}
        data-lk-theme="default"
        options={{
          adaptiveStream: true,
          dynacast: true,
          // Захват до 2K (1440p), при отсутствии поддержки браузер даст меньше
          videoCaptureDefaults: {
            resolution: PRESET_2K.resolution,
            frameRate: 30,
          },
          publishDefaults: {
            simulcast: true,
            // Камера: до 2K, 6 Mbps — вариативность через слои 1080p, 720p, 360p
            videoEncoding: PRESET_2K.encoding,
            // Два слоя поверх основного: 720p и 360p для вариативности при слабом канале
            videoSimulcastLayers: [VideoPresets.h720, VideoPresets.h360],
            // Демонстрация экрана: 2K, 6 Mbps, те же два слоя для адаптации
            screenShareEncoding: { maxBitrate: 6_000_000, maxFramerate: 30 },
            screenShareSimulcastLayers: [VideoPresets.h720, VideoPresets.h360],
            degradationPreference: 'maintain-resolution',
          },
          audioCaptureDefaults: {
            noiseSuppression: true,
            echoCancellation: true,
          },
        }}
      >
        <RoomContent
          lessonId={effectiveLessonId}
          boardId={boardId}
          boardLoading={boardLoading}
          showBoard={showBoard}
          setShowBoard={setShowBoard}
          userDisplayName={userDisplayName}
        />
        <RoomAudioRenderer />
        <ConnectionStateToast />
      </LiveKitRoom>
    </>
  );
}
