import { useState, useEffect, useRef, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Phone, PhoneOff, Video, VideoOff, Mic, MicOff, Monitor, MonitorOff, Minimize2, Volume2, ShieldCheck, ShieldOff, ChevronUp } from 'lucide-react';
import { getSocket } from '../lib/socket';
import { api } from '../lib/api';
import { useLang } from '../lib/i18n';

interface ParticipantInfo {
  id: string;
  username: string;
  displayName?: string;
  avatar?: string | null;
}

interface PeerState {
  pc: RTCPeerConnection;
  remoteStream: MediaStream;
  hasVideo: boolean;
}

interface GroupCallModalProps {
  isOpen: boolean;
  onClose: () => void;
  chatId: string;
  chatName: string;
  callType: 'voice' | 'video';
}

// ICE config reuse
let cachedIceConfig: RTCConfiguration | null = null;
let iceCacheFetchedAt = 0;
const ICE_CACHE_TTL = 3600_000;
const FALLBACK_ICE: RTCConfiguration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'stun:stun1.l.google.com:19302' },
  ],
};

async function getIceServers(): Promise<RTCConfiguration> {
  if (cachedIceConfig && Date.now() - iceCacheFetchedAt < ICE_CACHE_TTL) return cachedIceConfig;
  try {
    const data = await api.getIceServers();
    if (data.iceServers?.length > 0) {
      cachedIceConfig = { iceServers: data.iceServers };
      iceCacheFetchedAt = Date.now();
      return cachedIceConfig;
    }
  } catch { /* fallback */ }
  return FALLBACK_ICE;
}

