"use client"

import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { formatDistanceToNow } from "date-fns"
import { ptBR } from "date-fns/locale"
import { IconMessage, IconFileText, IconPencil } from "@tabler/icons-react"
import { Download, FileCode, FileIcon, Image as ImageIcon, Trash2, X, ClipboardCopy } from "lucide-react"
import { useAction, useMutation, useQuery } from "convex/react"
import { api } from "@/convex/_generated/api"
import { useAuth } from "@/lib/auth-client"
import type { Id } from "@/convex/_generated/dataModel"
import type { TicketWithDetails } from "@/lib/schemas/ticket"
import type { Editor } from "@tiptap/react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { toast } from "sonner"
import { Dropzone } from "@/components/ui/dropzone"
import { Textarea } from "@/components/ui/textarea"
import {
  RichTextEditor,
  RichTextContent,
  sanitizeEditorHtml,
  stripLeadingEmptyParagraphs,
  normalizeTicketMentionHtml,
} from "@/components/ui/rich-text-editor"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { Spinner } from "@/components/ui/spinner"
import { cn } from "@/lib/utils"

interface TicketCommentsProps {
  ticket: TicketWithDetails
}

const badgeInternal = "rounded-full border border-slate-300 bg-neutral-900 px-2.5 py-0.5 text-xs font-semibold text-white"
const badgePublic = "rounded-full border border-[#00d6eb]/40 bg-[#00e8ff]/15 px-2.5 py-0.5 text-xs font-semibold text-neutral-900"
const selectTriggerClass = "h-8 w-[140px] rounded-lg border border-slate-300 bg-white px-3 text-left text-sm font-medium text-neutral-800 shadow-sm focus:ring-0 data-[state=open]:border-slate-400"
const submitButtonClass =
  "inline-flex items-center gap-2 rounded-lg border border-black bg-black px-3 py-2 text-sm font-semibold text-white transition hover:bg-[#18181b]/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#18181b]/30"

const COMMENT_ATTACHMENT_LIMIT = 10
const COMMENT_ATTACHMENT_MAX_FILE_SIZE = 5 * 1024 * 1024

type CommentsOrder = "descending" | "ascending"

// Detecta e parseia comentários automáticos de sistema
interface SystemCommentData {
  type: "assignee_change" | "status_change" | "priority_change" | "queue_change"
  field: string
  from: string
  to: string
  reason?: string
}

function parseSystemComment(html: string): SystemCommentData | null {
  // Padrões de comentários automáticos
  const patterns = [
    { regex: /<strong>Responsável atualizado:<\/strong>\s*([^→<]+)\s*→\s*([^<]+)/i, type: "assignee_change" as const, field: "Responsável" },
    { regex: /<strong>Status atualizado:<\/strong>\s*([^→<]+)\s*→\s*([^<]+)/i, type: "status_change" as const, field: "Status" },
    { regex: /<strong>Prioridade atualizada:<\/strong>\s*([^→<]+)\s*→\s*([^<]+)/i, type: "priority_change" as const, field: "Prioridade" },
    { regex: /<strong>Fila atualizada:<\/strong>\s*([^→<]+)\s*→\s*([^<]+)/i, type: "queue_change" as const, field: "Fila" },
  ]

  for (const pattern of patterns) {
    const match = html.match(pattern.regex)
    if (match) {
      // Extrair motivo se existir
      const reasonMatch = html.match(/<strong>Motivo da troca:<\/strong><\/p>\s*<p>([^<]+)/i)
      return {
        type: pattern.type,
        field: pattern.field,
        from: match[1].trim(),
        to: match[2].trim(),
        reason: reasonMatch ? reasonMatch[1].trim() : undefined,
      }
    }
  }

  return null
}

function SystemCommentContent({ data }: { data: SystemCommentData }) {
  return (
    <div className="space-y-2">
      <div className="flex flex-wrap items-center gap-2">
        <span className="text-sm font-medium text-neutral-600">{data.field}:</span>
        <span className="inline-flex items-center gap-1.5 rounded-md border border-slate-200 bg-slate-50 px-2 py-0.5 text-sm text-neutral-600">
          {data.from}
        </span>
        <span className="text-neutral-400">→</span>
        <span className="inline-flex items-center gap-1.5 rounded-md border border-neutral-300 bg-neutral-900/10 px-2 py-0.5 text-sm font-medium text-neutral-900">
          {data.to}
        </span>
      </div>
      {data.reason && (
        <p className="text-sm text-neutral-500">
          <span className="font-medium">Motivo:</span> {data.reason}
        </p>
      )}
    </div>
  )
}

