T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.ca/public_html/src/components/Chat/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/components/Chat/GroupChat.tsx
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useSession } from 'next-auth/react';
import { motion, AnimatePresence } from 'framer-motion';
import { format, formatDistanceToNow } from 'date-fns';
import { useToast } from '@/components/ui/use-toast';
import { toast as hotToast } from 'react-hot-toast';
import { useWebSocket } from '../../context/StableWebSocketContext';
import ParticipantActions from './ParticipantActions';
// import VideoCall from './VideoCall'; // ✅ DISABLED - Using global SimpleVideoCall
import DirectMessage from './DirectMessage';
import ConnectionStatus from '../ConnectionStatus';
import TypingIndicator from '../TypingIndicator';
import UserPresence from '../UserPresence';
import EmojiPicker, { saveRecentEmoji } from '../ui/EmojiPicker';
import MessageReactions from '../ui/MessageReactions';
import FileUpload from '../ui/FileUpload';
import UserAvatar from '../UserAvatar';
import ProfilePopover from '../ProfilePopover';

// Type Definitions
interface User {
  id: string;
  name: string;
  email?: string;
  role?: string;
  profilePicture?: string;
  title?: string;
  specialization?: string;
  availability?: string;
  lastActive?: string;
  bio?: string;
}

interface Message {
  id: string;
  content: string;
  createdAt: string;
  user: User;
  chatRoomId: string;
  isOptimistic?: boolean;
  type?: 'USER' | 'SYSTEM' | 'ERROR' | 'ACTION'; // To distinguish between user messages and local system messages
  isAction?: boolean;
  // Modern features
  fileUrl?: string;
  fileName?: string;
  fileSize?: number;
  mimeType?: string;
  isEdited?: boolean;
  editedAt?: string;
  replyToId?: string;
  reactions?: Array<{
    id: string;
    emoji: string;
    userId: string;
    user: {
      id: string;
      name: string;
    };
    createdAt: string;
  }>;
  replyTo?: {
    id: string;
    content: string;
    user: {
      id: string;
      name: string;
    };
    fileUrl?: string;
    mimeType?: string;
    fileName?: string;
  };
}

interface Participant {
  user: User;
  role: string;
}

interface ChatRoom {
  id: string;
  name: string;
  participants: Participant[];
  lastMessage?: Message | null;
  _count?: {
    messages: number;
  };
  description?: string;
}

interface DirectMessageNotification {
  senderName: string;
  unreadCount: number;
  lastMessage?: string;
}

// Helper Components
const SkeletonLoader = ({ count = 1, className = 'h-10 w-full' }) => (
  <>
    {[...Array(count)].map((_, i) => (
      <div key={i} className={`bg-gray-200 dark:bg-gray-700 rounded animate-pulse ${className}`} />
    ))}
  </>
);

const EmptyState = ({ title, message }: { title: string, message: string }) => (
  <div className="flex flex-col items-center justify-center h-full text-center text-gray-500 p-8">
    <svg className="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 5.523-4.477 10-10 10S1 17.523 1 12 5.477 2 11 2s10 4.477 10 10z" /></svg>
    <h3 className="text-xl font-semibold text-gray-700 dark:text-gray-300">{title}</h3>
    <p className="mt-1">{message}</p>
  </div>
);


