/**
 * ChatWidget - Componente de chat em tempo real usando Convex subscriptions
 *
 * Arquitetura:
 * - Usa useQuery do Convex React para subscriptions reativas (tempo real verdadeiro)
 * - Usa useMutation do Convex React para enviar mensagens
 * - Mantém Tauri apenas para: upload de arquivos, gerenciamento de janela
 * - Sem polling - todas as atualizacoes sao push-based via WebSocket
 */

import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { open as openDialog } from "@tauri-apps/plugin-dialog"
import { openUrl as openExternal } from "@tauri-apps/plugin-opener"
import { invoke } from "@tauri-apps/api/core"
import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2, Eye, Download, Check, MessagesSquare } from "lucide-react"
import type { Id } from "@convex/_generated/dataModel"
import { useMachineMessages, useMachineSessions, usePostMachineMessage, useMarkMachineMessagesRead, type MachineMessage } from "./useConvexMachineQueries"
import { useConvexMachine } from "./ConvexMachineProvider"

const MAX_MESSAGES_IN_MEMORY = 200
const MARK_READ_BATCH_SIZE = 50
const SCROLL_BOTTOM_THRESHOLD_PX = 120

const ALLOWED_EXTENSIONS = [
  "jpg", "jpeg", "png", "gif", "webp",
  "pdf", "txt", "doc", "docx", "xls", "xlsx",
]

interface UploadedAttachment {
  storageId: string
  name: string
  size?: number
  type?: string
}

interface ChatAttachment {
  storageId: string
  name: string
  size?: number
  type?: string
}

function getFileIcon(fileName: string) {
  const ext = fileName.toLowerCase().split(".").pop() ?? ""
  if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) {
    return <ImageIcon className="size-4" />
  }
  if (["pdf", "doc", "docx", "txt"].includes(ext)) {
    return <FileText className="size-4" />
  }
  return <File className="size-4" />
}

function isImageAttachment(attachment: ChatAttachment) {
  if (attachment.type?.startsWith("image/")) return true
  const ext = attachment.name.toLowerCase().split(".").pop() ?? ""
  return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext)
}

function formatAttachmentSize(size?: number) {
  if (!size) return null
  if (size < 1024) return `${size}B`
  const kb = size / 1024
  if (kb < 1024) return `${Math.round(kb)}KB`
  return `${(kb / 1024).toFixed(1)}MB`
}

function getUnreadAgentMessageIds(messages: MachineMessage[], unreadCount: number): string[] {
  if (unreadCount <= 0 || messages.length === 0) return []
  const ids: string[] = []
  for (let i = messages.length - 1; i >= 0 && ids.length < unreadCount; i--) {
    const msg = messages[i]
    if (!msg.isFromMachine) {
      ids.push(msg.id)
    }
  }
  return ids.reverse()
}

function chunkArray<T>(items: T[], size: number): T[][] {
  if (size <= 0) return [items]
  const result: T[][] = []
  for (let i = 0; i < items.length; i += size) {
    result.push(items.slice(i, i + size))
  }
  return result
}