export function TicketComments({ ticket }: TicketCommentsProps) {
  const { convexUserId, isStaff, role } = useAuth()
  const normalizedRole = role ?? null
  const canSeeInternalComments = normalizedRole === "admin" || normalizedRole === "agent"
  const hasAssignee = Boolean(ticket.assignee)
  const isRequester = convexUserId ? ticket.requester.id === convexUserId : false
  // Gestor pode comentar sem responsável; admin/agent só com responsável
  const isManager = normalizedRole === "manager"
  const canStaffComment = hasAssignee || isManager
  const canComment = isRequester || (isStaff && canStaffComment)
  const allowTicketMentions = normalizedRole === "admin" || normalizedRole === "agent"
  const addComment = useMutation(api.tickets.addComment)
  const removeAttachment = useMutation(api.tickets.removeCommentAttachment)
  const updateComment = useMutation(api.tickets.updateComment)
  const [body, setBody] = useState("")
  const [attachmentsToSend, setAttachmentsToSend] = useState<Array<{ storageId: string; name: string; size?: number; type?: string; previewUrl?: string }>>([])
  const attachmentsToSendTotalBytes = useMemo(
    () => attachmentsToSend.reduce((acc, item) => acc + (item.size ?? 0), 0),
    [attachmentsToSend]
  )
  const editorRef = useRef<Editor | null>(null)
  const dropzoneUploadHandlerRef = useRef<((files: File[]) => Promise<void>) | null>(null)
  const [preview, setPreview] = useState<string | null>(null)
  const [pending, setPending] = useState<Pick<TicketWithDetails["comments"][number], "id" | "author" | "visibility" | "body" | "attachments" | "createdAt" | "updatedAt">[]>([])
  const [visibility, setVisibility] = useState<"PUBLIC" | "INTERNAL">("INTERNAL")
  const [attachmentToRemove, setAttachmentToRemove] = useState<{ commentId: string; attachmentId: string; name: string } | null>(null)
  const [removingAttachment, setRemovingAttachment] = useState(false)
  const [editingComment, setEditingComment] = useState<{ id: string; value: string } | null>(null)
  const [savingCommentId, setSavingCommentId] = useState<string | null>(null)
  const [localBodies, setLocalBodies] = useState<Record<string, string>>({})
  const [commentsOrder, setCommentsOrder] = useState<CommentsOrder>("descending")
  const [markdownDialogOpen, setMarkdownDialogOpen] = useState(false)
  const [markdownDraft, setMarkdownDraft] = useState("")
  const [isEditorReady, setIsEditorReady] = useState(false)

  const templateArgs =
    convexUserId && isStaff
      ? { tenantId: ticket.tenantId, viewerId: convexUserId as Id<"users">, kind: "comment" as const }
      : "skip"
  const templatesResult = useQuery(api.commentTemplates.list, templateArgs)
  const templates = Array.isArray(templatesResult)
    ? (templatesResult as { id: string; title: string; body: string }[])
    : []
  const templatesLoading = Boolean(convexUserId && isStaff) && !Array.isArray(templatesResult)
  const canUseTemplates = Boolean(convexUserId && isStaff)

  const insertTemplateIntoBody = (html: string) => {
    const sanitizedTemplate = stripLeadingEmptyParagraphs(sanitizeEditorHtml(html))
    setBody((current) => {
      if (!sanitizedTemplate) return current
      if (!current) return sanitizedTemplate
      const merged = `${current}<p><br /></p>${sanitizedTemplate}`
      return stripLeadingEmptyParagraphs(sanitizeEditorHtml(merged))
    })
  }

  const handleEditorInstance = useCallback((instance: Editor | null) => {
    editorRef.current = instance
    setIsEditorReady(Boolean(instance))
  }, [setIsEditorReady])

  const handleEditorPasteFiles = useCallback(
    (files: File[]) => {
      if (!files.length) return
      if (!canComment) {
        toast.error("Você não tem permissão para anexar arquivos neste comentário.")
        return
      }
      const uploader = dropzoneUploadHandlerRef.current
      if (!uploader) {
        toast.error("Uploader de arquivos indisponível no momento.")
        return
      }
      const remainingSlots = Math.max(0, COMMENT_ATTACHMENT_LIMIT - attachmentsToSend.length)
      if (remainingSlots <= 0) {
        toast.warning(`Limite de ${COMMENT_ATTACHMENT_LIMIT} anexos atingido.`)
        return
      }
      const sizeLimitMb = Math.round(COMMENT_ATTACHMENT_MAX_FILE_SIZE / (1024 * 1024))
      const withinSizeLimit: File[] = []
      let oversized = 0
      for (const file of files) {
        if (file.size <= COMMENT_ATTACHMENT_MAX_FILE_SIZE) {
          withinSizeLimit.push(file)
        } else {
          oversized += 1
        }
      }
      if (oversized > 0) {
        toast.warning(`${oversized} imagem(ns) foram ignoradas por excederem ${sizeLimitMb}MB.`)
      }
      if (!withinSizeLimit.length) {
        return
      }
      const selected = withinSizeLimit.slice(0, remainingSlots)
      if (selected.length < withinSizeLimit.length) {
        toast.warning("Algumas imagens foram ignoradas para respeitar o limite de anexos.")
      }
      const toastId = "clipboard-attachments"
      toast.loading("Processando imagens coladas...", { id: toastId })
      void uploader(selected)
        .then(() => {
          toast.success("Imagens coladas adicionadas como anexos.", { id: toastId })
        })
        .catch((error) => {
          console.error("Failed to upload clipboard images", error)
          toast.error("Não foi possível anexar as imagens coladas.", { id: toastId })
        })
    },
    [attachmentsToSend.length, canComment]
  )

  const handleCopyMarkdown = useCallback(async () => {
    const editor = editorRef.current
    if (!editor) return
    const markdownStorage = (editor.storage as { markdown?: { getMarkdown?: () => string } } | undefined)?.markdown
    const markdown = markdownStorage?.getMarkdown?.()
    if (!markdown) {
      toast.warning("Não há conteúdo em Markdown para copiar.")
      return
    }
    if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
      toast.error("Copiar para a área de transferência não é suportado neste navegador.")
      return
    }
    try {
      await navigator.clipboard.writeText(markdown)
      toast.success("Markdown copiado para a área de transferência.")
    } catch (error) {
      console.error("Failed to copy markdown", error)
      toast.error("Não foi possível copiar o Markdown.")
    }
  }, [])

  const handleOpenMarkdownImport = useCallback(() => {
    const editor = editorRef.current
    const markdownStorage = (editor?.storage as { markdown?: { getMarkdown?: () => string } } | undefined)?.markdown
    const currentMarkdown = markdownStorage?.getMarkdown?.() ?? ""
    setMarkdownDraft(currentMarkdown)
    setMarkdownDialogOpen(true)
  }, [])

  const handleApplyMarkdownImport = useCallback(() => {
    const editor = editorRef.current
    if (!editor) return
    const draft = markdownDraft.trim()
    if (!draft) {
      editor.commands.clearContent(true)
      editor.commands.focus("end")
      setMarkdownDialogOpen(false)
      setMarkdownDraft("")
      toast.success("Editor limpo.")
      return
    }
    const toastId = "markdown-import"
    toast.loading("Convertendo Markdown...", { id: toastId })
    try {
      editor.commands.setContent(draft, { contentType: "markdown" })
      editor.commands.focus("end")
      toast.success("Markdown convertido em rich text.", { id: toastId })
    } catch (error) {
      console.error("Failed to import markdown", error)
      toast.error("Não foi possível converter o Markdown.", { id: toastId })
    } finally {
      setMarkdownDialogOpen(false)
      setMarkdownDraft("")
    }
  }, [markdownDraft])

  const startEditingComment = useCallback((commentId: string, currentBody: string) => {
    const normalized = normalizeTicketMentionHtml(currentBody || "")
    setEditingComment({ id: commentId, value: normalized })
  }, [])

  const cancelEditingComment = useCallback(() => {
    setEditingComment(null)
  }, [])

  const saveEditedComment = useCallback(
    async (commentId: string, originalBody: string) => {
      if (!editingComment || editingComment.id !== commentId) return
      if (!convexUserId) return
      if (commentId.startsWith("temp-")) return

      const sanitized = sanitizeEditorHtml(editingComment.value)
      const normalized = normalizeTicketMentionHtml(sanitized)
      if (normalized === originalBody) {
        setEditingComment(null)
        return
      }

      const toastId = `edit-comment-${commentId}`
      setSavingCommentId(commentId)
      toast.loading("Salvando comentário...", { id: toastId })
      try {
        await updateComment({
          ticketId: ticket.id as Id<"tickets">,
          commentId: commentId as unknown as Id<"ticketComments">,
          actorId: convexUserId as Id<"users">,
          body: normalized,
        })
        setLocalBodies((prev) => ({ ...prev, [commentId]: normalized }))
        setEditingComment(null)
        toast.success("Comentário atualizado!", { id: toastId })
      } catch (error) {
        console.error(error)
        toast.error("Não foi possível atualizar o comentário.", { id: toastId })
      } finally {
        setSavingCommentId(null)
      }
    },
    [editingComment, ticket.id, updateComment, convexUserId]
  )

  const commentsAll = useMemo(() => {
    const base = [...pending, ...ticket.comments]
    return base.sort((a, b) => {
      const aTime = new Date(a.createdAt).getTime()
      const bTime = new Date(b.createdAt).getTime()
      if (commentsOrder === "ascending") {
        return aTime - bTime
      }
      return bTime - aTime
    })
  }, [pending, ticket.comments, commentsOrder])

  async function handleSubmit(event: React.FormEvent) {
    event.preventDefault()
    if (!convexUserId) return
    // Enforce generous max length for comment plain text
    const sanitized = sanitizeEditorHtml(body)
    const normalized = normalizeTicketMentionHtml(sanitized)
    const plain = normalized.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim()
    const MAX_COMMENT_CHARS = 20000
    if (plain.length > MAX_COMMENT_CHARS) {
      toast.error(`Comentário muito longo (máx. ${MAX_COMMENT_CHARS} caracteres)`, { id: "comment" })
      return
    }
    const now = new Date()
    const selectedVisibility = canSeeInternalComments ? visibility : "PUBLIC"
    const attachments = attachmentsToSend.map((item) => ({ ...item }))
    const previewsToRevoke = attachments
      .map((attachment) => attachment.previewUrl)
      .filter((previewUrl): previewUrl is string => Boolean(previewUrl && previewUrl.startsWith("blob:")))
    const optimistic = {
      id: `temp-${now.getTime()}`,
      author: ticket.requester,
      visibility: selectedVisibility,
      body: normalized,
      attachments: attachments.map((attachment) => ({
        id: attachment.storageId,
        name: attachment.name,
        type: attachment.type,
        url: attachment.previewUrl,
      })),
      createdAt: now,
      updatedAt: now,
    }

    setPending((current) => [optimistic, ...current])
    setBody("")
    setAttachmentsToSend([])
    toast.loading("Enviando comentário...", { id: "comment" })

    try {
      const payload = attachments.map((attachment) => ({
        storageId: attachment.storageId as unknown as Id<"_storage">,
        name: attachment.name,
        size: attachment.size,
        type: attachment.type,
      }))
      await addComment({
        ticketId: ticket.id as Id<"tickets">,
        authorId: convexUserId as Id<"users">,
        visibility: selectedVisibility,
        body: normalized,
        attachments: payload,
      })
      setPending([])
      toast.success("Comentário enviado!", { id: "comment" })
    } catch {
      setPending([])
      toast.error("Falha ao enviar comentário.", { id: "comment" })
    }
    previewsToRevoke.forEach((previewUrl) => {
      try {
        URL.revokeObjectURL(previewUrl)
      } catch (error) {
        console.error("Failed to revoke preview URL", error)
      }
    })
  }

  async function handleRemoveAttachment() {
    if (!attachmentToRemove || !convexUserId) return
    setRemovingAttachment(true)
    toast.loading("Removendo anexo...", { id: "remove-attachment" })
    try {
      await removeAttachment({
        ticketId: ticket.id as unknown as Id<"tickets">,
        commentId: attachmentToRemove.commentId as Id<"ticketComments">,
        attachmentId: attachmentToRemove.attachmentId as Id<"_storage">,
        actorId: convexUserId as Id<"users">,
      })
      toast.success("Anexo removido.", { id: "remove-attachment" })
      setAttachmentToRemove(null)
    } catch (error) {
      console.error(error)
      toast.error("Não foi possível remover o anexo.", { id: "remove-attachment" })
    } finally {
      setRemovingAttachment(false)
    }
  }

  return (
    <Card className="rounded-2xl border border-slate-200 bg-white shadow-sm">
      <CardHeader className="px-4 pb-3">
        <CardTitle className="flex items-center gap-2 text-lg font-semibold text-neutral-900">
          <IconMessage className="size-5 text-neutral-900" /> Comentários
          <Badge className="inline-flex h-7 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
            {commentsAll.length} {commentsAll.length === 1 ? "comentário" : "comentários"}
          </Badge>
        </CardTitle>
      </CardHeader>
      <CardContent className="space-y-6 px-4 pb-6">
        <div className="flex flex-wrap items-center justify-between gap-2">
          <Button
            type="button"
            variant="ghost"
            size="sm"
            className="flex items-center gap-2 text-sm text-neutral-600 hover:text-neutral-900"
            onClick={() => setCommentsOrder((prev) => (prev === "descending" ? "ascending" : "descending"))}
          >
            <svg
              aria-hidden="true"
              viewBox="0 0 24 24"
              className="size-4"
              focusable="false"
            >
              <path
                d="M7 18.5v-13M7 5.5L4 8.5M7 5.5l3 3M17 5.5v13M17 18.5l-3-3M17 18.5l3-3"
                fill="none"
                stroke="currentColor"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth={1.5}
              />
            </svg>
            {commentsOrder === "descending" ? "Mais recentes primeiro" : "Mais antigos primeiro"}
          </Button>
        </div>
        {commentsAll.length === 0 ? (
          <Empty>
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <IconMessage className="size-5 text-neutral-900" />
              </EmptyMedia>
              <EmptyTitle className="text-neutral-900">Nenhum comentário ainda</EmptyTitle>
              <EmptyDescription className="text-neutral-600">Registre o próximo passo abaixo.</EmptyDescription>
            </EmptyHeader>
          </Empty>
        ) : (
          commentsAll.map((comment) => {
            const initials = comment.author.name
              .split(" ")
              .slice(0, 2)
              .map((part) => part[0]?.toUpperCase())
              .join("")
            const commentId = String(comment.id)
            const storedBody = localBodies[commentId] ?? comment.body ?? ""
            const bodyPlain = storedBody.replace(/<[^>]*>/g, "").trim()
            const isEditing = editingComment?.id === commentId
            const isPending = commentId.startsWith("temp-")
            const canEdit = Boolean(convexUserId && String(comment.author.id) === convexUserId && !isPending)
            const hasBody = bodyPlain.length > 0 || isEditing
            const isInternal = comment.visibility === "INTERNAL" && canSeeInternalComments
            const isPublic = comment.visibility === "PUBLIC"

            return (
              <div
                key={comment.id}
                className={cn(
                  "group/comment overflow-hidden rounded-2xl border",
                  isPublic
                    ? "border-[#00d6eb]/30 bg-[#00e8ff]/[0.08]"
                    : "border-slate-200 bg-white"
                )}
              >
                {/* Header */}
                <div className={cn(
                  "flex items-center justify-between gap-3 border-b px-4 py-3",
                  isPublic ? "border-[#00d6eb]/20" : "border-slate-100"
                )}>
                  <div className="flex items-center gap-3">
                    <Avatar className="size-10 border border-slate-200">
                      <AvatarImage src={comment.author.avatarUrl} alt={comment.author.name} />
                      <AvatarFallback className="bg-slate-100 text-sm font-medium text-neutral-700">{initials}</AvatarFallback>
                    </Avatar>
                    <div>
                      <div className="flex items-center gap-2">
                        <span className="text-sm font-semibold text-neutral-900">{comment.author.name}</span>
                        {isInternal ? (
                          <Badge className={badgeInternal}>Interno</Badge>
                        ) : isPublic ? (
                          <Badge className={badgePublic}>Público</Badge>
                        ) : null}
                      </div>
                      <span className="text-xs text-neutral-500">
                        {formatDistanceToNow(comment.createdAt, { addSuffix: true, locale: ptBR })}
                      </span>
                    </div>
                  </div>
                  {canEdit && !isEditing ? (
                    <button
                      type="button"
                      onClick={() => startEditingComment(commentId, storedBody)}
                      className="inline-flex size-8 items-center justify-center rounded-lg border border-slate-200 bg-white text-neutral-600 opacity-0 transition hover:bg-slate-50 hover:text-neutral-900 group-hover/comment:opacity-100"
                      aria-label="Editar comentário"
                    >
                      <IconPencil className="size-4" />
                    </button>
                  ) : null}
                </div>

                {/* Conteúdo */}
                <div className="px-4 py-3">
                  {isEditing ? (
                    <div className="space-y-3">
                      <RichTextEditor
                        value={editingComment?.value ?? ""}
                        onChange={(next) =>
                          setEditingComment((prev) => (prev && prev.id === commentId ? { ...prev, value: next } : prev))
                        }
                        disabled={savingCommentId === commentId}
                        placeholder="Edite o comentário..."
                        ticketMention={{ enabled: allowTicketMentions }}
                        className="rounded-xl border border-slate-200"
                      />
                      <div className="flex items-center justify-end gap-2">
                        <Button
                          type="button"
                          variant="outline"
                          size="sm"
                          className="rounded-lg border border-slate-200 text-sm font-medium text-neutral-700 hover:bg-slate-100"
                          onClick={cancelEditingComment}
                          disabled={savingCommentId === commentId}
                        >
                          Cancelar
                        </Button>
                        <Button
                          type="button"
                          size="sm"
                          className={submitButtonClass}
                          onClick={() => saveEditedComment(commentId, storedBody)}
                          disabled={savingCommentId === commentId}
                        >
                          {savingCommentId === commentId ? <Spinner className="size-4 text-white" /> : "Salvar"}
                        </Button>
                      </div>
                    </div>
                  ) : hasBody ? (
                    (() => {
                      const systemData = parseSystemComment(storedBody)
                      if (systemData) {
                        return <SystemCommentContent data={systemData} />
                      }
                      return <RichTextContent html={storedBody} className="text-sm leading-relaxed text-neutral-700" />
                    })()
                  ) : canEdit ? (
                    <button
                      type="button"
                      onClick={() => startEditingComment(commentId, storedBody)}
                      className="flex w-full items-center gap-2 rounded-xl border border-dashed border-slate-200 bg-slate-50/50 px-3 py-2 text-sm text-neutral-500 transition hover:border-slate-300 hover:text-neutral-700"
                    >
                      <IconPencil className="size-4" />
                      Adicionar conteúdo ao comentário
                    </button>
                  ) : null}

                  {/* Anexos */}
                  {comment.attachments?.length ? (
                    <div className="mt-4 grid max-w-xl grid-cols-[repeat(auto-fill,minmax(96px,1fr))] gap-3">
                      {comment.attachments.map((attachment) => (
                        <CommentAttachmentCard
                          key={attachment.id}
                          attachment={attachment}
                          onOpenPreview={(url) => setPreview(url)}
                          onRequestRemoval={() =>
                            setAttachmentToRemove({ commentId: comment.id, attachmentId: attachment.id, name: attachment.name })
                          }
                        />
                      ))}
                    </div>
                  ) : null}
                </div>
              </div>
            )
          })
        )}
        {!canComment && (
          <div className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm text-neutral-600">
            Atribua um responsável ao chamado para que a equipe técnica possa comentar.
          </div>
        )}
        <form onSubmit={handleSubmit} className="mt-4 space-y-3">
          <RichTextEditor
            value={body}
            onChange={setBody}
            placeholder="Escreva um comentário..."
            className="rounded-2xl border border-slate-200"
            disabled={!canComment}
            ticketMention={{ enabled: allowTicketMentions }}
            onEditorReady={handleEditorInstance}
            onPasteFiles={handleEditorPasteFiles}
          />
          <Dropzone
            onUploaded={(files) => setAttachmentsToSend((prev) => [...prev, ...files])}
            currentFileCount={attachmentsToSend.length}
            currentTotalBytes={attachmentsToSendTotalBytes}
            disabled={!canComment}
            maxFiles={COMMENT_ATTACHMENT_LIMIT}
            onRegisterUploadHandler={(handler) => {
              dropzoneUploadHandlerRef.current = handler ?? null
            }}
          />
          {attachmentsToSend.length > 0 ? (
            <div className="grid max-w-xl grid-cols-[repeat(auto-fill,minmax(96px,1fr))] gap-3">
              {attachmentsToSend.map((attachment, index) => {
                const name = attachment.name
                const previewUrl = attachment.previewUrl
                const isImage =
                  (attachment.type ?? "").startsWith("image/") ||
                  /\.(png|jpe?g|gif|webp|svg)$/i.test(name)
                return (
                  <div key={`${attachment.storageId}-${index}`} className="group relative overflow-hidden rounded-lg border border-slate-200 bg-white p-0.5">
                    {isImage && previewUrl ? (
                      <button
                        type="button"
                        onClick={() => setPreview(previewUrl || null)}
                        className="block w-full overflow-hidden rounded-md"
                      >
                        {/* eslint-disable-next-line @next/next/no-img-element */}
                        <img src={previewUrl} alt={name} className="h-24 w-full rounded-md object-cover" />
                      </button>
                    ) : (
                      <div className="flex h-24 w-full flex-col items-center justify-center gap-2 rounded-md bg-slate-50 text-xs text-neutral-600">
                        <FileIcon className="size-4" />
                        <span className="line-clamp-2 px-2 text-center">{name}</span>
                      </div>
                    )}
                    <button
                      type="button"
                      onClick={() =>
                        setAttachmentsToSend((prev) => {
                          const next = [...prev]
                          const removed = next.splice(index, 1)[0]
                          if (removed?.previewUrl?.startsWith("blob:")) {
                            URL.revokeObjectURL(removed.previewUrl)
                          }
                          return next
                        })
                      }
                      className="absolute right-1.5 top-1.5 inline-flex size-7 items-center justify-center rounded-full border border-black bg-black text-white transition hover:bg-[#18181b]/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/30"
                      aria-label={`Remover ${name}`}
                    >
                      <X className="size-3.5" />
                    </button>
                    <div className="mt-1 line-clamp-1 w-full text-ellipsis text-center text-[11px] text-neutral-500">
                      {name}
                    </div>
                  </div>
                )
              })}
            </div>
          ) : null}
          <div className="flex flex-wrap items-center justify-between gap-3">
            <div className="flex flex-wrap items-center gap-3 text-xs text-neutral-600">
              {canUseTemplates ? (
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <Button
                      type="button"
                      variant="outline"
                      size="sm"
                      className="inline-flex items-center gap-2 border-slate-200 text-sm text-neutral-700 hover:bg-slate-50"
                      disabled={templatesLoading}
                    >
                      <IconFileText className="size-4" />
                      Inserir template
                    </Button>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent align="start" className="w-72">
                    {templatesLoading ? (
                      <div className="flex items-center gap-2 px-3 py-2 text-sm text-neutral-500">
                        <Spinner className="size-4" />
                        Carregando templates...
                      </div>
                    ) : templates.length === 0 ? (
                      <div className="px-3 py-2 text-sm text-neutral-500">
                        Nenhum template disponível. Cadastre novos em configurações.
                      </div>
                    ) : (
                      templates.map((template) => (
                        <DropdownMenuItem
                          key={template.id}
                          className="flex flex-col items-start whitespace-normal py-2"
                          onSelect={() => insertTemplateIntoBody(template.body)}
                        >
                          <span className="text-sm font-medium text-neutral-800">{template.title}</span>
                        </DropdownMenuItem>
                      ))
                    )}
                  </DropdownMenuContent>
                </DropdownMenu>
              ) : null}
              <Button
                type="button"
                variant="outline"
                size="sm"
                className="inline-flex items-center gap-2 border-slate-200 text-sm text-neutral-700 hover:bg-slate-50"
                onClick={handleOpenMarkdownImport}
                disabled={!canComment || !isEditorReady}
              >
                <FileCode className="size-4" />
                Colar Markdown
              </Button>
              <Button
                type="button"
                variant="outline"
                size="sm"
                className="inline-flex items-center gap-2 border-slate-200 text-sm text-neutral-700 hover:bg-slate-50"
                onClick={handleCopyMarkdown}
                disabled={!isEditorReady}
              >
                <ClipboardCopy className="size-4" />
                Copiar Markdown
              </Button>
              <div className="flex items-center gap-2">
                Visibilidade:
                <Select
                  value={visibility}
                  onValueChange={(value) => {
                    if (!canSeeInternalComments) return
                    setVisibility(value as "PUBLIC" | "INTERNAL")
                  }}
                  disabled={!canSeeInternalComments}
                >
                  <SelectTrigger className={selectTriggerClass}>
                    <SelectValue placeholder="Visibilidade" />
                  </SelectTrigger>
                  <SelectContent className="rounded-lg border border-slate-200 bg-white text-neutral-800 shadow-sm">
                    <SelectItem value="PUBLIC">Pública</SelectItem>
                    {canSeeInternalComments ? <SelectItem value="INTERNAL">Interna</SelectItem> : null}
                  </SelectContent>
                </Select>
              </div>
            </div>
            <Button type="submit" size="sm" className={submitButtonClass} disabled={!canComment}>
              Enviar
            </Button>
          </div>
        </form>
        <Dialog
          open={markdownDialogOpen}
          onOpenChange={(open) => {
            setMarkdownDialogOpen(open)
            if (!open) {
              setMarkdownDraft("")
            }
          }}
        >
          <DialogContent className="sm:max-w-xl space-y-4">
            <DialogHeader>
              <DialogTitle>Colar Markdown</DialogTitle>
              <DialogDescription>
                Cole conteúdo em Markdown para convertê-lo automaticamente em rich text. Isso substitui o texto atual do editor.
              </DialogDescription>
            </DialogHeader>
            <Textarea
              value={markdownDraft}
              onChange={(event) => setMarkdownDraft(event.target.value)}
              rows={10}
              placeholder="Cole aqui o texto em Markdown..."
              className="min-h-[200px]"
            />
            <div className="flex justify-end gap-2">
              <Button
                type="button"
                variant="outline"
                onClick={() => {
                  setMarkdownDialogOpen(false)
                  setMarkdownDraft("")
                }}
              >
                Cancelar
              </Button>
              <Button type="button" onClick={handleApplyMarkdownImport} disabled={!markdownDraft.trim()}>
                Converter
              </Button>
            </div>
          </DialogContent>
        </Dialog>
        <Dialog open={!!attachmentToRemove} onOpenChange={(open) => { if (!open && !removingAttachment) setAttachmentToRemove(null) }}>
          <DialogContent className="max-w-sm space-y-4">
            <DialogHeader>
              <DialogTitle>Remover anexo</DialogTitle>
              <DialogDescription>
                Tem certeza de que deseja remover "{attachmentToRemove?.name}" deste comentário?
              </DialogDescription>
            </DialogHeader>
            <div className="flex justify-end gap-2">
              <Button
                type="button"
                variant="outline"
                onClick={() => setAttachmentToRemove(null)}
                className="rounded-lg border border-slate-200 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-slate-100"
                disabled={removingAttachment}
              >
                Cancelar
              </Button>
              <Button
                type="button"
                onClick={handleRemoveAttachment}
                disabled={removingAttachment}
                className="inline-flex items-center gap-2 rounded-lg border border-black bg-black px-4 py-2 text-sm font-semibold text-white transition hover:bg-[#18181b]/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#18181b]/30 disabled:opacity-60"
              >
                <Trash2 className="size-4" />
                {removingAttachment ? "Removendo..." : "Excluir"}
              </Button>
            </div>
          </DialogContent>
        </Dialog>
        <Dialog open={!!preview} onOpenChange={(open) => !open && setPreview(null)}>
          <DialogContent className="max-w-3xl border border-slate-200 p-0">
            <DialogHeader className="relative px-4 py-3">
              <DialogTitle className="pr-10 text-base font-semibold text-neutral-800">Visualização do anexo</DialogTitle>
              <DialogClose className="absolute right-4 top-3 inline-flex size-7 items-center justify-center rounded-full border border-slate-200 bg-white text-neutral-600 transition hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400/30">
                <X className="size-4" />
              </DialogClose>
            </DialogHeader>
            {preview ? (
              <div className="rounded-b-2xl">
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img src={preview} alt="Preview do anexo" className="h-auto w-full rounded-b-2xl" />
              </div>
            ) : null}
          </DialogContent>
        </Dialog>
      </CardContent>
    </Card>
  )
}