export default function GroupCallModal({ isOpen, onClose, chatId, chatName, callType: initialCallType }: GroupCallModalProps) {
  const { t } = useLang();
  const [isMuted, setIsMuted] = useState(false);
  const [isVideoOff, setIsVideoOff] = useState(initialCallType === 'voice');
  const [isScreenSharing, setIsScreenSharing] = useState(false);
  const [isMinimized, setIsMinimized] = useState(false);
  const [duration, setDuration] = useState(0);
  const [participants, setParticipants] = useState<Map<string, ParticipantInfo>>(new Map());
  const [remoteVolume, setRemoteVolume] = useState(1);
  const [showVolumeSlider, setShowVolumeSlider] = useState(false);
  const [noiseSuppression, setNoiseSuppression] = useState(false);
  const [joined, setJoined] = useState(false);
  const [microphones, setMicrophones] = useState<MediaDeviceInfo[]>([]);
  const [activeMicId, setActiveMicId] = useState<string>('');
  const [showMicMenu, setShowMicMenu] = useState(false);

  const localStreamRef = useRef<MediaStream | null>(null);
  const screenStreamRef = useRef<MediaStream | null>(null);
  const peersRef = useRef<Map<string, PeerState>>(new Map());
  const localVideoRef = useRef<HTMLVideoElement>(null);
  const timerRef = useRef<ReturnType<typeof setInterval>>(undefined);
  const joinedRef = useRef(false);
  const remoteAudioRefs = useRef<Map<string, HTMLAudioElement>>(new Map());
  // Force re-render when peer video state changes
  const [, forceUpdate] = useState(0);
  // Noise gate refs
  const noiseGateCtxRef = useRef<AudioContext | null>(null);
  const noiseGateGainRef = useRef<GainNode | null>(null);
  const noiseGateRafRef = useRef<number>(0);
  const noiseGateTrackRef = useRef<MediaStreamTrack | null>(null);

  const cleanup = useCallback(() => {
    if (timerRef.current) clearInterval(timerRef.current);
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach(t => t.stop());
      localStreamRef.current = null;
    }
    if (screenStreamRef.current) {
      screenStreamRef.current.getTracks().forEach(t => t.stop());
      screenStreamRef.current = null;
    }
    for (const [, peer] of peersRef.current) {
      peer.pc.close();
    }
    peersRef.current.clear();
    remoteAudioRefs.current.clear();
    // Clean up noise gate
    if (noiseGateRafRef.current) cancelAnimationFrame(noiseGateRafRef.current);
    if (noiseGateTrackRef.current) { noiseGateTrackRef.current.stop(); noiseGateTrackRef.current = null; }
    if (noiseGateCtxRef.current) { noiseGateCtxRef.current.close().catch(() => {}); noiseGateCtxRef.current = null; }
    noiseGateGainRef.current = null;
    setParticipants(new Map());
    setDuration(0);
    setIsMuted(false);
    setIsVideoOff(initialCallType === 'voice');
    setIsScreenSharing(false);
    setIsMinimized(false);
    setJoined(false);
    joinedRef.current = false;
    setNoiseSuppression(false);
    setShowMicMenu(false);
  }, [initialCallType]);

  const createPeerConnection = useCallback(async (targetUserId: string, initiator: boolean) => {
    const iceConfig = await getIceServers();
    const pc = new RTCPeerConnection(iceConfig);
    const remoteStream = new MediaStream();

    const peerState: PeerState = { pc, remoteStream, hasVideo: false };
    peersRef.current.set(targetUserId, peerState);

    // Add local tracks
    if (localStreamRef.current) {
      localStreamRef.current.getTracks().forEach(track => pc.addTrack(track, localStreamRef.current!));
    }
    
    // Always ensure a video m-line exists for future camera/screen share stability
    const hasVideoTransceiver = pc.getTransceivers().some(t => t.receiver?.track?.kind === 'video');
    if (!hasVideoTransceiver) {
      pc.addTransceiver('video', { direction: 'recvonly' });
    }

    pc.onicecandidate = (e) => {
      if (e.candidate) {
        const socket = getSocket();
        socket?.emit('group_ice_candidate', { chatId, targetUserId, candidate: e.candidate });
      }
    };

    pc.ontrack = (e) => {
      if (!remoteStream.getTracks().includes(e.track)) {
        remoteStream.addTrack(e.track);
      }
      const checkHasVideo = () => {
        return remoteStream.getVideoTracks().some(t => t.readyState === 'live' && t.enabled && !t.muted);
      };
      peerState.hasVideo = checkHasVideo();

      e.track.onunmute = () => {
        peerState.hasVideo = checkHasVideo();
        forceUpdate(n => n + 1);
      };
      e.track.onmute = () => {
        peerState.hasVideo = checkHasVideo();
        forceUpdate(n => n + 1);
      };

      // Play audio through audio element
      const audioEl = remoteAudioRefs.current.get(targetUserId);
      if (audioEl && audioEl.srcObject !== remoteStream) {
        audioEl.srcObject = remoteStream;
        audioEl.volume = remoteVolume;
        audioEl.play().catch(() => {});
      }

      forceUpdate(n => n + 1);
    };

    pc.onconnectionstatechange = () => {
      if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') {
        console.warn(`[GroupCall] Peer ${targetUserId} connection ${pc.connectionState}`);
      }
    };

    if (initiator) {
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      const socket = getSocket();
      socket?.emit('group_call_offer', { chatId, targetUserId, offer: pc.localDescription });
    }

    return peerState;
  }, [chatId, remoteVolume]);

  const joinCall = useCallback(async () => {
    if (joinedRef.current) return;
    joinedRef.current = true;

    try {
      const wantVideo = initialCallType === 'video';
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: { noiseSuppression: true, echoCancellation: true, autoGainControl: true },
        video: wantVideo,
      }).catch(async () => {
        // Fallback to audio only
        return navigator.mediaDevices.getUserMedia({ audio: { noiseSuppression: true, echoCancellation: true } });
      });

      localStreamRef.current = stream;
      if (!stream.getVideoTracks().length) setIsVideoOff(true);

      const audioSettings = stream.getAudioTracks()[0]?.getSettings();
      if (audioSettings?.deviceId) setActiveMicId(audioSettings.deviceId);
      refreshMicrophones();

      const socket = getSocket();
      socket?.emit('group_call_join', { chatId, callType: initialCallType });
      setJoined(true);

      timerRef.current = setInterval(() => setDuration(d => d + 1), 1000);
    } catch (err: any) {
      console.error('Error joining group call:', err);
      if (err?.name === 'NotAllowedError' || err?.name === 'NotFoundError') {
        alert('Разрешите доступ к микрофону в настройках браузера для совершения звонков');
      }
      joinedRef.current = false;
    }
  }, [chatId, initialCallType, t]);

  const leaveCall = useCallback(() => {
    const socket = getSocket();
    socket?.emit('group_call_leave', { chatId });
    cleanup();
    onClose();
  }, [chatId, cleanup, onClose]);

  // Toggle mic
  const toggleMic = useCallback(() => {
    if (localStreamRef.current) {
      localStreamRef.current.getAudioTracks().forEach(t => { t.enabled = !t.enabled; });
      setIsMuted(m => !m);
    }
  }, []);

  // Toggle video
  const toggleVideo = useCallback(async () => {
    if (!isVideoOff) {
      // Turn off video
      if (localStreamRef.current) {
        localStreamRef.current.getVideoTracks().forEach(t => { t.enabled = false; });
      }
      setIsVideoOff(true);
    } else {
      // Turn on video
      if (localStreamRef.current?.getVideoTracks().some(t => t.readyState === 'live')) {
        localStreamRef.current.getVideoTracks().forEach(t => { t.enabled = true; });
        setIsVideoOff(false);
      } else {
        try {
          const camStream = await navigator.mediaDevices.getUserMedia({ video: true });
          const videoTrack = camStream.getVideoTracks()[0];
          if (videoTrack && localStreamRef.current) {
            localStreamRef.current.addTrack(videoTrack);
            // Add to all peer connections
            for (const [targetUserId, peer] of peersRef.current) {
              peer.pc.addTrack(videoTrack, localStreamRef.current);
              const offer = await peer.pc.createOffer();
              await peer.pc.setLocalDescription(offer);
              const socket = getSocket();
              socket?.emit('group_call_renegotiate', { chatId, targetUserId, offer: peer.pc.localDescription });
            }
            setIsVideoOff(false);
          }
        } catch { console.warn('Camera unavailable'); }
      }
    }
  }, [isVideoOff]);

  // Toggle screen share
  const toggleScreenShare = useCallback(async () => {
    if (isScreenSharing) {
      if (screenStreamRef.current) {
        screenStreamRef.current.getTracks().forEach(t => t.stop());
        screenStreamRef.current = null;
      }
      // Replace screen track with null on all peers
      for (const [targetUserId, peer] of peersRef.current) {
        const sender = peer.pc.getSenders().find(s => s.track?.kind === 'video');
        if (sender) {
          await sender.replaceTrack(null);
          const transceiver = peer.pc.getTransceivers().find(t => t.sender === sender);
          if (transceiver) transceiver.direction = 'recvonly';
          const offer = await peer.pc.createOffer();
          await peer.pc.setLocalDescription(offer);
          const socket = getSocket();
          socket?.emit('group_call_renegotiate', { chatId, targetUserId, offer: peer.pc.localDescription });
        }
      }
      setIsScreenSharing(false);
    } else {
      try {
        const screenStream = await navigator.mediaDevices.getDisplayMedia({
          video: { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 30 } },
          audio: false,
        });
        screenStreamRef.current = screenStream;
        const screenTrack = screenStream.getVideoTracks()[0];

        for (const [targetUserId, peer] of peersRef.current) {
          const sender = peer.pc.getSenders().find(s => s.track?.kind === 'video');
          if (sender) {
            await sender.replaceTrack(screenTrack);
            const transceiver = peer.pc.getTransceivers().find(t => t.sender === sender);
            if (transceiver && (transceiver.direction === 'recvonly' || transceiver.direction === 'inactive')) {
              transceiver.direction = 'sendrecv';
              const offer = await peer.pc.createOffer();
              await peer.pc.setLocalDescription(offer);
              const socket = getSocket();
              socket?.emit('group_call_renegotiate', { chatId, targetUserId, offer: peer.pc.localDescription });
            }
          } else {
            peer.pc.addTrack(screenTrack, localStreamRef.current || screenStream);
            const offer = await peer.pc.createOffer();
            await peer.pc.setLocalDescription(offer);
            const socket = getSocket();
            socket?.emit('group_call_renegotiate', { chatId, targetUserId, offer: peer.pc.localDescription });
          }
        }

        screenTrack.onended = () => {
          setIsScreenSharing(false);
          if (screenStreamRef.current) {
            screenStreamRef.current.getTracks().forEach(t => t.stop());
            screenStreamRef.current = null;
          }
        };

        setIsScreenSharing(true);
      } catch { console.error('Screen share failed'); }
    }
  }, [isScreenSharing, chatId]);

  // Noise gate with look-ahead: analyse undelayed signal, gate delayed signal
  const applyNoiseGate = useCallback(async () => {
    if (!localStreamRef.current) return;
    const rawTrack = localStreamRef.current.getAudioTracks()[0];
    if (!rawTrack) return;
    try {
      const ctx = new AudioContext();
      const source = ctx.createMediaStreamSource(new MediaStream([rawTrack]));

      // Look-ahead delay: signal is delayed so gate can open before audio arrives
      const delayNode = ctx.createDelay(0.2);
      delayNode.delayTime.value = 0.05; // 50ms look-ahead

      // Analysis path: bandpass on voice fundamentals (undelayed, sees audio early)
      const analysisHP = ctx.createBiquadFilter();
      analysisHP.type = 'highpass'; analysisHP.frequency.value = 100; analysisHP.Q.value = 0.7;
      const analysisLP = ctx.createBiquadFilter();
      analysisLP.type = 'lowpass'; analysisLP.frequency.value = 4000; analysisLP.Q.value = 0.7;

      const analyser = ctx.createAnalyser();
      analyser.fftSize = 2048;
      analyser.smoothingTimeConstant = 0.3;

      const gainNode = ctx.createGain();
      gainNode.gain.value = 0;
      const dest = ctx.createMediaStreamDestination();

      // Signal path: source → delay → gain → output (full quality, just gated)
      source.connect(delayNode);
      delayNode.connect(gainNode);
      gainNode.connect(dest);

      // Analysis path (no delay): source → bandpass → analyser
      source.connect(analysisHP);
      analysisHP.connect(analysisLP);
      analysisLP.connect(analyser);

      const dataArray = new Float32Array(analyser.fftSize);
      const OPEN_THRESHOLD = -38;
      const CLOSE_THRESHOLD = -48;
      const CONFIRM_MS = 22;
      const HOLD_TIME = 150;
      const ATTACK = 0.002;
      const RELEASE = 0.02;

      // State machine: 0=closed, 1=pending, 2=open
      let state = 0;
      let pendingSince = 0;
      let holdUntil = 0;

      const check = () => {
        analyser.getFloatTimeDomainData(dataArray);
        let sum = 0;
        for (let i = 0; i < dataArray.length; i++) sum += dataArray[i] * dataArray[i];
        const rms = Math.sqrt(sum / dataArray.length);
        const db = rms > 0 ? 20 * Math.log10(rms) : -100;
        const now = performance.now();

        if (state === 0) {
          if (db > OPEN_THRESHOLD) {
            state = 1;
            pendingSince = now;
          }
        } else if (state === 1) {
          if (db <= CLOSE_THRESHOLD) {
            state = 0;
          } else if (now - pendingSince >= CONFIRM_MS) {
            state = 2;
            holdUntil = now + HOLD_TIME;
            gainNode.gain.setTargetAtTime(1, ctx.currentTime, ATTACK);
          }
        } else {
          if (db > CLOSE_THRESHOLD) {
            holdUntil = now + HOLD_TIME;
          }
          if (now >= holdUntil) {
            state = 0;
            gainNode.gain.setTargetAtTime(0, ctx.currentTime, RELEASE);
          }
        }
        noiseGateRafRef.current = requestAnimationFrame(check);
      };
      check();

      const gatedTrack = dest.stream.getAudioTracks()[0];
      gatedTrack.enabled = !isMuted;

      // Replace on all peers
      for (const [, peer] of peersRef.current) {
        const sender = peer.pc.getSenders().find(s => s.track?.kind === 'audio');
        if (sender) await sender.replaceTrack(gatedTrack);
      }

      noiseGateCtxRef.current = ctx;
      noiseGateGainRef.current = gainNode;
      noiseGateTrackRef.current = gatedTrack;
      setNoiseSuppression(true);
    } catch (err) {
      console.error('Failed to apply noise gate:', err);
    }
  }, [isMuted]);

  const removeNoiseGate = useCallback(async () => {
    // Restore raw mic track to all peers
    if (localStreamRef.current) {
      const rawTrack = localStreamRef.current.getAudioTracks()[0];
      if (rawTrack) {
        for (const [, peer] of peersRef.current) {
          const sender = peer.pc.getSenders().find(s => s.track?.kind === 'audio');
          if (sender) await sender.replaceTrack(rawTrack);
        }
      }
    }
    if (noiseGateRafRef.current) { cancelAnimationFrame(noiseGateRafRef.current); noiseGateRafRef.current = 0; }
    if (noiseGateTrackRef.current) { noiseGateTrackRef.current.stop(); noiseGateTrackRef.current = null; }
    if (noiseGateCtxRef.current) { noiseGateCtxRef.current.close().catch(() => {}); noiseGateCtxRef.current = null; }
    noiseGateGainRef.current = null;
    setNoiseSuppression(false);
  }, []);

  const toggleNoiseSuppression = useCallback(async () => {
    if (noiseSuppression) {
      await removeNoiseGate();
    } else {
      await applyNoiseGate();
    }
  }, [noiseSuppression, applyNoiseGate, removeNoiseGate]);

  // Enumerate microphones
  const refreshMicrophones = useCallback(async () => {
    try {
      const devices = await navigator.mediaDevices.enumerateDevices();
      const audioInputs = devices.filter(d => d.kind === 'audioinput');
      setMicrophones(audioInputs);
      return audioInputs;
    } catch { return []; }
  }, []);

  // Switch microphone
  const switchMicrophone = useCallback(async (deviceId: string) => {
    setShowMicMenu(false);
    try {
      const newStream = await navigator.mediaDevices.getUserMedia({
        audio: { deviceId: { exact: deviceId }, echoCancellation: true, noiseSuppression: true, autoGainControl: true },
      });
      const newTrack = newStream.getAudioTracks()[0];
      if (!newTrack) return;
      newTrack.enabled = !isMuted;

      const wasGated = noiseSuppression;
      if (wasGated) await removeNoiseGate();

      if (localStreamRef.current) {
        localStreamRef.current.getAudioTracks().forEach(t => {
          localStreamRef.current!.removeTrack(t);
          t.stop();
        });
        localStreamRef.current.addTrack(newTrack);
      }
      for (const [, peer] of peersRef.current) {
        const sender = peer.pc.getSenders().find(s => s.track?.kind === 'audio');
        if (sender) await sender.replaceTrack(newTrack);
      }
      setActiveMicId(deviceId);
      if (wasGated) await applyNoiseGate();
    } catch (err) {
      console.error('Switch mic failed:', err);
    }
  }, [isMuted, noiseSuppression, removeNoiseGate, applyNoiseGate]);

  // Volume
  const handleVolumeChange = useCallback((vol: number) => {
    const v = Math.max(0, Math.min(1, vol));
    setRemoteVolume(v);
    for (const [, el] of remoteAudioRefs.current) {
      el.volume = v;
    }
  }, []);

  // Socket event handlers
  useEffect(() => {
    const socket = getSocket();
    if (!socket || !isOpen) return;

    const onParticipants = async (data: { chatId: string; participants: ParticipantInfo[] }) => {
      if (data.chatId !== chatId) return;
      const newMap = new Map<string, ParticipantInfo>();
      for (const p of data.participants) {
        newMap.set(p.id, p);
        // Create peer connection to each existing participant (we are the initiator)
        if (!peersRef.current.has(p.id)) {
          await createPeerConnection(p.id, true);
        }
      }
      setParticipants(prev => {
        const merged = new Map(prev);
        for (const [k, v] of newMap) merged.set(k, v);
        return merged;
      });
    };

    const onUserJoined = (data: { chatId: string; userId: string; userInfo: ParticipantInfo }) => {
      if (data.chatId !== chatId) return;
      setParticipants(prev => {
        const next = new Map(prev);
        next.set(data.userId, data.userInfo);
        return next;
      });
      // The new joiner will send us an offer — we wait for it
    };

    const onUserLeft = (data: { chatId: string; userId: string }) => {
      if (data.chatId !== chatId) return;
      const peer = peersRef.current.get(data.userId);
      if (peer) {
        peer.pc.close();
        peersRef.current.delete(data.userId);
      }
      remoteAudioRefs.current.delete(data.userId);
      setParticipants(prev => {
        const next = new Map(prev);
        next.delete(data.userId);
        return next;
      });
      forceUpdate(n => n + 1);
    };

    const onOffer = async (data: { chatId: string; from: string; offer: RTCSessionDescriptionInit }) => {
      if (data.chatId !== chatId) return;
      let peerState = peersRef.current.get(data.from);
      if (!peerState) {
        peerState = await createPeerConnection(data.from, false);
      }
      await peerState.pc.setRemoteDescription(new RTCSessionDescription(data.offer));
      const answer = await peerState.pc.createAnswer();
      await peerState.pc.setLocalDescription(answer);
      socket.emit('group_call_answer', { chatId, targetUserId: data.from, answer: peerState.pc.localDescription });
    };

    const onAnswer = async (data: { chatId: string; from: string; answer: RTCSessionDescriptionInit }) => {
      if (data.chatId !== chatId) return;
      const peerState = peersRef.current.get(data.from);
      if (peerState) {
        await peerState.pc.setRemoteDescription(new RTCSessionDescription(data.answer));
      }
    };

    const onIceCandidate = (data: { chatId: string; from: string; candidate: RTCIceCandidateInit }) => {
      if (data.chatId !== chatId) return;
      const peerState = peersRef.current.get(data.from);
      if (peerState?.pc.remoteDescription) {
        peerState.pc.addIceCandidate(new RTCIceCandidate(data.candidate)).catch(console.error);
      }
    };

    const onRenegotiate = async (data: { chatId: string; from: string; offer: RTCSessionDescriptionInit }) => {
      if (data.chatId !== chatId) return;
      const peerState = peersRef.current.get(data.from);
      if (!peerState) return;
      await peerState.pc.setRemoteDescription(new RTCSessionDescription(data.offer));
      const answer = await peerState.pc.createAnswer();
      await peerState.pc.setLocalDescription(answer);
      socket.emit('group_call_renegotiate_answer', { chatId, targetUserId: data.from, answer: peerState.pc.localDescription });
    };

    const onRenegotiateAnswer = async (data: { chatId: string; from: string; answer: RTCSessionDescriptionInit }) => {
      if (data.chatId !== chatId) return;
      const peerState = peersRef.current.get(data.from);
      if (peerState) {
        await peerState.pc.setRemoteDescription(new RTCSessionDescription(data.answer));
      }
    };

    socket.on('group_call_participants', onParticipants);
    socket.on('group_call_user_joined', onUserJoined);
    socket.on('group_call_user_left', onUserLeft);
    socket.on('group_call_offer', onOffer);
    socket.on('group_call_answer', onAnswer);
    socket.on('group_ice_candidate', onIceCandidate);
    socket.on('group_call_renegotiate', onRenegotiate);
    socket.on('group_call_renegotiate_answer', onRenegotiateAnswer);

    return () => {
      socket.off('group_call_participants', onParticipants);
      socket.off('group_call_user_joined', onUserJoined);
      socket.off('group_call_user_left', onUserLeft);
      socket.off('group_call_offer', onOffer);
      socket.off('group_call_answer', onAnswer);
      socket.off('group_ice_candidate', onIceCandidate);
      socket.off('group_call_renegotiate', onRenegotiate);
      socket.off('group_call_renegotiate_answer', onRenegotiateAnswer);
    };
  }, [isOpen, chatId, createPeerConnection]);

  // Auto-join on open
  useEffect(() => {
    if (isOpen && !joined) {
      joinCall();
    }
  }, [isOpen, joined, joinCall]);

  // Sync local video
  useEffect(() => {
    if (!localVideoRef.current) return;
    const desired = isScreenSharing && screenStreamRef.current ? screenStreamRef.current : localStreamRef.current;
    if (desired && localVideoRef.current.srcObject !== desired) {
      localVideoRef.current.srcObject = desired;
    }
  });

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (joinedRef.current) {
        const socket = getSocket();
        socket?.emit('group_call_leave', { chatId });
      }
      cleanup();
    };
  }, [chatId, cleanup]);

  const formatDuration = (sec: number) => {
    const m = Math.floor(sec / 60);
    const s = sec % 60;
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  };

  if (!isOpen) return null;

  const participantList = Array.from(participants.values());
  const hasLocalVideo = !!(localStreamRef.current?.getVideoTracks().some(t => t.enabled) || isScreenSharing);

  // Grid columns based on participant count
  const totalStreams = participantList.length + 1; // +1 for self
  const gridCols = totalStreams <= 1 ? 1 : totalStreams <= 4 ? 2 : 3;

  return (
    <AnimatePresence>
      {/* Hidden audio elements for each remote participant */}
      {participantList.map(p => (
        <audio
          key={`audio-${p.id}`}
          ref={el => { if (el) remoteAudioRefs.current.set(p.id, el); }}
          autoPlay
          playsInline
        />
      ))}

      {isMinimized && joined ? (
        <motion.div
          key="group-call-minimized"
          initial={{ opacity: 0, y: 50, scale: 0.8 }}
          animate={{ opacity: 1, y: 0, scale: 1 }}
          exit={{ opacity: 0, y: 50, scale: 0.8 }}
          className="fixed bottom-6 right-6 z-[100] flex items-center gap-3 px-4 py-3 rounded-2xl glass-strong shadow-2xl shadow-black/50 border border-white/10 cursor-pointer select-none"
          onClick={() => setIsMinimized(false)}
        >
          <div className="relative">
            <div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-call-wave" />
            <div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center text-white font-bold text-sm">
              {participantList.length + 1}
            </div>
          </div>
          <div className="min-w-0">
            <p className="text-sm text-white font-medium truncate max-w-[120px]">{chatName}</p>
            <p className="text-xs text-zinc-400 font-mono">{formatDuration(duration)}</p>
          </div>
          <div className="flex items-center gap-2 ml-2" onClick={e => e.stopPropagation()}>
            <button
              onClick={toggleMic}
              className={`w-8 h-8 rounded-full flex items-center justify-center transition-colors ${isMuted ? 'bg-red-500/20 text-red-400' : 'bg-white/10 text-white hover:bg-white/20'}`}
            >
              {isMuted ? <MicOff size={14} /> : <Mic size={14} />}
            </button>
            <button
              onClick={leaveCall}
              className="w-8 h-8 rounded-full bg-red-500 hover:bg-red-600 flex items-center justify-center text-white transition-colors"
            >
              <PhoneOff size={14} />
            </button>
          </div>
        </motion.div>
      ) : (
        <motion.div
          key="group-call-overlay"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          className="fixed inset-0 z-[100] flex items-center justify-center bg-surface/90 backdrop-blur-xl overflow-hidden"
          onClick={() => setShowVolumeSlider(false)}
        >
          <div className="absolute inset-0 pointer-events-none opacity-40">
            <div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-emerald-500/30 rounded-full blur-[120px] animate-float" />
            <div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-vortex-500/20 rounded-full blur-[120px] animate-float-delayed" />
          </div>

          <motion.div
            initial={{ scale: 0.9, opacity: 0, y: 20 }}
            animate={{ scale: 1, opacity: 1, y: 0 }}
            transition={{ type: 'spring', damping: 25, stiffness: 300 }}
            className="relative w-full max-w-5xl mx-4 rounded-[2.5rem] glass-strong shadow-2xl shadow-black/50 overflow-hidden border border-white/5"
          >
            {/* Volume slider popup */}
            {showVolumeSlider && (
              <>
                <div className="fixed inset-0 z-[200]" onClick={() => setShowVolumeSlider(false)} />
                <div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[250px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl p-4" onClick={e => e.stopPropagation()}>
                  <div className="text-xs text-zinc-400 uppercase tracking-wider mb-3">{t('volume')}</div>
                  <div className="flex items-center gap-3">
                    <Volume2 size={16} className="text-zinc-400 shrink-0" />
                    <input
                      type="range"
                      min="0"
                      max="1"
                      step="0.05"
                      value={remoteVolume}
                      onChange={e => handleVolumeChange(parseFloat(e.target.value))}
                      className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-vortex-500 cursor-pointer"
                    />
                    <span className="text-xs text-zinc-300 w-8 text-right">{Math.round(remoteVolume * 100)}%</span>
                  </div>
                </div>
              </>
            )}
            {/* Mic selector popup */}
            {showMicMenu && microphones.length > 0 && (
              <>
                <div className="fixed inset-0 z-[200]" onClick={() => setShowMicMenu(false)} />
                <div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[320px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl py-2">
                  <div className="px-3 py-1.5 text-xs text-zinc-400 uppercase tracking-wider border-b border-zinc-700 mb-1">{t('selectMicrophone')}</div>
                  {microphones.map((mic, i) => (
                    <button
                      key={mic.deviceId}
                      onClick={() => switchMicrophone(mic.deviceId)}
                      className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeMicId === mic.deviceId
                          ? 'text-vortex-400 bg-vortex-500/20 font-medium'
                          : 'text-zinc-200 hover:bg-zinc-700'
                        }`}
                    >
                      {mic.label || `${t('microphone')} ${i + 1}`}
                    </button>
                  ))}
                </div>
              </>
            )}

            {/* Header */}
            <div className="flex items-center justify-between px-8 py-4 border-b border-white/5">
              <div>
                <h3 className="text-lg font-bold text-white">{chatName}</h3>
                <p className="text-xs text-zinc-400">{participantList.length + 1} {t('participants') || 'участников'} · {formatDuration(duration)}</p>
              </div>
              <button
                onClick={() => setIsMinimized(true)}
                className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center text-white/70 hover:text-white transition-colors"
                title={t('minimize')}
              >
                <Minimize2 size={16} />
              </button>
            </div>

            {/* Participant grid */}
            <div className="p-4" style={{ minHeight: '300px', maxHeight: '60vh', overflowY: 'auto' }}>
              <div
                className="grid gap-3"
                style={{ gridTemplateColumns: `repeat(${gridCols}, 1fr)` }}
              >
                {/* Self */}
                <div className="relative bg-zinc-900 rounded-2xl overflow-hidden aspect-video flex items-center justify-center border border-white/5">
                  {hasLocalVideo ? (
                    <video ref={localVideoRef} autoPlay playsInline muted className="w-full h-full object-contain" />
                  ) : (
                    <div className="flex flex-col items-center">
                      <div className="w-16 h-16 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-xl mb-2">
                        {t('you')?.charAt(0).toUpperCase() || 'Я'}
                      </div>
                      {isMuted && <MicOff size={14} className="text-red-400" />}
                    </div>
                  )}
                  <div className="absolute bottom-2 left-2 px-2 py-0.5 rounded-full bg-black/60 text-xs text-white">
                    {t('you')} {isMuted ? '🔇' : ''}
                  </div>
                </div>

                {/* Remote participants */}
                {participantList.map(p => {
                  const peer = peersRef.current.get(p.id);
                  const hasVid = peer?.hasVideo;
                  const initials = (p.displayName || p.username).split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();

                  return (
                    <div key={p.id} className="relative bg-zinc-900 rounded-2xl overflow-hidden aspect-video flex items-center justify-center border border-white/5 cursor-pointer" title={t('rightClickVolume')} onContextMenu={(e) => { e.preventDefault(); setShowVolumeSlider(true); }}>
                      {hasVid ? (
                        <video
                          autoPlay
                          playsInline
                          muted
                          ref={el => {
                            if (el && peer?.remoteStream && el.srcObject !== peer.remoteStream) {
                              el.srcObject = peer.remoteStream;
                            }
                          }}
                          className="w-full h-full object-contain"
                        />
                      ) : (
                        <div className="flex flex-col items-center">
                          {p.avatar ? (
                            <img src={p.avatar} alt="" className="w-16 h-16 rounded-full object-cover mb-2" />
                          ) : (
                            <div className="w-16 h-16 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-xl mb-2">
                              {initials}
                            </div>
                          )}
                        </div>
                      )}
                      <div className="absolute bottom-2 left-2 px-2 py-0.5 rounded-full bg-black/60 text-xs text-white truncate max-w-[80%]">
                        {p.displayName || p.username}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>

            {/* Controls */}
            <div className="px-8 pb-8 pt-4 flex items-center justify-center gap-4 flex-wrap">
              {/* Mic with dropdown */}
              <div className="relative">
                <button
                  onClick={toggleMic}
                  className={`w-11 h-11 rounded-full flex items-center justify-center transition-colors ${isMuted ? 'bg-red-500/20 text-red-400' : 'bg-white/10 text-white hover:bg-white/20'}`}
                  title={isMuted ? t('unmute') : t('mute')}
                >
                  {isMuted ? <MicOff size={18} /> : <Mic size={18} />}
                </button>
                <button
                  onClick={async (e) => { e.stopPropagation(); await refreshMicrophones(); setShowMicMenu(!showMicMenu); setShowVolumeSlider(false); }}
                  className="absolute -top-1 -right-1 w-5 h-5 rounded-full bg-zinc-700 hover:bg-zinc-600 flex items-center justify-center text-white/70 hover:text-white transition-colors border border-zinc-600"
                >
                  <ChevronUp size={10} />
                </button>
              </div>
              {/* Camera toggle */}
              <button
                onClick={toggleVideo}
                className={`w-11 h-11 rounded-full flex items-center justify-center transition-colors ${isVideoOff ? 'bg-red-500/20 text-red-400' : 'bg-white/10 text-white hover:bg-white/20'}`}
              >
                {isVideoOff ? <VideoOff size={18} /> : <Video size={18} />}
              </button>
              <button
                onClick={toggleScreenShare}
                className={`w-11 h-11 rounded-full flex items-center justify-center transition-colors ${isScreenSharing ? 'bg-vortex-500/30 text-vortex-400' : 'bg-white/10 text-white hover:bg-white/20'}`}
                title={isScreenSharing ? t('stopScreenShare') : t('screenShare')}
              >
                {isScreenSharing ? <MonitorOff size={18} /> : <Monitor size={18} />}
              </button>
              <button
                onClick={e => { e.stopPropagation(); setShowVolumeSlider(!showVolumeSlider); }}
                className="w-11 h-11 rounded-full flex items-center justify-center transition-colors bg-white/10 text-white hover:bg-white/20"
                title={t('volume')}
              >
                <Volume2 size={18} />
              </button>
              <button
                onClick={toggleNoiseSuppression}
                className={`w-11 h-11 rounded-full flex items-center justify-center transition-colors ${noiseSuppression ? 'bg-emerald-500/20 text-emerald-400' : 'bg-white/10 text-white hover:bg-white/20'}`}
                title={noiseSuppression ? t('noiseSuppressionOn') : t('noiseSuppressionOff')}
              >
                {noiseSuppression ? <ShieldCheck size={18} /> : <ShieldOff size={18} />}
              </button>
              <button
                onClick={leaveCall}
                className="w-14 h-14 rounded-full bg-red-500 hover:bg-red-600 flex items-center justify-center text-white shadow-xl shadow-red-500/30 transition-all hover:scale-105 ml-2"
              >
                <PhoneOff size={22} />
              </button>
            </div>
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}