function MessageAttachment({
  attachment,
  isAgent,
  loadUrl,
}: {
  attachment: ChatAttachment
  isAgent: boolean
  loadUrl: (storageId: string) => Promise<string>
}) {
  const [url, setUrl] = useState<string | null>(null)
  const [loading, setLoading] = useState(true)
  const [downloading, setDownloading] = useState(false)
  const [downloaded, setDownloaded] = useState(false)

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    loadUrl(attachment.storageId)
      .then((resolved) => {
        if (!cancelled) setUrl(resolved)
      })
      .catch((err) => {
        console.error("Falha ao carregar URL do anexo:", err)
      })
      .finally(() => {
        if (!cancelled) setLoading(false)
      })

    return () => {
      cancelled = true
    }
  }, [attachment.storageId, loadUrl])

  const handleView = async () => {
    if (!url) return
    try {
      await openExternal(url)
    } catch (err) {
      console.error("Falha ao abrir anexo:", err)
    }
  }

  const handleDownload = async () => {
    if (!url || downloading) return
    setDownloading(true)
    try {
      const response = await fetch(url)
      const blob = await response.blob()
      const downloadUrl = URL.createObjectURL(blob)
      const a = document.createElement("a")
      a.href = downloadUrl
      a.download = attachment.name
      document.body.appendChild(a)
      a.click()
      document.body.removeChild(a)
      URL.revokeObjectURL(downloadUrl)
      setDownloaded(true)
      setTimeout(() => setDownloaded(false), 2000)
    } catch (err) {
      console.error("Falha ao baixar anexo:", err)
      await handleView()
    } finally {
      setDownloading(false)
    }
  }

  const sizeLabel = formatAttachmentSize(attachment.size)
  const isImage = isImageAttachment(attachment)

  if (loading) {
    return (
      <div className={`flex items-center gap-2 rounded-lg p-2 text-xs ${isAgent ? "bg-white/10" : "bg-slate-100"}`}>
        <Loader2 className="size-4 animate-spin" />
        <span className="truncate">Carregando anexo...</span>
      </div>
    )
  }

  if (isImage && url) {
    return (
      <div className={`group relative overflow-hidden rounded-lg border ${isAgent ? "border-white/10" : "border-slate-200"}`}>
        {/* eslint-disable-next-line @next/next/no-img-element -- Tauri desktop app, not Next.js */}
        <img
          src={url}
          alt={attachment.name}
          className="size-24 cursor-pointer object-cover"
          onClick={handleView}
        />
        <div className="absolute inset-0 flex items-center justify-center gap-1 bg-black/50 opacity-0 transition-opacity group-hover:opacity-100">
          <button
            onClick={handleView}
            className="flex size-7 items-center justify-center rounded-full bg-white/20 hover:bg-white/30"
            aria-label="Visualizar anexo"
          >
            <Eye className="size-4 text-white" />
          </button>
          <button
            onClick={handleDownload}
            disabled={downloading}
            className="flex size-7 items-center justify-center rounded-full bg-white/20 hover:bg-white/30 disabled:opacity-60"
            aria-label="Baixar anexo"
          >
            {downloading ? (
              <Loader2 className="size-4 animate-spin text-white" />
            ) : downloaded ? (
              <Check className="size-4 text-emerald-300" />
            ) : (
              <Download className="size-4 text-white" />
            )}
          </button>
        </div>
      </div>
    )
  }

  return (
    <div className={`flex items-center gap-2 rounded-lg p-2 text-xs ${isAgent ? "bg-white/10" : "bg-slate-100"}`}>
      {getFileIcon(attachment.name)}
      <button
        onClick={handleView}
        className="flex-1 truncate text-left hover:underline"
        aria-label={`Visualizar anexo ${attachment.name}`}
      >
        {attachment.name}
      </button>
      {sizeLabel && <span className="text-xs opacity-60">({sizeLabel})</span>}
      <div className="ml-1 flex items-center gap-1">
        <button
          onClick={handleView}
          className={`flex size-7 items-center justify-center rounded-md ${isAgent ? "hover:bg-white/10" : "hover:bg-slate-200"}`}
          aria-label="Visualizar anexo"
        >
          <Eye className="size-4" />
        </button>
        <button
          onClick={handleDownload}
          disabled={downloading}
          className={`flex size-7 items-center justify-center rounded-md disabled:opacity-60 ${isAgent ? "hover:bg-white/10" : "hover:bg-slate-200"}`}
          aria-label="Baixar anexo"
        >
          {downloading ? (
            <Loader2 className="size-4 animate-spin" />
          ) : downloaded ? (
            <Check className="size-4 text-emerald-500" />
          ) : (
            <Download className="size-4" />
          )}
        </button>
      </div>
    </div>
  )
}

interface ChatWidgetProps {
  ticketId: string
  ticketRef?: number
}