type CommentAttachment = TicketWithDetails["comments"][number]["attachments"][number]

function CommentAttachmentCard({
  attachment,
  onOpenPreview,
  onRequestRemoval,
}: {
  attachment: CommentAttachment
  onOpenPreview: (url: string) => void
  onRequestRemoval: () => void
}) {
  const getFileUrl = useAction(api.files.getUrl)
  const [url, setUrl] = useState<string | null>(attachment.url ?? null)
  const [refreshing, setRefreshing] = useState(false)
  const [errored, setErrored] = useState(false)
  const hasRefreshedRef = useRef(false)
  const isImageType = useMemo(() => {
    const name = attachment.name ?? ""
    const type = attachment.type ?? ""
    return (!!type && type.startsWith("image/")) || /\.(png|jpe?g|gif|webp|svg)$/i.test(name)
  }, [attachment.name, attachment.type])

  useEffect(() => {
    setUrl(attachment.url ?? null)
    setErrored(false)
    hasRefreshedRef.current = false
  }, [attachment.id, attachment.url])

  const ensureUrl = useCallback(async () => {
    try {
      setRefreshing(true)
      const fresh = await getFileUrl({ storageId: attachment.id as Id<"_storage"> })
      if (fresh) {
        setUrl(fresh)
        setErrored(false)
        return fresh
      }
    } catch (error) {
      console.error("Failed to refresh attachment URL", error)
    } finally {
      setRefreshing(false)
    }
    return null
  }, [attachment.id, getFileUrl])

  useEffect(() => {
    let cancelled = false
    ;(async () => {
      const fresh = await ensureUrl()
      if (!cancelled && fresh) {
        setUrl(fresh)
      }
    })()
    return () => {
      cancelled = true
    }
  }, [ensureUrl])

  const handleImageError = useCallback(async () => {
    if (hasRefreshedRef.current) {
      setErrored(true)
      return
    }
    hasRefreshedRef.current = true
    const fresh = await ensureUrl()
    if (!fresh) {
      setErrored(true)
    }
  }, [ensureUrl])

  const handlePreview = useCallback(async () => {
    const target = url ?? (await ensureUrl())
    if (target) {
      onOpenPreview(target)
    }
  }, [ensureUrl, onOpenPreview, url])

  const handleDownload = useCallback(async () => {
    const target = url ?? (await ensureUrl())
    if (!target) return
    const toastId = `attachment-download-${attachment.id}`
    toast.loading("Baixando anexo...", { id: toastId })
    try {
      const response = await fetch(target, { credentials: "include" })
      if (!response.ok) {
        throw new Error(`Unexpected status ${response.status}`)
      }
      const blob = await response.blob()
      const blobUrl = window.URL.createObjectURL(blob)
      const link = document.createElement("a")
      link.href = blobUrl
      link.download = attachment.name ?? "anexo"
      link.rel = "noopener noreferrer"
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      window.setTimeout(() => {
        window.URL.revokeObjectURL(blobUrl)
      }, 1000)
      toast.success("Download concluído!", { id: toastId })
    } catch (error) {
      console.error("Failed to download attachment", error)
      window.open(target, "_blank", "noopener,noreferrer")
      toast.error("Não foi possível baixar o anexo automaticamente.", { id: toastId })
    }
  }, [attachment.id, attachment.name, ensureUrl, url])

  const name = attachment.name ?? ""
  const urlLooksImage = url ? /\.(png|jpe?g|gif|webp|svg)$/i.test(url) : false
  const showImage = isImageType && url && !errored

  return (
    <div className="group relative overflow-hidden rounded-lg border border-slate-200 bg-white p-0.5 shadow-sm">
      {showImage ? (
        <button type="button" onClick={handlePreview} className="relative block w-full overflow-hidden rounded-md">
          {refreshing ? (
            <div className="absolute inset-0 flex items-center justify-center bg-white/70">
              <Spinner className="size-5 text-neutral-600" />
            </div>
          ) : null}
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img
            key={url ?? "no-url"}
            src={url ?? undefined}
            alt={name}
            className="h-24 w-full rounded-md object-cover"
            onError={handleImageError}
          />
        </button>
      ) : (
        <button
          type="button"
          onClick={isImageType || urlLooksImage ? handlePreview : handleDownload}
          className="flex h-24 w-full flex-col items-center justify-center gap-2 rounded-md bg-slate-50 text-xs text-neutral-700 transition hover:bg-slate-100"
          disabled={refreshing}
        >
          {refreshing ? (
            <Spinner className="size-5 text-neutral-600" />
          ) : isImageType || urlLooksImage ? (
            <ImageIcon className="size-5 text-neutral-600" />
          ) : (
            <FileIcon className="size-5 text-neutral-600" />
          )}
          <span className="font-medium">
            {errored ? "Não foi possível carregar" : refreshing ? "Gerando link..." : url ? "Abrir" : "Gerar link"}
          </span>
        </button>
      )}
      <button
        type="button"
        onClick={handleDownload}
        aria-label={`Baixar ${name}`}
        className="absolute left-1.5 top-1.5 inline-flex size-7 items-center justify-center rounded-full border border-slate-300 bg-white text-neutral-700 opacity-0 transition hover:border-black hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/20 focus-visible:opacity-100 group-hover:opacity-100"
      >
        <Download className="size-3.5" />
      </button>
      <button
        type="button"
        onClick={onRequestRemoval}
        aria-label={`Remover ${name}`}
        className="absolute right-1.5 top-1.5 inline-flex size-7 items-center justify-center rounded-full border border-black bg-black text-white opacity-0 transition hover:bg-[#18181b]/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/30 focus-visible:opacity-100 group-hover:opacity-100"
      >
        <X className="size-3.5" />
      </button>
      <div className="mt-1 line-clamp-1 w-full text-ellipsis text-center text-[11px] text-neutral-500">{name}</div>
    </div>
  )
}
