/** * useChatWindow — gerencia o estado e handlers da área de chat ativa. * * Extrai de inbox/index.tsx: * - scroll + carregamento de histórico * - indicador de digitação * - reply / reaction * - envio de mídia e áudio * - sync de avatar * - abertura de perfil / menu de chat */ import { useState, useRef, useCallback } from 'react' import { mediaApi } from '../../services/chatApiService' import type { NewWhatsChat } from '../../types/inboxTypes' import type { Message } from '../../types/inboxTypes' interface UseChatWindowOptions { selectedChat: NewWhatsChat | null activeInstanceId: string | null onLoadMoreHistory: () => Promise<{ hasMore: boolean }> } export function useChatWindow({ selectedChat, activeInstanceId, onLoadMoreHistory, }: UseChatWindowOptions) { // ── Refs ──────────────────────────────────────────────────────────────────── const scrollRef = useRef(null) const isTypingRef = useRef(false) const typingTimeout = useRef(null) // ── Estado ────────────────────────────────────────────────────────────────── const [isTyping, setIsTyping] = useState(false) const [replyingTo, setReplyingTo] = useState(null) const [reactionPickerFor, setReactionPickerFor] = useState(null) const [hasMoreHistory, setHasMoreHistory] = useState(true) const [loadingMore, setLoadingMore] = useState(false) const [isProfileOpen, setIsProfileOpen] = useState(false) const [isSyncingAvatar, setIsSyncingAvatar] = useState(false) const [isChatMenuOpen, setIsChatMenuOpen] = useState(false) // ── Handlers ──────────────────────────────────────────────────────────────── const handleTyping = useCallback(() => { if (!selectedChat) return if (!isTypingRef.current) { isTypingRef.current = true setIsTyping(true) } if (typingTimeout.current) clearTimeout(typingTimeout.current) typingTimeout.current = setTimeout(() => { isTypingRef.current = false setIsTyping(false) }, 3000) }, [selectedChat]) const handleScroll = useCallback(async () => { const el = scrollRef.current if (!el || !hasMoreHistory || loadingMore) return if (el.scrollTop < 100) { setLoadingMore(true) const { hasMore } = await onLoadMoreHistory() setHasMoreHistory(hasMore) setLoadingMore(false) } }, [hasMoreHistory, loadingMore, onLoadMoreHistory]) const handleSelectChat = useCallback(() => { setHasMoreHistory(true) }, []) const handleReactToMessage = useCallback((_msg: Message, _emoji: string) => { setReactionPickerFor(null) // Será implementado via endpoint /api/instances/:id/react }, []) const handleSendMedia = useCallback(async (file: File, caption?: string) => { if (!selectedChat || !activeInstanceId) return try { await mediaApi.sendMedia(activeInstanceId, selectedChat.remote_jid, file, caption || undefined, undefined) } catch (err) { console.error('Erro ao enviar mídia:', err) } }, [selectedChat, activeInstanceId]) const handleSendAudio = useCallback(async (blob: Blob) => { if (!selectedChat || !activeInstanceId) return try { await mediaApi.sendAudio(activeInstanceId, selectedChat.remote_jid, blob) } catch (err) { console.error('Erro ao enviar áudio:', err) } }, [selectedChat, activeInstanceId]) const handleSyncAvatar = useCallback(async () => { if (!selectedChat || !activeInstanceId) return setIsSyncingAvatar(true) try { const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3003' const token = localStorage.getItem('token') await fetch( `${API}/api/inbox/avatar/${encodeURIComponent(selectedChat.remote_jid)}?instance=${activeInstanceId}`, { headers: { Authorization: `Bearer ${token}` } } ) } finally { setIsSyncingAvatar(false) } }, [selectedChat, activeInstanceId]) const toggleReactionPicker = useCallback((id: string) => { setReactionPickerFor(prev => prev === id ? null : id) }, []) return { // Refs scrollRef, // Estado isTyping, replyingTo, reactionPickerFor, hasMoreHistory, loadingMore, isProfileOpen, isSyncingAvatar, isChatMenuOpen, // Setters simples setReplyingTo, setIsProfileOpen, setIsChatMenuOpen, // Handlers handleTyping, handleScroll, handleSelectChat, handleReactToMessage, handleSendMedia, handleSendAudio, handleSyncAvatar, toggleReactionPicker, } }