"use client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useRef, useState } from "react"; import type { Socket } from "socket.io-client"; import { useSession } from "./use-session"; import { useSocket } from "./use-socket"; export interface ChatMessage { _id: string; chat_id: string; sender_id: number; content: string; created_at: string; read_at: string | null; edited_at: string | null; message_type: "text" | "system"; metadata?: any; } export interface Chat { chat_id: string; listing_id: number; buyer_id: number; seller_id: number; created_at: string; updated_at: string; last_message_at: string | null; last_message_preview: string | null; buyer_unread_count: number; seller_unread_count: number; is_active: boolean; listing_cache: { item_name: string; item_image_url?: string; price_pix?: number; price_tibia_coins?: number; price_gold_coins?: number; server_name: string; listing_status: string; }; buyer_cache: { user_id: number; username: string; character_name?: string; }; seller_cache: { user_id: number; username: string; character_name?: string; }; } interface UseChatReturn { chat: Chat | null; messages: ChatMessage[]; isLoading: boolean; isSending: boolean; error: string | null; sendMessage: (content: string) => Promise; loadMoreMessages: () => Promise; hasMore: boolean; isTyping: boolean; otherUserTyping: boolean; } /** * Hook para gerenciar um chat específico */ export function useChat(chatId: string | null): UseChatReturn { const { user } = useSession(); const { socket, isConnected } = useSocket(); const queryClient = useQueryClient(); const [messages, setMessages] = useState([]); const [isSending, setIsSending] = useState(false); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [isTyping, setIsTyping] = useState(false); const [otherUserTyping, setOtherUserTyping] = useState(false); const typingTimeoutRef = useRef(null); // Buscar dados do chat const { data: chatData, isLoading: isLoadingChat } = useQuery<{ success: boolean; chat: Chat; }>({ queryKey: ["chat", chatId], queryFn: async () => { if (!chatId) return null; const res = await fetch(`/api/chats/${chatId}`); if (!res.ok) throw new Error("Erro ao buscar chat"); return res.json(); }, enabled: !!chatId && !!user, staleTime: 1000 * 30, }); // Buscar mensagens const { data: messagesData, isLoading: isLoadingMessages } = useQuery<{ success: boolean; messages: ChatMessage[]; pagination: { page: number; limit: number; total: number; totalPages: number; }; }>({ queryKey: ["chat-messages", chatId, page], queryFn: async () => { if (!chatId) return null; const res = await fetch( `/api/chats/${chatId}/messages?page=${page}&limit=50`, ); if (!res.ok) throw new Error("Erro ao buscar mensagens"); return res.json(); }, enabled: !!chatId && !!user, staleTime: 1000 * 10, }); // Atualizar mensagens quando dados mudarem useEffect(() => { if (messagesData?.messages) { if (page === 1) { setMessages(messagesData.messages); } else { // Adicionar mensagens antigas no início setMessages((prev) => [...messagesData.messages, ...prev]); } setHasMore( messagesData.pagination.page < messagesData.pagination.totalPages, ); } }, [messagesData, page]); // Conectar ao Socket.io quando chatId mudar useEffect(() => { if (!socket || !isConnected || !chatId) return; // Entrar no chat socket.emit("join_chat", { chat_id: chatId }); // Listener para novas mensagens const handleNewMessage = (data: { message: ChatMessage }) => { console.log("[CHAT-DEBUG] useChat: new_message recebido", { message: data.message, }); // Verificar se a mensagem já existe para evitar duplicação setMessages((prev) => { const messageExists = prev.some((msg) => msg._id === data.message._id); if (messageExists) { console.log("[CHAT-DEBUG] Mensagem já existe, ignorando duplicação", { messageId: data.message._id, }); return prev; } return [...prev, data.message]; }); // Invalidar cache queryClient.invalidateQueries({ queryKey: ["chat-messages", chatId] }); queryClient.invalidateQueries({ queryKey: ["chat", chatId] }); }; // Listener para indicador de digitação const handleTyping = (data: { user_id: number; username: string; is_typing: boolean; }) => { if (data.user_id !== user?.userId) { setOtherUserTyping(data.is_typing); } }; socket.on("new_message", handleNewMessage); socket.on("user_typing", handleTyping); return () => { socket.off("new_message", handleNewMessage); socket.off("user_typing", handleTyping); }; }, [socket, isConnected, chatId, user, queryClient]); // Função para enviar mensagem const sendMessage = useCallback( async (content: string) => { if (!chatId || !socket || !isConnected || !content.trim()) return; console.log("[CHAT-DEBUG] sendMessage chamado", { chatId, content, socketConnected: isConnected, }); setIsSending(true); try { // Enviar via Socket.io socket.emit("send_message", { chat_id: chatId, content: content.trim(), }); // REMOVIDO: Otimistic update que causava duplicação // A mensagem será adicionada automaticamente quando o servidor emitir "new_message" // Parar de digitar setIsTyping(false); if (typingTimeoutRef.current) { clearTimeout(typingTimeoutRef.current); } socket.emit("typing", { chat_id: chatId, is_typing: false }); } catch (error) { console.error("[CHAT-DEBUG] Erro ao enviar mensagem:", error); } finally { setIsSending(false); } }, [chatId, socket, isConnected], ); // Função para carregar mais mensagens const loadMoreMessages = useCallback(async () => { if (!hasMore || isLoadingMessages) return; setPage((prev) => prev + 1); }, [hasMore, isLoadingMessages]); return { chat: chatData?.chat || null, messages, isLoading: isLoadingChat || isLoadingMessages, isSending, error: null, sendMessage, loadMoreMessages, hasMore, isTyping, otherUserTyping, }; } // Exportar handlers de digitação separadamente se necessário export function useChatTyping( socket: Socket | null, isConnected: boolean, chatId: string | null, ) { const [isTyping, setIsTyping] = useState(false); const typingTimeoutRef = useRef(null); const handleTypingStart = useCallback(() => { if (!socket || !isConnected || !chatId || isTyping) return; setIsTyping(true); socket.emit("typing", { chat_id: chatId, is_typing: true }); if (typingTimeoutRef.current) { clearTimeout(typingTimeoutRef.current); } typingTimeoutRef.current = setTimeout(() => { setIsTyping(false); socket.emit("typing", { chat_id: chatId, is_typing: false }); }, 3000); }, [socket, isConnected, chatId, isTyping]); const handleTypingStop = useCallback(() => { if (!socket || !isConnected || !chatId || !isTyping) return; setIsTyping(false); socket.emit("typing", { chat_id: chatId, is_typing: false }); if (typingTimeoutRef.current) { clearTimeout(typingTimeoutRef.current); } }, [socket, isConnected, chatId, isTyping]); useEffect(() => { return () => { if (typingTimeoutRef.current) { clearTimeout(typingTimeoutRef.current); } }; }, []); return { handleTypingStart, handleTypingStop }; }