const GroupChat: React.FC = () => {
  const { data: session } = useSession();
  const { ws, connected, sendTyping, joinRoom, leaveRoom, connectionState, directMessageNotifications, getTotalUnreadDirectMessages, reconnect } = useWebSocket();
  const { toast } = useToast();
  
  // Debug WebSocket connection
  useEffect(() => {
    console.log(`[GroupChat] 🔌 WebSocket status changed:`, {
      hasWs: !!ws,
      connected,
      connectionState,
      readyState: ws?.readyState,
      sessionUser: session?.user?.name
    });
    
    // Auto-retry connection if it fails during chat usage
    if (connectionState === 'disconnected' && session?.user?.id && !connected) {
      const retryTimeout = setTimeout(() => {
        console.log('[GroupChat] 🔄 Auto-retrying WebSocket connection...');
        reconnect();
      }, 3000);
      
      return () => clearTimeout(retryTimeout);
    }
  }, [ws, connected, connectionState, session?.user?.name, session?.user?.id, reconnect]);

  // Request notification permission on component mount
  useEffect(() => {
    if ('Notification' in window && Notification.permission === 'default') {
      Notification.requestPermission().then(permission => {
        console.log('[GroupChat] Notification permission:', permission);
      });
    }
  }, []);

  // Video call event listener removed for now

  const [chatRooms, setChatRooms] = useState<ChatRoom[]>([]);
  const [selectedRoom, setSelectedRoom] = useState<ChatRoom | null>(null);
  const [messages, setMessages] = useState<Message[]>([]);
  const [newMessage, setNewMessage] = useState('');
  const [isLoadingRooms, setIsLoadingRooms] = useState(true);
  const [isLoadingMessages, setIsLoadingMessages] = useState(false);
  const [showParticipants, setShowParticipants] = useState(true);
  const [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());
  const [isTyping, setIsTyping] = useState(false);
  const [showCreateRoom, setShowCreateRoom] = useState(false);
  const [newRoomName, setNewRoomName] = useState('');
  const [isCreatingRoom, setIsCreatingRoom] = useState(false);
  const [participants, setParticipants] = useState<Participant[]>([]);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const selectedRoomRef = useRef(selectedRoom);
  const joinedRoomsRef = useRef<Set<string>>(new Set());
  const typingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
  const [showPrivateChat, setShowPrivateChat] = useState<{ open: boolean, registrationId: string | null }>({ open: false, registrationId: null });
  const messagesContainerRef = useRef<HTMLDivElement | null>(null);
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
  // Video call state removed for now
  const [welcomeMessage, setWelcomeMessage] = useState<string | null>(null);
  const [directMessage, setDirectMessage] = useState<{ recipientId: string; recipientName: string } | null>(null);
  
  // Modern chat features
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [uploading, setUploading] = useState(false);
  const [replyingTo, setReplyingTo] = useState<Message | null>(null);
  
  // Profile popover state
  const [profilePopover, setProfilePopover] = useState<{ userId: string; isOpen: boolean } | null>(null);

  // Mobile responsive states
  const [showLeftSidebar, setShowLeftSidebar] = useState(false);
  const [showRightSidebar, setShowRightSidebar] = useState(false);
  const [isMobile, setIsMobile] = useState(false);

  // Mobile detection and responsive handling
  useEffect(() => {
    const checkMobile = () => {
      const mobile = window.innerWidth < 768;
      setIsMobile(mobile);
      // Close sidebars when switching to desktop
      if (!mobile) {
        setShowLeftSidebar(false);
        setShowRightSidebar(false);
      }
    };

    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);

  useEffect(() => {
    selectedRoomRef.current = selectedRoom;
  }, [selectedRoom]);

  // Fetch initial chat rooms once
  useEffect(() => {
    fetchChatRooms();
  }, []);

  // Fetch messages when a room is selected
  useEffect(() => {
    if (selectedRoom?.id) {
        console.log(`[GroupChat] đŸŽ¯ Room selected: ${selectedRoom.name} (${selectedRoom.id})`);
        fetchMessages(selectedRoom.id);
    } else {
        console.log(`[GroupChat] đŸŽ¯ No room selected, clearing messages`);
        setMessages([]); // Clear messages when no room is selected
    }
  }, [selectedRoom?.id]);

  // Check if user is at bottom of messages
  const checkIfAtBottom = () => {
    if (!messagesContainerRef.current) return true;
    const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current;
    return scrollTop + clientHeight >= scrollHeight - 10; // 10px threshold
  };

  // Handle scroll events
  const handleScroll = () => {
    setShouldAutoScroll(checkIfAtBottom());
  };

  // Scroll to bottom of messages
  useEffect(() => {
    if (shouldAutoScroll && messagesEndRef.current) {
      messagesEndRef.current.scrollIntoView({ behavior: 'auto', block: 'end' });
    }
  }, [messages, shouldAutoScroll]);

  // Reset auto-scroll when changing rooms
  useEffect(() => {
    setShouldAutoScroll(true);
  }, [selectedRoom]);

  // Debug reply state changes
  useEffect(() => {
    if (replyingTo) {
      console.log('🔄 Reply mode activated for message:', replyingTo.id, 'by user:', replyingTo.user.name);
    } else {
      console.log('🔄 Reply mode deactivated');
    }
  }, [replyingTo]);

  // Add a new system message to the chat window
  const addSystemMessage = (content: string, type: 'SYSTEM' | 'ERROR' = 'SYSTEM') => {
    const systemMessage: Message = {
      id: `system-${Date.now()}`,
      content,
      createdAt: new Date().toISOString(),
      user: { id: 'system', name: 'System' },
      chatRoomId: selectedRoom?.id || 'local',
      type,
    };
    setMessages(prev => [...prev, systemMessage]);
  };

  const handleCommand = async (command: string) => {
    if (!session) {
      addSystemMessage('You must be logged in to use commands.', 'ERROR');
      return;
    }

    const [cmd, ...args] = command.substring(1).split(' ');
    const argString = args.join(' ');
    const [targetUser, ...messageParts] = args;
    const message = messageParts.join(' ');

    switch (cmd.toLowerCase()) {
      case 'help': {
        addSystemMessage('--- Help ---');
        addSystemMessage('/help - Shows this help message.');
        addSystemMessage('/clear - Clears the current chat window.');
        addSystemMessage('/switch <room_name> - Switches to a specified room.');
        addSystemMessage('/part - Leaves the current room.');
        addSystemMessage('/list - Lists all available rooms.');
        addSystemMessage('/msg <user_name> <message> - Sends a private message.');
        addSystemMessage('/me <action> - Performs an action message.');
        if (session.user.role === 'ADMIN') {
          addSystemMessage('--- Admin Commands ---');
          addSystemMessage('/createroom <room_name> - Creates a new room.');
          addSystemMessage('/kick <user_name> [reason] - Kicks a user from the room.');
        }
        break;
      }

      case 'clear': {
        setMessages([]);
        break;
      }
        
      case 'list': {
        addSystemMessage('Available rooms:');
        chatRooms.forEach(room => {
            addSystemMessage(`#${room.name} - ${room.participants.length} users`);
        });
        break;
      }

      case 'createroom': {
        if (session.user.role !== 'ADMIN') {
          addSystemMessage('Error: You do not have permission to create rooms.', 'ERROR');
          return;
        }
        if (!argString) {
          addSystemMessage('Usage: /createroom <room_name>', 'ERROR');
          return;
        }
        await handleCreateRoom(undefined, argString);
        break;
      }

      case 'switch':
      case 'join': {
        if (!argString) {
          addSystemMessage('Usage: /switch <room_name>', 'ERROR');
          return;
        }
        const roomToSwitch = chatRooms.find(r => r.name.toLowerCase() === argString.toLowerCase());
        if (roomToSwitch) {
          setSelectedRoom(roomToSwitch);
          addSystemMessage(`Switched to room: ${roomToSwitch.name}`);
        } else {
          addSystemMessage(`Room "${argString}" not found.`, 'ERROR');
        }
        break;
      }

      case 'part': {
        if (selectedRoom) {
          addSystemMessage(`You have left room: ${selectedRoom.name}`);
          setSelectedRoom(null);
        } else {
          addSystemMessage('You are not in a room.', 'ERROR');
        }
        break;
      }
        
      case 'kick': {
        if (session.user.role !== 'ADMIN') {
          addSystemMessage('Error: You do not have permission to kick users.', 'ERROR');
          return;
        }
        if (!selectedRoom) {
          addSystemMessage('Error: You must be in a room to kick a user.', 'ERROR');
          return;
        }
        if (!targetUser) {
          addSystemMessage('Usage: /kick <user_name> [reason]', 'ERROR');
          return;
        }
        const participantToKick = participants.find(p => p.user.name.toLowerCase() === targetUser.toLowerCase());

        if (!participantToKick) {
          addSystemMessage(`Error: User "${targetUser}" not found in this room.`, 'ERROR');
          return;
        }
        
        try {
                  const response = await fetch(`/api/chat/rooms/${selectedRoom.id}/participants/${participantToKick.user.id}`, {
          method: 'DELETE',
          credentials: 'same-origin',
        });
          const resData = await response.json();
          if (!response.ok) {
            throw new Error(resData.error || 'Failed to kick user.');
          }
          // The system message will be sent via WebSocket, so we don't need to add it here.
        } catch (err) {
            handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
        }
        break;
      }
        
      case 'msg':
      case 'query': {
        if (!targetUser || !message) {
          addSystemMessage('Usage: /msg <user_name> <message>', 'ERROR');
          return;
        }
        
        let recipient = null;
        for (const room of chatRooms) {
            const found = room.participants.find(p => p.user.name.toLowerCase() === targetUser.toLowerCase());
            if (found) {
                recipient = found.user;
                break;
            }
        }

        if (!recipient) {
          addSystemMessage(`Error: User "${targetUser}" not found.`, 'ERROR');
          return;
        }
        if (recipient.id === session.user.id) {
          addSystemMessage('You cannot send a private message to yourself.', 'ERROR');
          return;
        }

        try {
          const response = await fetch(`/api/chat/direct/${recipient.id}`, {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content: message }),
          });

          if (!response.ok) throw new Error('Failed to send private message.');
          
          addSystemMessage(`Message sent to ${recipient.name}: ${message}`);
        } catch (err) {
          handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
        }
        break;
      }
        
      case 'me': {
        if (!argString) {
          addSystemMessage('Usage: /me <action>', 'ERROR');
          return;
        }
        if (!selectedRoom) {
          addSystemMessage('You must be in a room to perform an action.', 'ERROR');
          return;
        }

        const optimisticId = `optimistic-${Date.now()}`;
        const actionContent = `* ${session.user.name} ${argString}`;
        
        const optimisticMessage: Message = {
          id: optimisticId,
          content: actionContent,
          createdAt: new Date().toISOString(),
          user: session.user as User,
          chatRoomId: selectedRoom.id,
          isOptimistic: true,
          isAction: true,
        };

        setMessages(prev => [...prev, optimisticMessage]);

        try {
            await fetch('/api/chat/group/messages', {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ 
                    content: actionContent, 
                    chatRoomId: selectedRoom.id,
                    isAction: true
                }),
            });
        } catch (err) {
            handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
            setMessages(prev => prev.filter(m => m.id !== optimisticId));
        }
        break;
      }

      default: {
        addSystemMessage(`Unknown command: /${cmd}. Type /help for a list of commands.`, 'ERROR');
        break;
      }
    }
  };

  const handleError = useCallback((message: string, description?: string) => {
    console.error(message, description);
    toast({
      title: message,
      description: description,
      variant: 'destructive',
    });
  }, [toast]);

  // WebSocket message handling
  useEffect(() => {
    if (!ws) return;

    const handleMessage = (event: MessageEvent) => {
      try {
        const data = JSON.parse(event.data);
        console.log(`[GroupChat] đŸ“Ĩ WebSocket message received:`, data.type, data);
        
        switch (data.type) {
          case 'CHAT_MESSAGE':
            console.log(`[GroupChat] đŸ’Ŧ Processing chat message for room ${data.data.chatRoomId}, current room: ${selectedRoom?.id}`);
            console.log(`[GroupChat] 📝 Message data:`, data.data);
            
            // Show browser notification for new messages from other users
            if (data.data.user.id !== session?.user?.id) {
              // Only show notification if:
              // 1. The message is NOT for the currently active room, OR
              // 2. The tab is not visible/active
              const isForCurrentRoom = data.data.chatRoomId === selectedRoom?.id;
              const isTabVisible = !document.hidden;
              const roomName = chatRooms.find(r => r.id === data.data.chatRoomId)?.name || 'Chat Room';
              
                             if (!isForCurrentRoom || !isTabVisible) {
                 // Show toast notification
                 hotToast((t) => (
                   <div className="flex items-center space-x-3">
                     <div className="flex-shrink-0">
                       <div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
                         <span className="text-white text-sm font-bold">
                           {data.data.user.name?.charAt(0) || 'U'}
                         </span>
                       </div>
                     </div>
                     <div className="flex-1">
                       <p className="font-medium text-gray-900">
                         đŸ’Ŧ {data.data.user.name} in #{roomName}
                       </p>
                       <p className="text-sm text-gray-600 truncate max-w-48">
                         {data.data.content}
                       </p>
                     </div>
                     <button
                       onClick={() => {
                         hotToast.dismiss(t.id);
                         const room = chatRooms.find(r => r.id === data.data.chatRoomId);
                         if (room) {
                           handleJoinRoom(room);
                         }
                       }}
                       className="flex-shrink-0 bg-green-500 text-white px-3 py-1 rounded text-sm hover:bg-green-600"
                     >
                       View
                     </button>
                   </div>
                 ), {
                   duration: 5000,
                   position: 'top-right',
                   style: {
                     maxWidth: '400px',
                   },
                 });

                // Show browser notification
                if (Notification.permission === 'granted') {
                  new Notification(`New message in #${roomName}`, {
                    body: `${data.data.user.name}: ${data.data.content}`,
                    icon: '/icons/apple-touch-icon-180x180.png'
                  });
                }

                console.log(`[GroupChat] 🔔 Notification shown for message from ${data.data.user.name} in room ${roomName}`);
              }
            }
            
            // Update messages if it's for the current room
            if (data.data.chatRoomId === selectedRoom?.id) {
              console.log(`[GroupChat] ✅ Adding message to current room`);
              setMessages(prev => {
                // Check if this message already exists (avoid duplicates)
                const existing = prev.find(m => m.id === data.data.id);
                if (existing) {
                  console.log(`[GroupChat] 🔄 Message already exists, skipping duplicate`);
                  return prev;
                }
                
                // Check if this is an update to an optimistic message
                const optimistic = prev.find(m => m.isOptimistic && m.content === data.data.content && Math.abs(new Date(m.createdAt).getTime() - new Date(data.data.createdAt).getTime()) < 5000);
                if (optimistic) {
                  console.log(`[GroupChat] 🔄 Updating optimistic message`);
                  return prev.map(m => m.id === optimistic.id ? { ...data.data, isOptimistic: false } : m);
                }
                
                console.log(`[GroupChat] ➕ Adding new message to chat`);
                const newMessages = [...prev, data.data];
                console.log(`[GroupChat] 📊 Total messages now: ${newMessages.length}`);
                return newMessages;
              });
            } else {
              console.log(`[GroupChat] â­ī¸ Message for different room (${data.data.chatRoomId}), skipping`);
            }
            
            // Always update room's last message and count
            setChatRooms(prev => prev.map(room => 
              room.id === data.data.chatRoomId ? { 
                ...room, 
                lastMessage: data.data,
                _count: { 
                  ...room._count, 
                  messages: (room._count?.messages || 0) + 1 
                }
              } : room
            ));
            break;
            
          case 'ROOM_CREATED':
            const newRoom = data.room;
            setChatRooms(prev => [newRoom, ...prev]);
            try {
              if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: 'JOIN_ROOM', data: { chatRoomId: newRoom.id } }));
              }
            } catch (error) {
              console.error('Failed to send JOIN_ROOM for new room:', error);
            }
            toast({ title: "New Room Created", description: `You were added to "${newRoom.name}".` });
            break;
            
          case 'TYPING':
            if (data.data.chatRoomId === selectedRoom?.id) {
              setTypingUsers(prev => {
                const newSet = new Set(prev);
                if (data.data.isTyping) {
                  newSet.add(data.data.userName);
                } else {
                  newSet.delete(data.data.userName);
                }
                return newSet;
              });
            }
            break;
            
          case 'USER_KICKED':
            if (data.chatRoomId === selectedRoom?.id) {
              if (data.userId === session?.user?.id) {
                addSystemMessage(`You have been kicked from the room by ${data.kickedBy}.`, 'ERROR');
                if(selectedRoom?.id === data.chatRoomId) {
                  setSelectedRoom(null);
                }
              }
              setChatRooms(prev => prev.map(room => {
                if (room.id === data.chatRoomId) {
                  return {
                    ...room,
                    participants: room.participants.filter(p => p.user.id !== data.userId)
                  };
                }
                return room;
              }));
            }
            break;
            
          case 'PARTICIPANT_LIST_UPDATE':
            if (data.data.chatRoomId === selectedRoom?.id) {
              setParticipants(data.data.participants);
            }
            break;
        }
      } catch (error) {
        console.error('Failed to parse WebSocket message:', error);
      }
    };

    ws.addEventListener('message', handleMessage);
    return () => ws.removeEventListener('message', handleMessage);
  }, [ws, selectedRoom?.id, session?.user?.id, toast, addSystemMessage, chatRooms]);

  // Clear joined rooms when WebSocket connection changes
  useEffect(() => {
    if (ws && connected) {
      console.log('[GroupChat] WebSocket connected, clearing joined rooms ref to ensure re-joining');
      joinedRoomsRef.current.clear();
    }
  }, [ws, connected]);

  // Fetch initial data
  const fetchChatRooms = async () => {
    setIsLoadingRooms(true);
    try {
      console.log('[GroupChat] 🔄 Fetching chat rooms...');
      const response = await fetch('/api/chat/rooms', {
        method: 'GET',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json',
        },
      });
      
      if (!response.ok) {
        if (response.status === 401) {
          addSystemMessage('Authentication required. Please log in again.', 'ERROR');
          return;
        }
        const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
        throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch chat rooms`);
      }
      
      const data: ChatRoom[] = await response.json();
      console.log('[GroupChat] 📋 Fetched chat rooms:', data.map(r => ({ id: r.id, name: r.name, messageCount: r._count?.messages })));
      
      if (!data || data.length === 0) {
        console.log('[GroupChat] âš ī¸ No chat rooms found');
        addSystemMessage('No chat rooms available. Please contact an administrator.', 'ERROR');
        setChatRooms([]);
        return;
      }
      
      setChatRooms(data);

      // ✅ PERFORMANCE FIX: Don't auto-join all rooms on connection
      // Users will only join rooms when they actively select them
      console.log('[GroupChat] 📋 Chat rooms loaded. Users will join rooms on selection for better performance.');

    } catch (error) {
      console.error('[GroupChat] ❌ Error fetching chat rooms:', error);
      const errorMessage = error instanceof Error ? error.message : 'Failed to load chat rooms';
      addSystemMessage(`Error: ${errorMessage}`, 'ERROR');
      setChatRooms([]);
    } finally {
      setIsLoadingRooms(false);
    }
  };

  // Fetch messages for selected room with pagination
  const fetchMessages = useCallback(async (roomId: string, cursor?: string, append = false) => {
    try {
      console.log(`[GroupChat] 🔄 Fetching messages for room: ${roomId}${cursor ? ` (cursor: ${cursor})` : ''}`);
      setIsLoadingMessages(true);
      
      const url = new URL('/api/chat/group/messages', window.location.origin);
      url.searchParams.set('chatRoomId', roomId);
      url.searchParams.set('limit', '50');
      if (cursor) url.searchParams.set('cursor', cursor);
      
      const response = await fetch(url.toString(), {
        method: 'GET',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/json',
        },
      });
      
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
        throw new Error(errorData.error || `HTTP ${response.status}: Failed to fetch messages`);
      }
      
      const data = await response.json();
      console.log(`[GroupChat] 📋 Fetched ${data.messages?.length || 0} messages for room ${roomId} (hasMore: ${data.pagination?.hasMore})`);
      
      if (append) {
        // Append older messages to the beginning
        setMessages(prev => [...(data.messages || []), ...prev]);
      } else {
        // Replace messages (initial load)
        setMessages(data.messages || []);
      }
      
      // Store pagination info for potential "load more" functionality
      if (data.pagination) {
        console.log(`[GroupChat] 📊 Pagination info:`, data.pagination);
      }
      
    } catch (err) {
      console.error(`[GroupChat] ❌ Error fetching messages for room ${roomId}:`, err);
      addSystemMessage(`Failed to load chat history: ${err instanceof Error ? err.message : 'Unknown error'}`, 'ERROR');
      if (!append) setMessages([]); // Only clear messages on initial load error
    } finally {
      setIsLoadingMessages(false);
    }
  }, []);

  // Fetch initial data
  useEffect(() => {
    if (session) {
      fetchChatRooms();
    }
  }, [session]);

  // Fetch messages for selected room
  useEffect(() => {
    if (!selectedRoom) return;
    fetchMessages(selectedRoom.id);
  }, [selectedRoom, fetchMessages]);

  // Removed aggressive auto-refresh - using real-time WebSocket instead

  // DEBUG: Temporarily disabled automatic room joining to isolate connection issues
  // Will re-enable once basic WebSocket connection is stable

  // Handle typing with enhanced WebSocket
  const handleTyping = useCallback((typing: boolean) => {
    if (!selectedRoom || !sendTyping) return;
    setIsTyping(typing);
    sendTyping(selectedRoom.id, typing);
  }, [selectedRoom, sendTyping]);

  const handleSendMessage = async (e: React.FormEvent) => {
    e.preventDefault();
    if ((!newMessage.trim() && !selectedFile) || !selectedRoom || !session) return;
    
    console.log(`[GroupChat] 📤 Sending message to room ${selectedRoom.id}:`, newMessage.trim());
    
    // Command handling
    if (newMessage.startsWith('/') && !selectedFile) {
      await handleCommand(newMessage);
      setNewMessage('');
      return;
    }
    
    if (!selectedRoom) {
        addSystemMessage('You must be in a room to send a message.', 'ERROR');
        setNewMessage('');
        return;
    }

    try {
      let fileData: any = {};

      // Handle file upload first
      if (selectedFile) {
        setUploading(true);
        const formData = new FormData();
        formData.append('file', selectedFile);

        const uploadResponse = await fetch('/api/chat/upload', {
          method: 'POST',
          body: formData,
        });

        if (!uploadResponse.ok) {
          throw new Error('Failed to upload file');
        }

        fileData = await uploadResponse.json();
        setSelectedFile(null);
        setUploading(false);
      }

      const messageData = {
        content: newMessage.trim() || fileData.fileName || 'File attachment',
        chatRoomId: selectedRoom.id,
        type: selectedFile ? (selectedFile.type.startsWith('image/') ? 'IMAGE' : 'FILE') : 'TEXT',
        replyToId: replyingTo?.id,
        ...fileData
      };

      const optimisticId = `optimistic-${Date.now()}`;
      const optimisticMessage: Message = {
        id: optimisticId,
        content: messageData.content,
        createdAt: new Date().toISOString(),
        user: session.user as User,
        chatRoomId: selectedRoom.id,
        isOptimistic: true,
        fileUrl: fileData.fileUrl,
        fileName: fileData.fileName,
        fileSize: fileData.fileSize,
        mimeType: fileData.mimeType,
        replyToId: replyingTo?.id,
        replyTo: replyingTo ? {
          id: replyingTo.id,
          content: replyingTo.content,
          user: replyingTo.user
        } : undefined
      };

      console.log(`[GroupChat] đŸŽ¯ Adding optimistic message:`, optimisticMessage);
      setMessages(prev => [...prev, optimisticMessage]);
      setNewMessage('');
      setReplyingTo(null);
      setShowEmojiPicker(false);
      handleTyping(false);

      console.log(`[GroupChat] 🚀 Sending API request...`);
      const response = await fetch('/api/chat/group/messages', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(messageData),
      });

      if (!response.ok) throw new Error('Message failed to send.');

      const savedMessage = await response.json();
      console.log('[GroupChat] ✅ Message saved to database:', savedMessage);
      
      setMessages(prev => prev.map(m => m.id === optimisticId ? { ...savedMessage, isOptimistic: false } : m));
      
      // WebSocket will handle broadcasting to other users
    } catch (err) {
      console.error(`[GroupChat] ❌ Error sending message:`, err);
      handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
      setUploading(false);
      // Remove optimistic message on error
      setMessages(prev => prev.filter(m => m.id && !m.id.startsWith('optimistic-')));
    }
  };

  const handleCreateRoom = async (e?: React.FormEvent, roomName?: string) => {
    if (e) e.preventDefault();
    const name = roomName || newRoomName;

    if (!name.trim() || !session?.user) return;

    setIsCreatingRoom(true);
    try {
      const response = await fetch('/api/chat/rooms', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: name.trim() })
      });

      if (!response.ok) throw new Error('Failed to create room');

      const newRoom = await response.json();
      
      // Add a system message locally
      if (!e) { // Only show system message if called from a command
        addSystemMessage(`Room "${newRoom.name}" has been created.`);
      }

      // The ROOM_CREATED websocket event will add the room to the list
      setSelectedRoom(newRoom);
      setNewRoomName('');
      setShowCreateRoom(false);
      toast({ title: "Room Created", description: `"${newRoom.name}" has been created successfully.` });
    } catch (err) {
      handleError(err instanceof Error ? err.message : 'Failed to create room');
    } finally {
      setIsCreatingRoom(false);
    }
  };

  const handleDeleteRoom = async (roomId: string, roomName: string) => {
    if (!window.confirm(`Are you sure you want to delete the room "${roomName}"? This action cannot be undone.`)) {
      return;
    }

    try {
      const response = await fetch(`/api/chat/rooms?roomId=${roomId}`, {
        method: 'DELETE',
        credentials: 'same-origin',
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'Failed to delete room');
      }

      toast({ title: "Room Deleted", description: `"${roomName}" has been permanently deleted.` });

      setChatRooms(prev => prev.filter(room => room.id !== roomId));
      if (selectedRoom?.id === roomId) {
        setSelectedRoom(null);
      }

    } catch (err) {
      handleError(err instanceof Error ? err.message : 'Failed to delete room');
    }
  };

  const handleKickParticipant = async (userId: string) => {
    if (!selectedRoom) return;
    try {
      const response = await fetch(`/api/admin/chat/participants?roomId=${selectedRoom.id}&userId=${userId}`, {
        method: 'DELETE',
        credentials: 'same-origin',
      });
      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'Failed to kick user');
      }
      // Optimistically remove participant from UI
      setChatRooms(prev => prev.map(room => {
        if (room.id === selectedRoom.id) {
          return { ...room, participants: room.participants.filter(p => p.user.id !== userId) };
        }
        return room;
      }));
      toast({ title: "User Kicked", description: "The user has been removed from the room." });
    } catch (err) {
      handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
    }
  };

  const handleDoubleClickUser = (userId: string, userName: string) => {
    if (userId === session?.user?.id) {
      addSystemMessage('You cannot start a private chat with yourself.', 'ERROR');
      return;
    }
    setDirectMessage({ recipientId: userId, recipientName: userName });
  };

  // Handle reaction actions
  const handleAddReaction = useCallback(async (messageId: string, emoji: string) => {
    try {
      console.log('Adding reaction:', { messageId, emoji });
      
      const response = await fetch('/api/chat/group/reactions', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ messageId, emoji }),
      });

      if (response.ok) {
        const reaction = await response.json();
        console.log('Reaction added successfully:', reaction);
        
        // Optimistically update the UI
        setMessages(prev => prev.map(msg => 
          msg.id === messageId 
            ? { ...msg, reactions: [...(msg.reactions || []), reaction] }
            : msg
        ));
        saveRecentEmoji(emoji);
      } else {
        const errorData = await response.text();
        console.error('Failed to add reaction:', response.status, errorData);
        toast({
          title: "Failed to add reaction",
          description: "Please try again",
          variant: "destructive",
        });
      }
    } catch (error) {
      console.error('Failed to add reaction:', error);
      toast({
        title: "Failed to add reaction",
        description: "Please try again",
        variant: "destructive",
      });
    }
  }, [toast]);

  const handleRemoveReaction = useCallback(async (messageId: string, emoji: string) => {
    try {
      console.log('Removing reaction:', { messageId, emoji });
      
      const response = await fetch(`/api/chat/group/reactions?messageId=${messageId}&emoji=${emoji}`, {
        method: 'DELETE',
        credentials: 'same-origin',
      });

      if (response.ok) {
        console.log('Reaction removed successfully');
        
        // Optimistically update the UI
        setMessages(prev => prev.map(msg => 
          msg.id === messageId 
            ? { 
                ...msg, 
                reactions: (msg.reactions || []).filter(r => 
                  !(r.emoji === emoji && r.userId === session?.user?.id)
                )
              }
            : msg
        ));
      } else {
        const errorData = await response.text();
        console.error('Failed to remove reaction:', response.status, errorData);
        toast({
          title: "Failed to remove reaction",
          description: "Please try again",
          variant: "destructive",
        });
      }
    } catch (error) {
      console.error('Failed to remove reaction:', error);
      toast({
        title: "Failed to remove reaction",
        description: "Please try again",
        variant: "destructive",
      });
    }
  }, [toast]);

  // Handle emoji selection
  const handleEmojiSelect = (emoji: string) => {
    setNewMessage(prev => prev + emoji);
    // Don't auto-close picker for better UX
    saveRecentEmoji(emoji);
  };

  // Handle quick reactions
  const handleQuickReaction = (message: Message, emoji: string) => {
    const hasReacted = message.reactions?.some(r => 
      r.emoji === emoji && r.userId === session?.user?.id
    );

    if (hasReacted) {
      handleRemoveReaction(message.id, emoji);
    } else {
      handleAddReaction(message.id, emoji);
    }
  };

  // Format file size
  const formatFileSize = (bytes: number): string => {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
  };

  // Memoized message component for better performance
  const MessageComponent = React.memo(({ message }: { message: Message }) => {
    const time = format(new Date(message.createdAt), 'HH:mm');

    if (message.type === 'SYSTEM' || message.type === 'ERROR') {
      return (
        <div key={message.id} className={`text-sm py-1 px-4 ${message.type === 'ERROR' ? 'text-red-500' : 'text-gray-500 dark:text-gray-400'}`}>
          <span className="text-gray-400 dark:text-gray-500 mr-2">[{time}]</span>
          <span className="italic">-- {message.content}</span>
        </div>
      );
    }
    
    if (message.isAction) {
      return (
        <div key={message.id} className="text-sm py-1 px-4 text-purple-600 dark:text-purple-400 italic">
            <span className="text-gray-400 dark:text-gray-500 mr-2">[{time}]</span>
            <span>{message.content}</span>
        </div>
      );
    }
    
    const userName = message.user?.name || 'Unknown User';
    const isOwn = message.user?.id === session?.user?.id;
    const isImage = message.fileUrl && (
      message.mimeType?.startsWith('image/') ||
      /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i.test(message.fileName || '')
    );

    return (
      <div key={message.id} className="group text-sm py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-800/50">
        {/* Reply context */}
        {message.replyTo && (
          <div className="mb-1 ml-16 p-2 bg-gray-100 dark:bg-gray-700 rounded border-l-4 border-blue-500 text-xs">
            <div className="flex items-center gap-2">
              {message.replyTo.fileUrl && message.replyTo.mimeType?.startsWith('image/') && (
                <img 
                  src={message.replyTo.fileUrl} 
                  alt="Reply thumbnail" 
                  className="w-8 h-8 object-cover rounded border flex-shrink-0"
                />
              )}
              <span className="text-gray-600 dark:text-gray-400 flex-1">
                Replying to {message.replyTo.user.name}:{' '}
                {message.replyTo.fileUrl ? (
                  message.replyTo.mimeType?.startsWith('image/') ? (
                    <span className="italic">📷 Image</span>
                  ) : (
                    <span className="italic">📎 File attachment</span>
                  )
                ) : (
                  <>
                    {message.replyTo.content.substring(0, 100)}
                    {message.replyTo.content.length > 100 ? '...' : ''}
                  </>
                )}
              </span>
            </div>
          </div>
        )}

        <div className="flex items-start gap-3">
          <div className="relative">
            <UserAvatar 
              user={message.user}
              size="sm"
              showTooltip={true}
              clickable={true}
              onClick={() => setProfilePopover({ 
                userId: message.user.id, 
                isOpen: profilePopover?.userId !== message.user.id || !profilePopover?.isOpen 
              })}
            />
            {profilePopover?.userId === message.user.id && profilePopover?.isOpen && (
              <ProfilePopover
                userId={message.user.id}
                isOpen={true}
                onClose={() => setProfilePopover(null)}
                position="right"
                onStartDirectMessage={(userId, userName) => {
                  handleDoubleClickUser(userId, userName);
                }}
              />
            )}
          </div>
          <div className="flex-1">
            <div className="flex items-baseline gap-2">
              <span className="text-gray-400 dark:text-gray-500 text-xs">[{time}]</span>
              <span className={`font-semibold text-sm ${
                isOwn ? 'text-blue-600 dark:text-blue-400' : 
                message.user.role === 'ADMIN' ? 'text-purple-600 dark:text-purple-400' :
                'text-green-600 dark:text-green-400'
              }`}>
                {message.user.role === 'ADMIN' ? 'âš–ī¸ ' : ''}{userName}
              </span>
              {message.user.title && (
                <span className="text-xs text-gray-500 dark:text-gray-400">
                  â€ĸ {message.user.title}
                </span>
              )}
              {message.isEdited && (
                <span className="text-xs text-gray-400 dark:text-gray-500">(edited)</span>
              )}
            </div>

            {/* File attachment */}
            {message.fileUrl && (
              <div className="mt-1 mb-2">
                {isImage ? (
                  <div className="relative group">
                    <img
                      src={message.fileUrl}
                      alt={message.fileName || 'Image'}
                      className="max-w-xs max-h-64 rounded-lg border border-gray-200 dark:border-gray-600 cursor-pointer hover:opacity-90 transition-all duration-200 shadow-sm hover:shadow-md"
                      onClick={() => {
                        // Create and show image modal
                        const modal = document.createElement('div');
                        modal.className = 'fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4';
                        modal.style.zIndex = '9999';
                        modal.onclick = (e) => {
                          if (e.target === modal) modal.remove();
                        };
                        
                        // Create modal content
                        const modalContent = document.createElement('div');
                        modalContent.className = 'relative max-w-4xl max-h-full';
                        
                        // Close button
                        const closeBtn = document.createElement('button');
                        closeBtn.className = 'absolute -top-10 right-0 text-white hover:text-gray-300 text-xl font-bold z-10';
                        closeBtn.innerHTML = '✕';
                        closeBtn.onclick = () => modal.remove();
                        
                        // Image element
                        const img = document.createElement('img');
                        img.src = message.fileUrl || '';
                        img.alt = message.fileName || 'Image';
                        img.className = 'max-w-full max-h-full object-contain rounded-lg';
                        
                        // Bottom overlay with download
                        const bottomOverlay = document.createElement('div');
                        bottomOverlay.className = 'absolute bottom-0 left-0 right-0 bg-black bg-opacity-80 text-white p-3 rounded-b-lg flex items-center justify-between';
                        
                        // File info section
                        const fileInfo = document.createElement('div');
                        fileInfo.className = 'flex-1';
                        const fileTitle = document.createElement('div');
                        fileTitle.className = 'font-medium';
                        fileTitle.textContent = '📷 Image';
                        fileInfo.appendChild(fileTitle);
                        
                        if (message.fileSize) {
                          const fileSize = document.createElement('div');
                          fileSize.className = 'text-sm text-gray-300';
                          fileSize.textContent = formatFileSize(message.fileSize);
                          fileInfo.appendChild(fileSize);
                        }
                        
                        // Download button
                        const downloadBtn = document.createElement('a');
                        downloadBtn.href = message.fileUrl || '';
                        downloadBtn.download = message.fileName || 'image';
                        downloadBtn.className = 'bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded flex items-center gap-2 transition-colors';
                        downloadBtn.innerHTML = `
                          <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
                          </svg>
                          Download
                        `;
                        
                        // Assemble modal
                        bottomOverlay.appendChild(fileInfo);
                        bottomOverlay.appendChild(downloadBtn);
                        modalContent.appendChild(closeBtn);
                        modalContent.appendChild(img);
                        modalContent.appendChild(bottomOverlay);
                        modal.appendChild(modalContent);
                        
                        document.body.appendChild(modal);
                        
                        // Handle escape key
                        const handleEscape = (e: KeyboardEvent) => {
                          if (e.key === 'Escape') {
                            modal.remove();
                            document.removeEventListener('keydown', handleEscape);
                          }
                        };
                        document.addEventListener('keydown', handleEscape);
                      }}
                      onError={(e) => {
                        const target = e.target as HTMLImageElement;
                        target.style.display = 'none';
                        const errorDiv = target.nextElementSibling as HTMLElement;
                        if (errorDiv) errorDiv.style.display = 'block';
                      }}
                      loading="lazy"
                    />
                    {/* Error fallback */}
                    <div 
                      className="hidden max-w-xs p-4 bg-gray-100 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 text-center"
                    >
                      <svg className="w-8 h-8 text-gray-400 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
                      </svg>
                      <p className="text-sm text-gray-500 dark:text-gray-400">Image failed to load</p>
                      <a
                        href={message.fileUrl || '#'}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="inline-block mt-2 text-blue-500 hover:text-blue-600 text-xs"
                      >
                        Open in new tab
                      </a>
                    </div>
                    {/* Click hint overlay */}
                    <div className="absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors rounded-lg flex items-center justify-center">
                      <div className="opacity-0 group-hover:opacity-100 transition-opacity bg-black/70 text-white text-sm px-3 py-1 rounded-full pointer-events-none">
                        🔍 Click to view
                      </div>
                    </div>
                  </div>
                ) : (
                  <a
                    href={message.fileUrl || '#'}
                    download={message.fileName}
                    className="inline-flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors group cursor-pointer"
                  >
                    <div className="flex-shrink-0">
                      <svg className="w-8 h-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                      </svg>
                    </div>
                    <div className="flex-1 min-w-0">
                      <div className="font-medium text-gray-900 dark:text-gray-100">
                        {message.mimeType?.includes('pdf') ? '📄 PDF Document' :
                         message.mimeType?.includes('doc') ? '📝 Document' :
                         message.mimeType?.includes('spreadsheet') || message.mimeType?.includes('excel') ? '📊 Spreadsheet' :
                         message.mimeType?.includes('zip') || message.mimeType?.includes('rar') ? 'đŸ—ƒī¸ Archive' :
                         message.mimeType?.includes('video') ? 'đŸŽĨ Video' :
                         message.mimeType?.includes('audio') ? 'đŸŽĩ Audio' :
                         '📎 File Attachment'}
                      </div>
                      {message.fileSize && (
                        <div className="text-sm text-gray-500 dark:text-gray-400">{formatFileSize(message.fileSize)}</div>
                      )}
                    </div>
                    <div className="flex-shrink-0">
                      <svg className="w-5 h-5 text-gray-400 group-hover:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
                      </svg>
                    </div>
                  </a>
                )}
              </div>
            )}

            {/* Message content */}
            {message.content && (
              <div className="text-gray-800 dark:text-gray-200 break-words whitespace-pre-wrap">
                {message.content}
              </div>
            )}

            {/* Quick reaction buttons (visible on hover) */}
            <div className="opacity-0 group-hover:opacity-100 transition-opacity mt-1 flex items-center gap-1">
              {['👍', 'â¤ī¸', '😂', '😮'].map((emoji) => (
                <button
                  key={emoji}
                  onClick={() => handleQuickReaction(message, emoji)}
                  className="text-sm hover:scale-110 transition-transform opacity-60 hover:opacity-100"
                  title={`React with ${emoji}`}
                >
                  {emoji}
                </button>
              ))}
              <button
                onClick={() => {
                  console.log('Reply button clicked for message:', message.id);
                  setReplyingTo(message);
                }}
                className="text-xs text-blue-500 hover:text-blue-700 dark:hover:text-blue-300 ml-2 font-medium"
                title="Reply to this message"
              >
                â†Šī¸ Reply
              </button>
            </div>

            {/* Reactions display */}
            <MessageReactions
              messageId={message.id}
              reactions={message.reactions || []}
              onAddReaction={handleAddReaction}
              onRemoveReaction={handleRemoveReaction}
              className="mt-1"
            />
          </div>
        </div>
      </div>
    );
  });
  
  const uniqueParticipants = participants;

  const handleJoinRoom = async (room: ChatRoom) => {
    if (selectedRoom?.id === room.id) {
      console.log(`[GroupChat] 📍 Already in room: ${room.name}`);
      return; // Already in this room
    }

    console.log(`[GroupChat] đŸšĒ Switching to room: ${room.name} (${room.id})`);
    
    // Leave the current room first (only if we're actually joined)
    if (selectedRoom?.id && leaveRoom && joinedRoomsRef.current.has(selectedRoom.id)) {
      try {
        console.log(`[GroupChat] 👋 Leaving room: ${selectedRoom.name}`);
        await leaveRoom(selectedRoom.id);
        joinedRoomsRef.current.delete(selectedRoom.id);
        console.log(`[GroupChat] ✅ Left room: ${selectedRoom.name}`);
      } catch (error) {
        console.error(`[GroupChat] ❌ Failed to leave room ${selectedRoom.name}:`, error);
      }
    }

    // Update UI immediately for better responsiveness
    setSelectedRoom(room);
    setMessages([]);
    setWelcomeMessage(room.description || `Welcome to ${room.name}`);

    // Join the new room via WebSocket
    if (joinRoom && !joinedRoomsRef.current.has(room.id)) {
      try {
        console.log(`[GroupChat] 🔌 Joining room via WebSocket: ${room.name}`);
        await joinRoom(room.id);
        joinedRoomsRef.current.add(room.id);
        console.log(`[GroupChat] ✅ Successfully joined room: ${room.name}`);
      } catch (error) {
        console.error(`[GroupChat] ❌ Failed to join room ${room.name}:`, error);
        addSystemMessage(`Failed to join room: ${room.name}`, 'ERROR');
      }
    } else if (joinedRoomsRef.current.has(room.id)) {
      console.log(`[GroupChat] 📍 Already joined room ${room.name}, just switching view`);
    }
  };

  // Fetch initial participants when a room is selected, and on component mount
  const fetchParticipants = async (roomId: string) => {
    try {
      const response = await fetch(`/api/chat/rooms/${roomId}/participants`, {
        method: 'GET',
        credentials: 'same-origin',
      });
      if (!response.ok) throw new Error('Failed to fetch participants.');
      const data = await response.json();
      setParticipants(data);
    } catch (err) {
      handleError(err instanceof Error ? err.message : 'An unknown error occurred.');
    }
  };

  useEffect(() => {
    if (selectedRoom?.id) {
      fetchParticipants(selectedRoom.id);
    }
  }, [selectedRoom?.id]);

  // Only join a room when explicitly selected by the user
  // Removed automatic bulk room joining that was overwhelming the WebSocket connection

  // ✅ PERFORMANCE: Memoize expensive operations
  const filteredMessages = useMemo(() => {
    return messages.filter(msg => !msg.isOptimistic || !messages.find(m => !m.isOptimistic && m.content === msg.content && Math.abs(new Date(m.createdAt).getTime() - new Date(msg.createdAt).getTime()) < 5000));
  }, [messages]);

  const sortedChatRooms = useMemo(() => {
    return [...chatRooms].sort((a, b) => {
      const aTime = a.lastMessage?.createdAt ? new Date(a.lastMessage.createdAt).getTime() : 0;
      const bTime = b.lastMessage?.createdAt ? new Date(b.lastMessage.createdAt).getTime() : 0;
      return bTime - aTime;
    });
  }, [chatRooms]);

  const currentRoomParticipants = useMemo(() => {
    return selectedRoom?.participants || [];
  }, [selectedRoom?.participants]);

  // Request notification permission
  useEffect(() => {
    if ('Notification' in window && Notification.permission === 'default') {
      Notification.requestPermission().then(permission => {
        console.log('[GroupChat] Notification permission:', permission);
      });
    }
  }, []);

  const isLawyer = session?.user?.role === 'LAWYER';
  const isVerified = session?.user?.isVerifiedLawyer || session?.user?.verificationStatus === 'VERIFIED_BARREAU';

  // UI Rendering...
  return (
    <div className="relative flex h-[26rem] md:h-[32rem] w-full font-mono antialiased bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 text-sm border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg overflow-hidden">
      
      {/* Mobile Header */}
      {isMobile && (
        <div className="absolute top-0 left-0 right-0 z-30 flex items-center justify-between p-3 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 h-14">
          <button
            onClick={() => setShowLeftSidebar(!showLeftSidebar)}
            className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
          >
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
            </svg>
          </button>
          
          <div className="flex-1 text-center">
            <h2 className="font-bold text-base truncate">
              {selectedRoom?.name || 'Group Chat'}
            </h2>
          </div>
          
          <button
            onClick={() => setShowRightSidebar(!showRightSidebar)}
            className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
          >
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
            </svg>
          </button>
        </div>
      )}

      {/* Mobile Overlay */}
      {isMobile && (showLeftSidebar || showRightSidebar) && (
        <div 
          className="absolute inset-0 bg-black bg-opacity-50 z-20"
          onClick={() => {
            setShowLeftSidebar(false);
            setShowRightSidebar(false);
          }}
        />
      )}

      {/* Left Sidebar: Room List */}
      <aside className={`
        flex flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900
        ${isMobile 
          ? `absolute top-0 left-0 h-full w-80 max-w-[85vw] z-30 transform transition-transform duration-300 ${
              showLeftSidebar ? 'translate-x-0' : '-translate-x-full'
            }`
          : 'w-64 relative'
        }
      `}>
        <header className="flex items-center justify-between p-3 border-b border-gray-200 dark:border-gray-800 h-14 flex-shrink-0">
          <div className="flex items-center gap-2">
            <h1 className="text-base font-bold">Channels</h1>
            {getTotalUnreadDirectMessages() > 0 && (
              <div className="relative">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-blue-500" viewBox="0 0 20 20" fill="currentColor">
                  <path fillRule="evenodd" d="M18 10c0 3.866-3.582 7-8 7a8.841 8.841 0 01-4.083-.98L2 17l1.338-3.123C2.493 12.767 2 11.434 2 10c0-3.866 3.582-7 8-7s8 3.134 8 7zM7 9H5v2h2V9zm8 0h-2v2h2V9zM9 9h2v2H9V9z" clipRule="evenodd" />
                </svg>
                <span className="absolute -top-1 -right-1 block h-4 w-4 rounded-full bg-red-500 text-white text-xs font-bold flex items-center justify-center border border-white dark:border-gray-900">
                  {getTotalUnreadDirectMessages() > 9 ? '9+' : getTotalUnreadDirectMessages()}
                </span>
              </div>
            )}
          </div>
          <div className="flex items-center gap-3">
            {session?.user?.role === 'ADMIN' && (
              <button
                onClick={() => setShowCreateRoom(true)}
                className="p-1.5 rounded text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
                title="Create New Room"
              >
                <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
                </svg>
              </button>
            )}
            <div title={connected ? 'Connected' : 'Disconnected'} className="relative w-2.5 h-2.5">
              <div className={`w-full h-full rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'}`}></div>
              {connected && <div className={`absolute top-0 left-0 w-full h-full rounded-full ${connected ? 'bg-green-500' : 'bg-red-500'} animate-ping`}></div>}
            </div>
          </div>
        </header>

        {/* Create Room Form */}
        {showCreateRoom && (
          <div className="p-3 border-b border-gray-200 dark:border-gray-800 bg-gray-50 dark:bg-gray-800/50">
            <form onSubmit={handleCreateRoom} className="space-y-2">
              <input
                type="text"
                value={newRoomName}
                onChange={(e) => setNewRoomName(e.target.value)}
                placeholder="New channel name..."
                className="w-full px-2 py-1 border border-gray-300 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-sm focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
                disabled={isCreatingRoom}
              />
              <div className="flex gap-2">
                <button
                  type="submit"
                  disabled={!newRoomName.trim() || isCreatingRoom}
                  className="w-full px-2 py-1 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 disabled:opacity-50"
                >
                  {isCreatingRoom ? 'Creating...' : 'Create'}
                </button>
                <button
                  type="button"
                  onClick={() => { setShowCreateRoom(false); setNewRoomName(''); }}
                  className="w-full px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 text-xs rounded hover:bg-gray-300 dark:hover:bg-gray-600"
                >
                  Cancel
                </button>
              </div>
            </form>
          </div>
        )}

        <div className="flex-1 overflow-y-auto">
          {/* Direct Messages Section */}
          {directMessageNotifications.size > 0 && (
            <div className="p-2 border-b border-gray-200 dark:border-gray-800">
              <h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1">
                Direct Messages ({getTotalUnreadDirectMessages()})
              </h2>
              <div className="space-y-1">
                {Array.from(directMessageNotifications.entries()).map(([senderId, notification]) => (
                  <button
                    key={senderId}
                    onClick={() => handleDoubleClickUser(senderId, notification.senderName)}
                    className="w-full text-left flex items-center gap-2 p-1.5 rounded transition-colors duration-100 hover:bg-gray-100 dark:hover:bg-gray-800/50"
                  >
                    <div className="relative">
                      <div className="w-6 h-6 rounded-full bg-blue-500 text-white flex items-center justify-center text-xs font-bold">
                        {notification.senderName.charAt(0).toUpperCase()}
                      </div>
                      {notification.unreadCount > 0 && (
                        <span className="absolute -top-1 -right-1 block h-3 w-3 rounded-full bg-red-500 text-white text-xs font-bold flex items-center justify-center border border-white dark:border-gray-900">
                          {notification.unreadCount > 9 ? '9+' : notification.unreadCount}
                        </span>
                      )}
                    </div>
                    <div className="flex-1 min-w-0">
                      <p className={`text-sm font-semibold truncate ${
                        notification.unreadCount > 0 
                          ? 'text-blue-600 dark:text-blue-400' 
                          : 'text-gray-900 dark:text-gray-100'
                      }`}>
                        {notification.senderName}
                      </p>
                      <p className="text-xs text-gray-500 dark:text-gray-400 truncate">
                        {notification.lastMessage}
                      </p>
                    </div>
                    {notification.unreadCount > 0 && (
                      <span className="text-xs px-1.5 py-0.5 bg-red-500 text-white rounded-full font-bold">
                        {notification.unreadCount}
                      </span>
                    )}
                  </button>
                ))}
              </div>
            </div>
          )}

          {isLoadingRooms ? (
             <div className="p-2 space-y-2">
               <SkeletonLoader count={8} className="h-8 w-full rounded" />
             </div>
          ) : (
            <div className="p-2 space-y-1">
              {/* Channels Section Header */}
              <h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-1 px-1">
                Channels {chatRooms.length > 0 && `(${chatRooms.length})`}
              </h2>
              
              {chatRooms.length === 0 && !isLoadingRooms && (
                <div className="text-center text-gray-500 py-4">
                  <p className="text-sm">No channels available</p>
                  {!connected && (
                    <button 
                      onClick={reconnect}
                      className="mt-2 px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700"
                    >
                      Reconnect
                    </button>
                  )}
                </div>
              )}
              
            {chatRooms.map(room => {
              const isSelected = selectedRoom?.id === room.id;
              const isJoined = joinedRoomsRef.current.has(room.id);
              
              return (
                <div key={room.id} className="flex items-center group">
                  <button
                    onClick={() => handleJoinRoom(room)}
                    className={`w-full text-left flex items-center gap-2 p-1.5 rounded transition-colors duration-100 ${
                      isSelected
                        ? 'bg-blue-600 text-white font-semibold'
                        : 'hover:bg-gray-100 dark:hover:bg-gray-800/50'
                    }`}
                >
                  <span className={`font-mono font-bold ${isSelected ? 'text-blue-200' : 'text-gray-400'}`}>#</span>
                  <span className="flex-1 truncate">{room.name}</span>
                  
                  <div className="flex items-center gap-1">
                    {/* Connection indicator */}
                    {isJoined && (
                      <div 
                        className={`w-2 h-2 rounded-full ${
                          isSelected ? 'bg-green-300' : 'bg-green-500'
                        }`}
                        title={`Connected to ${room.name}`}
                      />
                    )}
                    
                    {/* Message count */}
                    {room._count && room._count.messages > 0 && (
                        <span className={`text-xs px-1.5 py-0.5 rounded-full ${
                          isSelected 
                            ? 'bg-white text-blue-600' 
                            : 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-300'
                        }`}>
                            {room._count.messages}
                        </span>
                    )}
                  </div>
                </button>
                {session?.user?.role === 'ADMIN' && (
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      handleDeleteRoom(room.id, room.name);
                    }}
                    className="p-1 ml-1 rounded text-gray-400 hover:text-red-600 dark:hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity"
                    title={`Delete ${room.name}`}
                  >
                    <svg xmlns="http://www.w3.org/2000/svg" className="h-3.5 w-3.5" viewBox="0 0 20 20" fill="currentColor">
                      <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
                    </svg>
                  </button>
                )}
              </div>
            );
          })}
            </div>
          )}
        </div>
      </aside>

      {/* Center Panel: Chat Area */}
      <main className={`flex-1 flex flex-col bg-white dark:bg-gray-900 ${!isMobile ? 'border-r border-gray-200 dark:border-gray-800' : ''} ${isMobile ? 'pt-14' : ''}`}>
        {!selectedRoom ? (
          <div className="flex-1 flex items-center justify-center text-gray-500 p-4">
            <div className="text-center max-w-sm">
              {chatRooms.length === 0 ? (
                <>
                  <p className="text-base">No chat rooms available.</p>
                  <p className="text-sm mt-2 text-gray-400">Please contact an administrator to create chat rooms.</p>
                  {!connected && (
                    <div className="mt-4 p-4 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg">
                      <p className="text-orange-600 dark:text-orange-400 text-sm">âš ī¸ Connection issues detected</p>
                      <button 
                        onClick={reconnect}
                        className="mt-2 px-4 py-2 bg-orange-600 text-white rounded text-sm hover:bg-orange-700 transition-colors"
                      >
                        Reconnect
                      </button>
                    </div>
                  )}
                </>
              ) : (
                <>
                  <p className="text-base">No channel selected.</p>
                  <p className="text-sm mt-2 text-gray-400">
                    {isMobile ? 'Tap the menu to select a channel' : 'Click on a channel to start chatting'}
                  </p>
                </>
              )}
            </div>
          </div>
        ) : (
          <>
            {/* Desktop Header */}
            {!isMobile && (
              <header className="flex items-center justify-between p-3 border-b border-gray-200 dark:border-gray-800 h-14 flex-shrink-0">
                <div className="flex items-center space-x-2">
                  <h2 className="text-base font-bold text-gray-900 dark:text-gray-100">#{selectedRoom.name}</h2>
                  <button
                    onClick={() => fetchMessages(selectedRoom.id)}
                    className="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded text-sm"
                    title="Refresh messages"
                  >
                    🔄
                  </button>
                  <ConnectionStatus />
                </div>
                <div className="flex items-center gap-2">
                  <button 
                    onClick={() => setShowParticipants(!showParticipants)} 
                    className="p-2 rounded-full text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800" 
                    title="Toggle Members List"
                  >
                    <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
                      <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
                    </svg>
                  </button>
                </div>
              </header>
            )}
            
            <div 
              className={`overflow-y-auto p-2 ${isMobile ? 'h-[calc(100%-8rem)]' : 'h-[19.5rem]'} touch-pan-y`}
              onScroll={handleScroll} 
              ref={messagesContainerRef}
            >
              {/* Welcome Message */}
              {welcomeMessage && (
                <div className="text-center p-3 my-2 bg-gray-100 dark:bg-gray-800 rounded-lg">
                  <p className="text-sm text-gray-600 dark:text-gray-300">{welcomeMessage}</p>
                </div>
              )}

              {isLoadingMessages ? (
                <div className="flex flex-col items-center justify-center h-full">
                  <div className="w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
                  <p className="text-gray-500 dark:text-gray-400 mt-3 text-xs">Loading Messages...</p>
                </div>
              ) : (
                <div>
                  {messages.length === 0 ? (
                    <div className="text-center text-gray-500 dark:text-gray-400 py-8">
                      <p>No messages yet. Be the first to say something!</p>
                    </div>
                  ) : (
                    messages.map(message => (
                      <MessageComponent key={message.id} message={message} />
                    ))
                  )}
                  <div ref={messagesEndRef} />
                </div>
              )}
            </div>

            {/* Typing Indicator */}
            <TypingIndicator roomId={selectedRoom.id} />

            <footer className="p-2 border-t border-gray-200 dark:border-gray-800">
              {/* Reply preview */}
              <AnimatePresence>
                {replyingTo && (
                  <motion.div
                    initial={{ opacity: 0, height: 0 }}
                    animate={{ opacity: 1, height: 'auto' }}
                    exit={{ opacity: 0, height: 0 }}
                    className="mb-2 p-2 bg-gray-100 dark:bg-gray-700 rounded border-l-4 border-blue-500"
                  >
                    <div className="flex items-center justify-between">
                      <div className="flex items-center gap-2 flex-1 min-w-0">
                        {replyingTo.fileUrl && replyingTo.mimeType?.startsWith('image/') && (
                          <img 
                            src={replyingTo.fileUrl} 
                            alt="Reply thumbnail" 
                            className="w-8 h-8 object-cover rounded border flex-shrink-0"
                          />
                        )}
                        <div className="flex-1 min-w-0">
                          <p className="text-xs text-gray-600 dark:text-gray-400">
                            Replying to {replyingTo.user.name}
                          </p>
                          <p className="text-sm text-gray-800 dark:text-gray-200 truncate">
                            {replyingTo.fileUrl ? (
                              replyingTo.mimeType?.startsWith('image/') ? (
                                <span className="italic">📷 Image</span>
                              ) : (
                                <span className="italic">📎 File attachment</span>
                              )
                            ) : (
                              replyingTo.content
                            )}
                          </p>
                        </div>
                      </div>
                      <div className="flex items-center gap-2">
                        {!isMobile && (
                          <div className="relative">
                            <button
                              type="button"
                              onClick={() => setShowEmojiPicker(!showEmojiPicker)}
                              className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xs"
                              title="Add emoji"
                            >
                              😀
                            </button>
                            <EmojiPicker
                              isOpen={showEmojiPicker}
                              onClose={() => setShowEmojiPicker(false)}
                              onEmojiSelect={handleEmojiSelect}
                              position="bottom"
                            />
                          </div>
                        )}
                        <button
                          onClick={() => setReplyingTo(null)}
                          className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-xs p-1"
                        >
                          ×
                        </button>
                      </div>
                    </div>
                  </motion.div>
                )}
              </AnimatePresence>

              <form onSubmit={handleSendMessage} className="relative">
                <div className="flex items-end gap-2">
                  {/* File upload */}
                  <FileUpload
                    onFileSelect={setSelectedFile}
                    onRemoveFile={() => setSelectedFile(null)}
                    selectedFile={selectedFile}
                    uploading={uploading}
                    className="flex-shrink-0"
                  />

                  {/* Message input container */}
                  <div className="flex-1 relative">
                    <input
                      type="text"
                      value={newMessage}
                      onChange={(e) => {
                        setNewMessage(e.target.value);
                        // Handle typing indicators
                        if (selectedRoom && sendTyping) {
                          if (e.target.value.length > 0 && !isTyping) {
                            setIsTyping(true);
                            sendTyping(selectedRoom.id, true);
                          } else if (e.target.value.length === 0 && isTyping) {
                            setIsTyping(false);
                            sendTyping(selectedRoom.id, false);
                          }
                        }
                      }}
                      onKeyPress={(e) => {
                        if (e.key === 'Enter' && !e.shiftKey) {
                          handleSendMessage(e);
                        }
                      }}
                      onBlur={() => {
                        if (isTyping && selectedRoom && sendTyping) {
                          setIsTyping(false);
                          sendTyping(selectedRoom.id, false);
                        }
                      }}
                      placeholder={
                        replyingTo 
                          ? `Reply to ${replyingTo.user.name}...`
                          : selectedFile 
                            ? 'Add a caption...' 
                            : `Message #${selectedRoom.name}`
                      }
                      className={`w-full bg-gray-100 dark:bg-gray-800 border-transparent rounded pl-3 ${isMobile ? 'pr-12' : 'pr-20'} py-2 text-sm transition-all ${
                        replyingTo 
                          ? 'ring-2 ring-blue-500 border-blue-500 bg-blue-50 dark:bg-blue-900/20' 
                          : 'focus:ring-1 focus:ring-blue-500 focus:border-blue-500'
                      }`}
                      disabled={isLoadingMessages || uploading}
                    />

                    {/* Emoji picker button - desktop only */}
                    {!isMobile && (
                      <div className="absolute right-10 top-1/2 transform -translate-y-1/2">
                        <button
                          type="button"
                          onClick={() => setShowEmojiPicker(!showEmojiPicker)}
                          className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors"
                          title="Add emoji"
                        >
                          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                          </svg>
                        </button>

                        <EmojiPicker
                          isOpen={showEmojiPicker}
                          onClose={() => setShowEmojiPicker(false)}
                          onEmojiSelect={handleEmojiSelect}
                          position="top"
                        />
                      </div>
                    )}
                  </div>

                  {/* Send button */}
                  <motion.button 
                    type="submit" 
                    disabled={(!newMessage.trim() && !selectedFile) || isLoadingMessages || uploading} 
                    className="flex-shrink-0 p-2 bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white rounded transition-colors disabled:cursor-not-allowed"
                    whileHover={{ scale: 1.05 }}
                    whileTap={{ scale: 0.95 }}
                    title="Send message"
                  >
                    {uploading ? (
                      <div className="w-4 h-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
                    ) : (
                      <svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M3.478 2.405a.75.75 0 00-.926.94l2.432 7.905H13.5a.75.75 0 010 1.5H4.984l-2.432 7.905a.75.75 0 00.926.94 60.519 60.519 0 0018.445-8.986.75.75 0 000-1.218A60.517 60.517 0 003.478 2.405z" />
                      </svg>
                    )}
                  </motion.button>
                </div>

                {/* Input help text */}
                <p className="text-xs text-gray-500 dark:text-gray-400 mt-1 text-center">
                  {isMobile ? 'Tap to send' : 'Press Enter to send'} â€ĸ {selectedFile ? `File: ${selectedFile.name}` : 'Drag & drop files or use 📎'}
                </p>
              </form>
            </footer>
          </>
        )}
      </main>

      {/* Right Sidebar: Participants List */}
      <AnimatePresence>
      {((showParticipants && !isMobile) || (showRightSidebar && isMobile)) && selectedRoom && (
        <motion.aside 
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 50 }}
          transition={{ duration: 0.2 }}
          className={`
            border-l border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 flex flex-col
            ${isMobile 
              ? 'absolute top-0 right-0 h-full w-80 max-w-[85vw] z-30'
              : 'w-64 relative'
            }
          `}
        >
          <header className="p-3 border-b border-gray-200 dark:border-gray-800 h-14 flex-shrink-0 flex items-center justify-between">
            <h3 className="text-base font-bold">Users ({uniqueParticipants.length})</h3>
            <button 
              onClick={() => {
                if (isMobile) {
                  setShowRightSidebar(false);
                } else {
                  setShowParticipants(false);
                }
              }} 
              className="p-1.5 rounded text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
            >
                <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
                  <path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
                </svg>
            </button>
          </header>
          <div className="flex-1 overflow-y-auto p-2 space-y-1">
            {uniqueParticipants.filter(p => p && p.user).map(participant => {
              const notification = directMessageNotifications.get(participant.user.id);
              const hasUnread = notification && notification.unreadCount > 0;
              
              return (
                <div key={participant.user.id} className="flex items-center gap-2 p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800/50">
                  <div className="relative">
                    <UserAvatar 
                      user={participant.user}
                      size="sm"
                      showStatus={true}
                      clickable={true}
                      onClick={() => setProfilePopover({ 
                        userId: participant.user.id, 
                        isOpen: profilePopover?.userId !== participant.user.id || !profilePopover?.isOpen 
                      })}
                    />
                    {profilePopover?.userId === participant.user.id && profilePopover?.isOpen && (
                      <ProfilePopover
                        userId={participant.user.id}
                        isOpen={true}
                        onClose={() => setProfilePopover(null)}
                        position="left"
                        onStartDirectMessage={(userId, userName) => {
                          handleDoubleClickUser(userId, userName);
                        }}
                      />
                    )}
                    {participant.role === 'ADMIN' && (
                      <span className="absolute -top-1 -right-1 block h-3 w-3 rounded-full bg-yellow-500 border border-white dark:border-gray-900" title="Admin"></span>
                    )}
                    {hasUnread && (
                      <span className="absolute -top-1 -left-1 block h-4 w-4 rounded-full bg-red-500 text-white text-xs font-bold flex items-center justify-center border border-white dark:border-gray-900" title={`${notification.unreadCount} unread message${notification.unreadCount > 1 ? 's' : ''}`}>
                        {notification.unreadCount > 9 ? '9+' : notification.unreadCount}
                      </span>
                      )}
                  </div>
                  <div className="flex-1">
                    <p 
                      className={`text-sm font-semibold truncate cursor-pointer ${
                        hasUnread 
                          ? 'text-blue-600 dark:text-blue-400 font-bold' 
                          : 'text-gray-900 dark:text-gray-100 hover:text-blue-600 dark:hover:text-blue-400'
                      }`}
                      onDoubleClick={() => {
                        handleDoubleClickUser(participant.user.id, participant.user.name);
                        // Close sidebar on mobile after starting DM
                        if (isMobile) {
                          setShowRightSidebar(false);
                        }
                      }}
                      title={`${isMobile ? 'Tap' : 'Double-click'} to start private chat with ${participant.user.name}${hasUnread ? ` (${notification.unreadCount} unread)` : ''}`}
                    >
                      {participant.user.name}
                      {hasUnread && (
                        <span className="ml-1 text-xs text-red-500 dark:text-red-400">
                          ({notification.unreadCount})
                        </span>
                      )}
                    </p>
                  </div>
                  <div className="ml-auto">
                    <ParticipantActions
                      participant={participant}
                      roomId={selectedRoom.id}
                      onKick={handleKickParticipant}
                    />
                  </div>
                </div>
              );
            })}
          </div>
        </motion.aside>
      )}
      </AnimatePresence>

      {/* ✅ OLD GROUP VIDEO CALL DISABLED - Using global SimpleVideoCall now */}
      <AnimatePresence>
        {false && (
          <div>Old group video call disabled</div>
        )}
      </AnimatePresence>

      <AnimatePresence>
        {directMessage && (
          <DirectMessage
            chatId={directMessage.recipientId}
            onClose={() => setDirectMessage(null)}
          />
        )}
      </AnimatePresence>
    </div>
  );
};

export default GroupChat;

CasperSecurity Mini