export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
  const [inputValue, setInputValue] = useState("")
  const [isSending, setIsSending] = useState(false)
  const [isUploading, setIsUploading] = useState(false)
  const [pendingAttachments, setPendingAttachments] = useState<UploadedAttachment[]>([])
  // Inicializa baseado na altura real da janela (< 100px = minimizado)
  const [isMinimized, setIsMinimized] = useState(() => window.innerHeight < 100)

  // Convex hooks
  const { apiBaseUrl, machineToken } = useConvexMachine()
  const { sessions: machineSessions = [] } = useMachineSessions()
  const { messages: convexMessages, hasSession, unreadCount, isLoading } = useMachineMessages(
    ticketId as Id<"tickets">,
    { limit: MAX_MESSAGES_IN_MEMORY }
  )
  const postMessage = usePostMachineMessage()
  const markMessagesRead = useMarkMachineMessagesRead()

  // Limitar mensagens em memoria
  const messages = useMemo(() => convexMessages.slice(-MAX_MESSAGES_IN_MEMORY), [convexMessages])

  const messagesEndRef = useRef<HTMLDivElement>(null)
  const messagesContainerRef = useRef<HTMLDivElement>(null)
  const messageElementsRef = useRef<Map<string, HTMLDivElement>>(new Map())
  const prevHasSessionRef = useRef<boolean>(false)

  const [isAtBottom, setIsAtBottom] = useState(true)
  const isAtBottomRef = useRef(true)
  const pendingScrollActionRef = useRef<
    | { type: "bottom"; behavior: ScrollBehavior; markRead: boolean }
    | { type: "message"; messageId: string; behavior: ScrollBehavior; markRead: boolean }
    | null
  >(null)
  const autoReadInFlightRef = useRef(false)
  const lastAutoReadCountRef = useRef<number | null>(null)

  const unreadAgentMessageIds = useMemo(() => getUnreadAgentMessageIds(messages, unreadCount), [messages, unreadCount])
  const firstUnreadAgentMessageId = unreadAgentMessageIds[0] ?? null

  const otherUnreadCount = useMemo(() => {
    if (machineSessions.length <= 1) return 0
    return machineSessions.reduce((sum, session) => {
      return sum + (session.ticketId === ticketId ? 0 : session.unreadCount)
    }, 0)
  }, [machineSessions, ticketId])

  const handleOpenHub = useCallback(async () => {
    try {
      await invoke("open_hub_window")
      await invoke("set_hub_minimized", { minimized: false })
    } catch (err) {
      console.error("Erro ao abrir hub:", err)
    }
  }, [])

  const updateIsAtBottom = useCallback(() => {
    const el = messagesContainerRef.current
    if (!el) return
    const distance = el.scrollHeight - el.scrollTop - el.clientHeight
    const atBottom = distance <= SCROLL_BOTTOM_THRESHOLD_PX
    if (isAtBottomRef.current !== atBottom) {
      isAtBottomRef.current = atBottom
      setIsAtBottom(atBottom)
    }
  }, [])

  const scrollToBottom = useCallback((behavior: ScrollBehavior) => {
    messagesEndRef.current?.scrollIntoView({ behavior })
    requestAnimationFrame(() => updateIsAtBottom())
  }, [updateIsAtBottom])

  const scrollToMessage = useCallback((messageId: string, behavior: ScrollBehavior) => {
    const el = messageElementsRef.current.get(messageId)
    if (!el) return false
    el.scrollIntoView({ behavior, block: "center" })
    requestAnimationFrame(() => updateIsAtBottom())
    return true
  }, [updateIsAtBottom])

  // Fechar janela quando sessao termina
  useEffect(() => {
    const prevHasSession = prevHasSessionRef.current
    if (prevHasSession && !hasSession) {
      invoke("close_chat_window", { ticketId }).catch((err) => {
        console.error("Erro ao fechar janela ao encerrar sessao:", err)
      })
    }
    prevHasSessionRef.current = hasSession
  }, [hasSession, ticketId])

  // Ref para acessar isMinimized dentro de callbacks
  const isMinimizedRef = useRef(isMinimized)
  useEffect(() => {
    isMinimizedRef.current = isMinimized
  }, [isMinimized])

  // Cache de URLs de anexos
  const attachmentUrlCacheRef = useRef<Map<string, string>>(new Map())

  const loadAttachmentUrl = useCallback(async (storageId: string) => {
    const cached = attachmentUrlCacheRef.current.get(storageId)
    if (cached) return cached

    if (!apiBaseUrl || !machineToken) {
      throw new Error("Configuracao nao disponivel")
    }

    const response = await fetch(`${apiBaseUrl}/api/machines/chat/attachments/url`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        machineToken,
        ticketId,
        storageId,
      }),
    })

    if (!response.ok) {
      const text = await response.text().catch(() => "")
      throw new Error(text || `Falha ao obter URL do anexo (${response.status})`)
    }

    const data = (await response.json()) as { url?: string }
    if (!data.url) {
      throw new Error("Resposta invalida ao obter URL do anexo")
    }

    attachmentUrlCacheRef.current.set(storageId, data.url)
    return data.url
  }, [apiBaseUrl, machineToken, ticketId])

  const markUnreadMessagesRead = useCallback(async () => {
    if (unreadCount <= 0) return false
    const ids = getUnreadAgentMessageIds(messages, unreadCount)
    if (ids.length === 0) return false

    const chunks = chunkArray(ids, MARK_READ_BATCH_SIZE)
    for (const chunk of chunks) {
      await markMessagesRead({
        ticketId: ticketId as Id<"tickets">,
        messageIds: chunk as Id<"ticketChatMessages">[],
      })
    }
    return true
  }, [messages, ticketId, unreadCount, markMessagesRead])

  const maybeAutoMarkRead = useCallback(async () => {
    if (autoReadInFlightRef.current) return
    if (!hasSession || unreadCount <= 0) return
    if (isMinimizedRef.current || !isAtBottomRef.current) return
    if (lastAutoReadCountRef.current === unreadCount) return

    autoReadInFlightRef.current = true
    try {
      const didMark = await markUnreadMessagesRead()
      if (didMark) {
        lastAutoReadCountRef.current = unreadCount
      }
    } finally {
      autoReadInFlightRef.current = false
    }
  }, [hasSession, unreadCount, markUnreadMessagesRead])

  // Auto-scroll quando novas mensagens chegam (se ja estava no bottom)
  const prevMessagesLengthRef = useRef(messages.length)
  useEffect(() => {
    if (messages.length > prevMessagesLengthRef.current && isAtBottomRef.current && !isMinimizedRef.current) {
      pendingScrollActionRef.current = { type: "bottom", behavior: "smooth", markRead: true }
    }
    prevMessagesLengthRef.current = messages.length
  }, [messages.length])

  // Executar scroll pendente
  useEffect(() => {
    if (isMinimized) return

    const action = pendingScrollActionRef.current
    if (!action) return

    if (action.type === "bottom") {
      if (!messagesEndRef.current) return
      pendingScrollActionRef.current = null
      scrollToBottom(action.behavior)
      if (action.markRead) {
        markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err))
      }
      return
    }

    const ok = scrollToMessage(action.messageId, action.behavior)
    if (!ok) {
      if (!messagesEndRef.current) return
      pendingScrollActionRef.current = null
      scrollToBottom(action.behavior)
      if (action.markRead) {
        markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err))
      }
      return
    }

    pendingScrollActionRef.current = null
    if (action.markRead) {
      markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err))
    }
  }, [isMinimized, messages, markUnreadMessagesRead, scrollToBottom, scrollToMessage])

  useEffect(() => {
    if (unreadCount === 0) {
      lastAutoReadCountRef.current = null
      return
    }
    maybeAutoMarkRead().catch((err) => console.error("Falha ao auto-marcar mensagens:", err))
  }, [isMinimized, isAtBottom, unreadCount, maybeAutoMarkRead])

  // Sincronizar estado minimizado com tamanho da janela
  useEffect(() => {
    const mountTime = Date.now()
    const STABILIZATION_DELAY = 500

    const handler = () => {
      if (Date.now() - mountTime < STABILIZATION_DELAY) {
        return
      }
      const h = window.innerHeight
      setIsMinimized(h < 100)
    }
    window.addEventListener("resize", handler)
    return () => window.removeEventListener("resize", handler)
  }, [])

  // Selecionar arquivo para anexar
  const handleAttach = async () => {
    if (isUploading || isSending) return

    try {
      const selected = await openDialog({
        multiple: false,
        filters: [{
          name: "Arquivos permitidos",
          extensions: ALLOWED_EXTENSIONS,
        }],
      })

      if (!selected) return

      const filePath = typeof selected === "string" ? selected : (selected as { path: string }).path

      setIsUploading(true)

      if (!apiBaseUrl || !machineToken) {
        throw new Error("Configuracao nao disponivel")
      }

      const attachment = await invoke<UploadedAttachment>("upload_chat_file", {
        baseUrl: apiBaseUrl,
        token: machineToken,
        filePath,
      })

      setPendingAttachments(prev => [...prev, attachment])
    } catch (err) {
      console.error("Erro ao anexar arquivo:", err)
      alert(typeof err === "string" ? err : "Erro ao anexar arquivo")
    } finally {
      setIsUploading(false)
    }
  }

  // Remover anexo pendente
  const handleRemoveAttachment = (storageId: string) => {
    setPendingAttachments(prev => prev.filter(a => a.storageId !== storageId))
  }

  // Enviar mensagem
  const handleSend = async () => {
    if ((!inputValue.trim() && pendingAttachments.length === 0) || isSending) return

    const messageText = inputValue.trim()
    const attachmentsToSend = [...pendingAttachments]
    setInputValue("")
    setPendingAttachments([])
    setIsSending(true)

    try {
      await postMessage({
        ticketId: ticketId as Id<"tickets">,
        body: messageText,
        attachments: attachmentsToSend.length > 0 ? attachmentsToSend.map(a => ({
          storageId: a.storageId as Id<"_storage">,
          name: a.name,
          size: a.size,
          type: a.type,
        })) : undefined,
      })
      pendingScrollActionRef.current = { type: "bottom", behavior: "smooth", markRead: false }
    } catch (err) {
      console.error("Erro ao enviar mensagem:", err)
      setInputValue(messageText)
      setPendingAttachments(attachmentsToSend)
    } finally {
      setIsSending(false)
    }
  }

  const handleMinimize = async () => {
    setIsMinimized(true)
    try {
      await invoke("set_chat_minimized", { ticketId, minimized: true })
    } catch (err) {
      console.error("Erro ao minimizar janela:", err)
    }
  }

  const handleExpand = async () => {
    if (firstUnreadAgentMessageId) {
      pendingScrollActionRef.current = { type: "message", messageId: firstUnreadAgentMessageId, behavior: "auto", markRead: unreadCount > 0 }
    } else {
      pendingScrollActionRef.current = { type: "bottom", behavior: "auto", markRead: false }
    }

    setIsMinimized(false)
    try {
      await invoke("open_chat_window", { ticketId, ticketRef: ticketRef ?? 0 })
    } catch (err) {
      console.error("Erro ao expandir janela:", err)
    }
  }

  const handleClose = () => {
    invoke("close_chat_window", { ticketId }).catch((err) => {
      console.error("Erro ao fechar janela de chat:", err)
    })
  }

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault()
      handleSend()
    }
  }

  // Loading
  if (isLoading) {
    return (
      <div className="pointer-events-none flex h-full w-full items-end justify-end bg-transparent p-2">
        <div className="pointer-events-auto flex items-center gap-2 rounded-full bg-slate-200 px-4 py-2 text-slate-600 shadow-lg">
          <Loader2 className="size-4 animate-spin" />
          <span className="text-sm font-medium">Carregando...</span>
        </div>
      </div>
    )
  }

  // Sem sessao ativa
  if (!hasSession) {
    return (
      <div className="pointer-events-none flex h-full w-full items-end justify-end bg-transparent">
        <div className="pointer-events-auto flex items-center gap-2 rounded-full bg-slate-200 px-4 py-2 text-slate-600 shadow-lg">
          <MessageCircle className="size-4" />
          <span className="text-sm font-medium">
            {ticketRef ? `Ticket #${ticketRef}` : "Chat"}
          </span>
          <span className="size-2 rounded-full bg-slate-400" />
          <span className="text-xs text-slate-500">Offline</span>
          <button
            onClick={handleClose}
            className="ml-1 rounded-full p-1 text-slate-600 hover:bg-slate-300/60"
            aria-label="Fechar chat"
          >
            <X className="size-4" />
          </button>
        </div>
      </div>
    )
  }

  // Minimizado
  if (isMinimized) {
    return (
      <div className="pointer-events-none flex h-full w-full items-end justify-end bg-transparent pr-3">
        <button
          onClick={handleExpand}
          className="pointer-events-auto relative flex items-center gap-2 rounded-full bg-black px-4 py-2 text-white shadow-lg hover:bg-black/90"
        >
          <MessageCircle className="size-4" />
          <span className="text-sm font-medium">
            Ticket #{ticketRef}
          </span>
          <span className="size-2 rounded-full bg-emerald-400" />
          <ChevronUp className="size-4" />
          {unreadCount > 0 && (
            <span className="absolute -right-1 -top-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-xs font-bold">
              {unreadCount > 9 ? "9+" : unreadCount}
            </span>
          )}
        </button>
      </div>
    )
  }

  // Expandido
  return (
    <div className="flex h-full flex-col overflow-hidden rounded-2xl bg-white shadow-xl">
      {/* Header */}
      <div
        data-tauri-drag-region
        className="flex items-center justify-between border-b border-slate-200 bg-slate-50 px-4 py-3 rounded-t-2xl"
      >
        <div className="flex items-center gap-3">
          <div className="flex size-10 items-center justify-center rounded-full bg-black text-white">
            <MessageCircle className="size-5" />
          </div>
          <div>
            <div className="flex items-center gap-2">
              <p className="text-sm font-semibold text-slate-900">Chat</p>
              <span className="flex items-center gap-1.5 text-xs text-emerald-600">
                <span className="size-2 rounded-full bg-emerald-500 animate-pulse" />
                Online
              </span>
            </div>
            <p className="text-xs text-slate-500">
              Ticket #{ticketRef} - Suporte
            </p>
          </div>
        </div>
        <div className="flex items-center gap-1">
          {machineSessions.length > 1 && (
            <button
              onClick={handleOpenHub}
              className="relative rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
              aria-label="Abrir lista de chats"
            >
              <MessagesSquare className="size-4" />
              {otherUnreadCount > 0 && (
                <span className="absolute -right-1 -top-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white">
                  {otherUnreadCount > 9 ? "9+" : otherUnreadCount}
                </span>
              )}
            </button>
          )}
          <button
            onClick={handleMinimize}
            className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
            aria-label="Minimizar chat"
          >
            <Minimize2 className="size-4" />
          </button>
          <button
            onClick={handleClose}
            className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
            aria-label="Fechar chat"
          >
            <X className="size-4" />
          </button>
        </div>
      </div>

      {/* Mensagens */}
      <div
        ref={messagesContainerRef}
        onScroll={updateIsAtBottom}
        className="flex-1 overflow-y-auto p-4"
      >
        {messages.length === 0 ? (
          <div className="flex h-full flex-col items-center justify-center text-center">
            <p className="text-sm text-slate-400">
              Nenhuma mensagem ainda
            </p>
            <p className="mt-1 text-xs text-slate-400">
              O agente iniciara a conversa em breve
            </p>
          </div>
        ) : (
          <div className="space-y-4">
            {messages.map((msg) => {
              const isAgent = !msg.isFromMachine
              const bodyText = msg.body.trim()
              const shouldShowBody =
                bodyText.length > 0 && !(bodyText === "[Anexo]" && (msg.attachments?.length ?? 0) > 0)
              return (
                <div key={msg.id} className="space-y-2">
                  {firstUnreadAgentMessageId === msg.id && unreadCount > 0 && !isAtBottom && (
                    <div className="flex items-center gap-2">
                      <div className="h-px flex-1 bg-slate-200" />
                      <span className="text-xs font-medium text-slate-500">Novas mensagens</span>
                      <div className="h-px flex-1 bg-slate-200" />
                    </div>
                  )}

                  <div
                    ref={(el) => {
                      if (el) {
                        messageElementsRef.current.set(msg.id, el)
                      } else {
                        messageElementsRef.current.delete(msg.id)
                      }
                    }}
                    className={`flex gap-2 ${isAgent ? "flex-row-reverse" : "flex-row"}`}
                  >
                    {/* Avatar */}
                    <div
                      className={`flex size-7 shrink-0 items-center justify-center rounded-full ${
                        isAgent ? "bg-black text-white" : "bg-slate-200 text-slate-600"
                      }`}
                    >
                      {isAgent ? <MessageCircle className="size-3.5" /> : <User className="size-3.5" />}
                    </div>

                    {/* Bubble */}
                    <div
                      className={`max-w-[75%] rounded-2xl px-4 py-2 ${
                        isAgent
                          ? "rounded-br-md bg-black text-white"
                          : "rounded-bl-md border border-slate-100 bg-white text-slate-900 shadow-sm"
                      }`}
                    >
                      {!isAgent && (
                        <p className="mb-1 text-xs font-medium text-slate-500">
                          {msg.authorName}
                        </p>
                      )}
                      {shouldShowBody && <p className="whitespace-pre-wrap text-sm">{msg.body}</p>}
                      {/* Anexos */}
                      {msg.attachments && msg.attachments.length > 0 && (
                        <div className="mt-2 space-y-2">
                          {msg.attachments.map((att) => (
                            <MessageAttachment
                              key={att.storageId}
                              attachment={{
                                storageId: att.storageId as string,
                                name: att.name,
                                size: att.size,
                                type: att.type,
                              }}
                              isAgent={isAgent}
                              loadUrl={loadAttachmentUrl}
                            />
                          ))}
                        </div>
                      )}
                      <p
                        className={`mt-1 text-right text-xs ${
                          isAgent ? "text-white/60" : "text-slate-400"
                        }`}
                      >
                        {formatTime(msg.createdAt)}
                      </p>
                    </div>
                  </div>
                </div>
              )
            })}
            <div ref={messagesEndRef} />
          </div>
        )}
      </div>

      {/* Input */}
      <div className="border-t border-slate-200 p-3">
        {unreadCount > 0 && !isAtBottom && (
          <div className="mb-2 flex justify-center">
            <button
              type="button"
              onClick={() => {
                const target = firstUnreadAgentMessageId
                if (target) {
                  scrollToMessage(target, "smooth")
                } else {
                  scrollToBottom("smooth")
                }
                markUnreadMessagesRead().catch((err) => console.error("Falha ao marcar mensagens como lidas:", err))
              }}
              className="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-700 hover:bg-slate-200"
            >
              Ver novas mensagens ({unreadCount > 9 ? "9+" : unreadCount})
            </button>
          </div>
        )}
        {/* Anexos pendentes */}
        {pendingAttachments.length > 0 && (
          <div className="mb-2 flex flex-wrap gap-2">
            {pendingAttachments.map((att) => (
              <div
                key={att.storageId}
                className="flex items-center gap-1 rounded-lg bg-slate-100 px-2 py-1 text-xs"
              >
                {getFileIcon(att.name)}
                <span className="max-w-[100px] truncate">{att.name}</span>
                <button
                  onClick={() => handleRemoveAttachment(att.storageId)}
                  className="ml-1 rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600"
                  aria-label={`Remover anexo ${att.name}`}
                >
                  <X className="size-3" />
                </button>
              </div>
            ))}
          </div>
        )}
        <div className="flex items-end gap-2">
          <textarea
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onKeyDown={handleKeyDown}
            placeholder="Digite sua mensagem..."
            className="max-h-24 min-h-[36px] flex-1 resize-none rounded-lg border border-slate-200 px-3 py-2 text-sm focus:border-slate-400 focus:outline-none focus:ring-1 focus:ring-slate-400"
            rows={1}
          />
          <button
            onClick={handleAttach}
            disabled={isUploading || isSending}
            className="flex size-9 items-center justify-center rounded-lg text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 disabled:opacity-50"
            aria-label="Anexar arquivo"
          >
            {isUploading ? (
              <Loader2 className="size-4 animate-spin" />
            ) : (
              <Paperclip className="size-4" />
            )}
          </button>
          <button
            onClick={handleSend}
            disabled={(!inputValue.trim() && pendingAttachments.length === 0) || isSending}
            className="flex size-9 items-center justify-center rounded-lg bg-black text-white transition hover:bg-black/90 disabled:opacity-50"
            aria-label="Enviar mensagem"
          >
            {isSending ? (
              <Loader2 className="size-4 animate-spin" />
            ) : (
              <Send className="size-4" />
            )}
          </button>
        </div>
      </div>
    </div>
  )
}

function formatTime(timestamp: number): string {
  const date = new Date(timestamp)
  return date.toLocaleTimeString("pt-BR", {
    hour: "2-digit",
    minute: "2-digit",
  })
}
