"use client"

import {
  forwardRef,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react"
import type { ReactNode } from "react"
import { useEditor, EditorContent } from "@tiptap/react"
import type { Editor } from "@tiptap/react"
import StarterKit from "@tiptap/starter-kit"
import Placeholder from "@tiptap/extension-placeholder"
import Mention from "@tiptap/extension-mention"
import TiptapLink from "@tiptap/extension-link"
import { Markdown } from "@tiptap/markdown"
import { ReactRenderer } from "@tiptap/react"
import tippy, { type Instance, type Props as TippyProps } from "tippy.js"
// Nota: o CSS do Tippy não é obrigatório, mas melhora muito a renderização
// do popover de sugestões. O componente aplica um z-index alto e estratégia
// "fixed" para evitar problemas de sobreposição/scrolling mesmo sem CSS global.
import { cn } from "@/lib/utils"
import sanitize from "sanitize-html"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { Input } from "@/components/ui/input"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { getTicketStatusDotClass, getTicketStatusLabel } from "@/lib/ticket-status-style"
import {
  Bold,
  Italic,
  Strikethrough,
  List,
  ListOrdered,
  Quote,
  Undo,
  Redo,
  Link as LinkIcon,
  Check,
  Link2Off,
} from "lucide-react"

type RichTextEditorProps = {
  value?: string
  onChange?: (html: string) => void
  className?: string
  placeholder?: string
  disabled?: boolean
  minHeight?: number
  ticketMention?: {
    enabled?: boolean
  }
  onEditorReady?: (editor: Editor | null) => void
  onPasteFiles?: (files: File[]) => void
}

type TicketMentionItem = {
  id: string
  reference: number
  subject: string
  status: string
  priority: string
  requesterName: string | null
  assigneeName: string | null
  companyName: string | null
  url: string
  updatedAt: string
}

type TicketMentionSuggestionProps = {
  command: (item: TicketMentionItem) => void
  items: TicketMentionItem[]
  onRegister?: (handler: ((event: KeyboardEvent) => boolean) | null) => void
}

const TICKET_MENTION_BASE_CLASSES =
  "ticket-mention inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-100 px-2.5 py-1 text-xs font-semibold text-neutral-800 no-underline transition hover:bg-slate-200"
const TICKET_MENTION_DOT_BASE_CLASSES = "ticket-mention-dot inline-flex size-2 rounded-full bg-slate-400"
const TICKET_MENTION_REF_CLASSES = "ticket-mention-ref text-neutral-900"
const TICKET_MENTION_SEP_CLASSES = "ticket-mention-sep text-neutral-400"
const TICKET_MENTION_SUBJECT_CLASSES = "ticket-mention-subject max-w-[220px] truncate text-neutral-700"

function formatMentionSubject(subject: string) {
  if (!subject) return ""
  return subject.length > 60 ? `${subject.slice(0, 57)}…` : subject
}

function extractTicketIdFromHref(href: string | null | undefined): string {
  if (!href) return ""
  try {
    const normalized = href.split("?")[0]
    const segments = normalized.split("/").filter(Boolean)
    return segments.pop() ?? ""
  } catch {
    return ""
  }
}

function parseTicketMentionText(text: string | null | undefined) {
  const fallback = { reference: "", subject: "" }
  if (!text) return fallback
  const trimmed = text.replace(/\s+/g, " ").trim()
  if (!trimmed) return fallback
  const bulletIndex = trimmed.indexOf("•")
  if (bulletIndex === -1) {
    const hashIndex = trimmed.indexOf("#")
    const ref = hashIndex === -1 ? trimmed : trimmed.slice(hashIndex + 1)
    return { reference: ref.trim(), subject: "" }
  }
  const referencePart = trimmed.slice(0, bulletIndex)
  const subjectPart = trimmed.slice(bulletIndex + 1)
  const ref = referencePart.replace(/^#/, "").trim()
  return { reference: ref, subject: subjectPart.trim() }
}

type TicketMentionAttributes = Record<string, unknown>

const TICKET_MENTION_FALLBACK_STATUS = "PENDING"
const TICKET_MENTION_FALLBACK_PRIORITY = "MEDIUM"

const MAX_PASTED_IMAGE_FILES = 10

function extractImageFilesFromClipboard(event: ClipboardEvent): File[] {
  const clipboard = event.clipboardData
  if (!clipboard) return []

  const filesFromItems = Array.from(clipboard.items ?? [])
    .filter((item) => item.kind === "file")
    .map((item) => item.getAsFile())
    .filter((file): file is File => Boolean(file && file.type.startsWith("image/")))

  if (filesFromItems.length > 0) {
    return filesFromItems
  }

  return Array.from(clipboard.files ?? []).filter((file) => file.type.startsWith("image/"))
}

function toPlainString(value: unknown): string {
  if (value === null || value === undefined) return ""
  return String(value)
}

function normalizeTicketMentionAttributes(attrs: TicketMentionAttributes) {
  const id = toPlainString(attrs.id ?? attrs["data-ticket-id"])
  const referenceRaw = attrs.reference ?? attrs["data-ticket-reference"] ?? attrs.id ?? ""
  const reference = toPlainString(referenceRaw)
  const subject = toPlainString(attrs.subject ?? attrs["data-ticket-subject"])
  const statusRaw = toPlainString(attrs.status ?? attrs["data-ticket-status"] ?? TICKET_MENTION_FALLBACK_STATUS)
  const status = statusRaw ? statusRaw.toUpperCase() : TICKET_MENTION_FALLBACK_STATUS
  const priorityRaw = toPlainString(attrs.priority ?? attrs["data-ticket-priority"] ?? TICKET_MENTION_FALLBACK_PRIORITY)
  const priority = priorityRaw ? priorityRaw.toUpperCase() : TICKET_MENTION_FALLBACK_PRIORITY
  const url = toPlainString(attrs.url ?? attrs["data-ticket-url"] ?? attrs.href ?? "")
  return { id, reference, subject, status, priority, url }
}

type TicketMentionNodeElements = {
  root: HTMLAnchorElement
  dotEl: HTMLSpanElement
  referenceEl: HTMLSpanElement
  subjectEl: HTMLSpanElement
  separatorEl: HTMLSpanElement
}

function updateTicketMentionNodeElements(elements: TicketMentionNodeElements, attrs: TicketMentionAttributes) {
  const { root, dotEl, referenceEl, subjectEl, separatorEl } = elements
  const normalized = normalizeTicketMentionAttributes(attrs)
  const referenceLabel = normalized.reference || normalized.id
  const displayedReference = referenceLabel ? `#${referenceLabel}` : "#"
  const formattedSubject = normalized.subject ? formatMentionSubject(normalized.subject) : ""
  const additionalClass = toPlainString(attrs.class ?? attrs.className)
  const dotClass = getTicketStatusDotClass(normalized.status)

  root.className = cn(TICKET_MENTION_BASE_CLASSES, additionalClass)
  root.dataset.ticketMention = "true"
  dotEl.className = cn(TICKET_MENTION_DOT_BASE_CLASSES, dotClass)
  referenceEl.className = TICKET_MENTION_REF_CLASSES
  subjectEl.className = TICKET_MENTION_SUBJECT_CLASSES
  separatorEl.className = TICKET_MENTION_SEP_CLASSES

  referenceEl.textContent = displayedReference
  subjectEl.textContent = formattedSubject
  separatorEl.style.display = formattedSubject ? "" : "none"

  if (normalized.id) {
    root.dataset.ticketId = normalized.id
  } else {
    delete root.dataset.ticketId
  }
  if (normalized.reference) {
    root.dataset.ticketReference = normalized.reference
  } else {
    delete root.dataset.ticketReference
  }
  if (normalized.status) {
    root.dataset.ticketStatus = normalized.status
    root.setAttribute("status", normalized.status)
  } else {
    root.dataset.ticketStatus = TICKET_MENTION_FALLBACK_STATUS
    root.setAttribute("status", TICKET_MENTION_FALLBACK_STATUS)
  }
  if (normalized.priority) {
    root.dataset.ticketPriority = normalized.priority
  } else {
    delete root.dataset.ticketPriority
  }
  if (normalized.subject) {
    root.dataset.ticketSubject = normalized.subject
  } else {
    delete root.dataset.ticketSubject
  }
  if (normalized.url) {
    root.dataset.ticketUrl = normalized.url
  } else {
    delete root.dataset.ticketUrl
  }

  if (normalized.url) {
    root.href = normalized.url
    root.rel = "noopener noreferrer"
    root.target = "_self"
  } else {
    root.removeAttribute("href")
  }

  root.title = formattedSubject ? `${displayedReference} • ${formattedSubject}` : displayedReference
}

function escapeHtml(value: string): string {
  return value
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;")
}

function buildTicketMentionAnchorHtml(attrs: {
  id: string
  reference: string
  subject: string
  status: string
  priority: string
  url: string
}) {
  const id = attrs.id ?? ""
  const reference = attrs.reference ?? ""
  const subject = attrs.subject ?? ""
  const status = (attrs.status || TICKET_MENTION_FALLBACK_STATUS).toUpperCase()
  const priority = (attrs.priority || TICKET_MENTION_FALLBACK_PRIORITY).toUpperCase()
  const url = attrs.url ?? ""
  const displayedReference = reference || id
  const formattedSubject = subject ? formatMentionSubject(subject) : ""
  const dotClass = `${TICKET_MENTION_DOT_BASE_CLASSES} ${getTicketStatusDotClass(status)}`
  const title = formattedSubject ? `#${displayedReference} • ${formattedSubject}` : `#${displayedReference}`

  return `<a data-ticket-mention="true" data-ticket-id="${escapeHtml(id)}" data-ticket-reference="${escapeHtml(reference)}" data-ticket-status="${escapeHtml(status)}" data-ticket-priority="${escapeHtml(priority)}" data-ticket-subject="${escapeHtml(subject)}" status="${escapeHtml(status)}" href="${escapeHtml(url)}" class="${escapeHtml(TICKET_MENTION_BASE_CLASSES)}" rel="noopener noreferrer" target="_self" title="${escapeHtml(title)}"><span class="${escapeHtml(dotClass)}"></span><span class="${escapeHtml(TICKET_MENTION_REF_CLASSES)}">#${escapeHtml(displayedReference)}</span><span class="${escapeHtml(TICKET_MENTION_SEP_CLASSES)}">•</span><span class="${escapeHtml(TICKET_MENTION_SUBJECT_CLASSES)}">${escapeHtml(formattedSubject)}</span></a>`
}

export function normalizeTicketMentionHtml(html: string): string {
  if (!html || html.indexOf("ticket-mention") === -1) {
    return html
  }
  const mentionPattern = /<a\b[^>]*class="[^"]*ticket-mention[^"]*"[^>]*>[\s\S]*?<\/a>/gi
  const attributePattern = /(data-[\w-]+|href|status)="([^"]*)"/gi

  return html.replace(mentionPattern, (match) => {
    const attributes: Record<string, string> = {}
    let attrMatch: RegExpExecArray | null
    attributePattern.lastIndex = 0
    while ((attrMatch = attributePattern.exec(match)) !== null) {
      attributes[attrMatch[1]] = attrMatch[2]
    }

    const href = attributes.href ?? ""
    const parsedText = parseTicketMentionText(match.replace(/<[^>]*>/g, " "))
    const id = attributes["data-ticket-id"] || extractTicketIdFromHref(href)
    const reference = attributes["data-ticket-reference"] || parsedText.reference || id
    const subject = attributes["data-ticket-subject"] || parsedText.subject || ""
    const status = attributes["data-ticket-status"] || attributes.status || TICKET_MENTION_FALLBACK_STATUS
    const priority = attributes["data-ticket-priority"] || TICKET_MENTION_FALLBACK_PRIORITY
    return buildTicketMentionAnchorHtml({ id, reference, subject, status, priority, url: href })
  })
}

function resolveMentionPopupContainer(editor: Editor): HTMLElement | null {
  if (typeof document === "undefined") {
    return null
  }
  const viewElement = editor.view.dom as HTMLElement | null
  if (!viewElement) {
    return document.body
  }
  const container =
    viewElement.closest<HTMLElement>(
      "[data-radix-dialog-content],[data-radix-drawer-content],[data-radix-popover-content],[data-radix-dropdown-menu-content],[data-radix-sheet-content]"
    ) ?? viewElement.closest<HTMLElement>("[role=\"dialog\"],[role=\"menu\"]")
  return container ?? document.body
}

function toRelativeClientRect(rect: DOMRect | undefined, target: HTMLElement | null): DOMRect {
  const baseRect = rect ?? new DOMRect()
  if (typeof document === "undefined" || !target || target === document.body) {
    return baseRect
  }
  const containerRect = target.getBoundingClientRect()
  return new DOMRect(
    baseRect.x - containerRect.x,
    baseRect.y - containerRect.y,
    baseRect.width,
    baseRect.height
  )
}

const priorityLabels: Record<string, string> = {
  LOW: "Baixa",
  MEDIUM: "Média",
  HIGH: "Alta",
  URGENT: "Urgente",
}

// Cache simples para resultados de menções.
// Obs.: não armazenamos resultados vazios para permitir que
// consultas que antes falharam (ex.: endpoint indisponível)
// sejam revalidadas em tentativas subsequentes.
const mentionCache = new Map<string, TicketMentionItem[]>()
let mentionAbortController: AbortController | null = null

async function fetchTicketMentions(query: string): Promise<TicketMentionItem[]> {
  const cacheKey = query.trim().toLowerCase()
  if (mentionCache.has(cacheKey)) {
    return mentionCache.get(cacheKey) ?? []
  }

  try {
    mentionAbortController?.abort()
    mentionAbortController = new AbortController()
    const response = await fetch(`/api/tickets/mentions?q=${encodeURIComponent(query)}`, {
      signal: mentionAbortController.signal,
    })
    if (!response.ok) {
      return []
    }
    const json = (await response.json()) as { items?: TicketMentionItem[] }
    const items = Array.isArray(json.items) ? json.items : []
    if (items.length > 0) {
      mentionCache.set(cacheKey, items)
    } else {
      mentionCache.delete(cacheKey)
    }
    return items
  } catch (error) {
    if ((error as Error).name === "AbortError") {
      return []
    }
    return []
  }
}

function TicketMentionList({ items, command, onRegister }: TicketMentionSuggestionProps) {
  const [selectedIndex, setSelectedIndex] = useState(0)

  const selectItem = useCallback(
    (index: number) => {
      const item = items[index]
      if (item) {
        command(item)
      }
    },
    [command, items]
  )

  const upHandler = useCallback(() => {
    setSelectedIndex((prev) => {
      const nextIndex = (prev + items.length - 1) % items.length
      return nextIndex
    })
  }, [items.length])

  const downHandler = useCallback(() => {
    setSelectedIndex((prev) => {
      const nextIndex = (prev + 1) % items.length
      return nextIndex
    })
  }, [items.length])

  const enterHandler = useCallback(() => {
    selectItem(selectedIndex)
  }, [selectItem, selectedIndex])

  const handleKeyDown = useCallback(
    (event: KeyboardEvent) => {
      if (event.key === "ArrowUp") {
        upHandler()
        return true
      }
      if (event.key === "ArrowDown") {
        downHandler()
        return true
      }
      if (event.key === "Enter") {
        enterHandler()
        return true
      }
      return false
    },
    [downHandler, enterHandler, upHandler]
  )

  useEffect(() => {
    onRegister?.(handleKeyDown)
    return () => {
      onRegister?.(null)
    }
  }, [handleKeyDown, onRegister])

  useEffect(() => {
    setSelectedIndex(0)
  }, [items])

  if (!items.length) {
    return (
      <div className="min-w-[260px] p-3 text-sm text-muted-foreground">
        Nenhum chamado encontrado com esse termo.
      </div>
    )
  }

  return (
    <div className="max-h-72 min-w-[320px] space-y-1 overflow-y-auto p-2">
      {items.map((item, index) => {
        const isActive = index === selectedIndex
        const status = getTicketStatusLabel(item.status)
        const statusDot = getTicketStatusDotClass(item.status)
        const priority = priorityLabels[item.priority] ?? item.priority
        return (
          <button
            key={item.id}
            type="button"
            className={cn(
              "w-full rounded-lg border border-transparent px-3 py-2 text-left transition",
              isActive ? "border-slate-200 bg-slate-100" : "hover:bg-slate-50"
            )}
            onMouseEnter={() => setSelectedIndex(index)}
            onMouseDown={(event) => {
              event.preventDefault()
              selectItem(index)
            }}
          >
            <div className="flex items-center gap-2">
              <span className="text-sm font-semibold text-neutral-900">#{item.reference}</span>
              <span className="text-xs font-medium uppercase tracking-wide text-neutral-500">{priority}</span>
            </div>
            <div className="mt-1 line-clamp-1 text-sm text-neutral-700">{formatMentionSubject(item.subject)}</div>
            <div className="mt-1 space-y-1 text-xs text-neutral-500">
              <span className="inline-flex items-center gap-1 text-neutral-500">
                <span className={cn("inline-flex size-2 rounded-full", statusDot)} />
                {status}
              </span>
              {item.companyName ? <span className="block text-neutral-500">{item.companyName}</span> : null}
              {item.assigneeName ? (
                <span className="block text-neutral-400">Responsável • {item.assigneeName}</span>
              ) : null}
            </div>
          </button>
        )
      })}
    </div>
  )
}

TicketMentionList.displayName = "TicketMentionList"

const TicketMentionListComponent = (props: TicketMentionSuggestionProps) => (
  <TicketMentionList {...props} />
)

const SafeLinkExtension = TiptapLink.extend({
  priority: 90,
  parseHTML() {
    return [{ tag: 'a[href]:not([data-ticket-mention="true"])' }]
  },
})

const TicketMentionExtension = Mention.extend({
  name: "ticketMention",
  priority: 1000,
  group: "inline",
  inline: true,
  draggable: false,
  selectable: true,
  addAttributes() {
    return {
      id: { default: null },
      reference: { default: null },
      subject: { default: null },
      status: { default: null },
      priority: { default: null },
      url: { default: null },
    }
  },
  addKeyboardShortcuts() {
    // Reuse base Mention keyboard shortcuts when extending the extension.
    const parentShortcuts = (this as unknown as {
      parent?: () => Record<string, (args: { editor: Editor }) => boolean>
    }).parent?.()
    const parent = parentShortcuts ?? {}
    return {
      ...parent,
      Backspace: ({ editor }: { editor: Editor }) => {
        const { state } = editor
        const { selection } = state
        if (selection.empty) {
          const { $from } = selection
          const nodeBefore = $from.nodeBefore
          if (nodeBefore?.type?.name === this.name) {
            const from = $from.pos - nodeBefore.nodeSize
            const to = $from.pos
            editor.chain().focus().deleteRange({ from, to }).run()
            return true
          }
        }
        return parent.Backspace ? parent.Backspace({ editor }) : false
      },
      Delete: ({ editor }: { editor: Editor }) => {
        const { state } = editor
        const { selection } = state
        if (selection.empty) {
          const { $from } = selection
          const nodeAfter = $from.nodeAfter
          if (nodeAfter?.type?.name === this.name) {
            const from = $from.pos
            const to = $from.pos + nodeAfter.nodeSize
            editor.chain().focus().deleteRange({ from, to }).run()
            return true
          }
        }
        return parent.Delete ? parent.Delete({ editor }) : false
      },
    }
  },
  parseHTML() {
    return [
      {
        tag: `a[data-ticket-mention="true"]`,
        priority: 1000,
        getAttrs: (dom: HTMLElement | string) => {
          if (dom instanceof HTMLElement) {
            return {
              id: dom.dataset.ticketId ?? null,
              reference: dom.dataset.ticketReference ?? null,
              status: dom.dataset.ticketStatus ?? null,
              priority: dom.dataset.ticketPriority ?? null,
              subject: dom.getAttribute("data-ticket-subject") ?? dom.textContent ?? null,
              url: dom.getAttribute("href") ?? null,
            }
          }
          return {}
        },
      },
      {
        tag: "a.ticket-mention",
        priority: 1000,
        getAttrs: (dom: HTMLElement | string) => {
          if (dom instanceof HTMLElement) {
            const href = dom.getAttribute("href") ?? null
            const id = dom.dataset.ticketId ?? extractTicketIdFromHref(href)
            const parsed = parseTicketMentionText(dom.textContent)
            const reference = dom.dataset.ticketReference ?? parsed.reference ?? null
            const subject = dom.dataset.ticketSubject ?? parsed.subject ?? null
            const status = dom.dataset.ticketStatus ?? dom.getAttribute("status") ?? null
            const priority = dom.dataset.ticketPriority ?? null
            return {
              id,
              reference,
              status,
              priority,
              subject,
              url: href,
            }
          }
          return {}
        },
      },
    ]
  },
  renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
    const referenceValue =
      HTMLAttributes.reference ?? HTMLAttributes["data-ticket-reference"] ?? ""
    const reference = String(referenceValue ?? "")
    const subjectValue =
      HTMLAttributes.subject ?? HTMLAttributes["data-ticket-subject"] ?? ""
    const subject = String(subjectValue ?? "")
    const status = String(HTMLAttributes.status ?? HTMLAttributes["data-ticket-status"] ?? "PENDING").toUpperCase()
    const priority = String(HTMLAttributes.priority ?? HTMLAttributes["data-ticket-priority"] ?? "MEDIUM").toUpperCase()
    const href = String(HTMLAttributes.url ?? HTMLAttributes.href ?? "#")
    const anchorClass = cn(TICKET_MENTION_BASE_CLASSES, toPlainString(HTMLAttributes.class ?? HTMLAttributes.className))
    const dotClass = cn(TICKET_MENTION_DOT_BASE_CLASSES, getTicketStatusDotClass(status))
    return [
      "a",
      {
        ...HTMLAttributes,
        href,
        "data-type": HTMLAttributes["data-type"] ?? this.name,
        "data-ticket-mention": "true",
        "data-ticket-id": HTMLAttributes.id ?? HTMLAttributes["data-ticket-id"] ?? "",
        "data-ticket-reference": reference ?? "",
        "data-ticket-status": status,
        "data-ticket-priority": priority,
        "data-ticket-subject": subject ?? "",
        status,
        class: anchorClass,
      },
      [
        "span",
        { class: dotClass },
      ],
      [
        "span",
        { class: TICKET_MENTION_REF_CLASSES },
        `#${reference ?? ""}`,
      ],
      [
        "span",
        { class: TICKET_MENTION_SEP_CLASSES },
        "•",
      ],
      [
        "span",
        { class: TICKET_MENTION_SUBJECT_CLASSES },
        formatMentionSubject(subject ?? ""),
      ],
    ]
  },
  renderLabel({ node }: { node: { attrs: Record<string, unknown> } }) {
    const subjectAttr = node.attrs.subject ?? node.attrs["data-ticket-subject"] ?? ""
    const displayedSubject = typeof subjectAttr === "string" ? subjectAttr : String(subjectAttr ?? "")
    const refAttr = node.attrs.reference ?? node.attrs.id ?? node.attrs["data-ticket-reference"] ?? ""
    const reference = typeof refAttr === "string" ? refAttr : String(refAttr ?? "")
    const subjectPart = displayedSubject ? ` • ${formatMentionSubject(displayedSubject)}` : ""
    return `#${reference}${subjectPart}`
  },
  addNodeView() {
    const extensionName = this.name
    return ({ node }: { node: { attrs: TicketMentionAttributes; type: { name: string } } }) => {
      if (typeof document === "undefined") {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return null as any
      }

      const root = document.createElement("a")
      root.dataset.ticketMention = "true"
      root.setAttribute("contenteditable", "false")
      root.setAttribute("data-type", extensionName)
      root.setAttribute("draggable", "false")
      root.setAttribute("spellcheck", "false")
      root.tabIndex = -1

      const dotEl = document.createElement("span")
      dotEl.className = TICKET_MENTION_DOT_BASE_CLASSES
      dotEl.setAttribute("aria-hidden", "true")

      const referenceEl = document.createElement("span")
      referenceEl.className = TICKET_MENTION_REF_CLASSES

      const separatorEl = document.createElement("span")
      separatorEl.className = TICKET_MENTION_SEP_CLASSES
      separatorEl.textContent = "•"

      const subjectEl = document.createElement("span")
      subjectEl.className = TICKET_MENTION_SUBJECT_CLASSES

      root.append(dotEl, referenceEl, separatorEl, subjectEl)

      const elements: TicketMentionNodeElements = {
        root,
        dotEl,
        referenceEl,
        subjectEl,
        separatorEl,
      }

      updateTicketMentionNodeElements(elements, node.attrs ?? {})

      const preventDefaultClick = (event: MouseEvent) => {
        event.preventDefault()
      }
      root.addEventListener("click", preventDefaultClick)

      return {
        dom: root,
        update: (updatedNode: { attrs: TicketMentionAttributes; type: { name: string } }) => {
          if (updatedNode.type.name !== extensionName) {
            return false
          }
          updateTicketMentionNodeElements(elements, updatedNode.attrs ?? {})
          return true
        },
        ignoreMutation: () => true,
        destroy: () => {
          root.removeEventListener("click", preventDefaultClick)
        },
      }
    }
  },
}).configure({
  suggestion: {
    char: "@",
    startOfLine: false,
    allowSpaces: false,
    render: () => {
      let component: ReactRenderer | null = null
      let popup: Instance<TippyProps> | null = null
      let keydownHandler: ((event: KeyboardEvent) => boolean) | null = null
      let appendTarget: HTMLElement | null = null
      const registerHandler = (handler: ((event: KeyboardEvent) => boolean) | null) => {
        keydownHandler = handler
      }
      return {
        onStart: (props) => {
          const listProps: TicketMentionSuggestionProps = {
            command: props.command,
            items: props.items,
            onRegister: registerHandler,
          }
          component = new ReactRenderer(TicketMentionListComponent, {
            props: listProps,
            editor: props.editor,
          })
          const container = resolveMentionPopupContainer(props.editor)
          appendTarget = container ?? document.body
          if (!props.clientRect) return
          const computeRect = () => toRelativeClientRect(props.clientRect?.() ?? undefined, appendTarget)
          popup = tippy(appendTarget, {
            getReferenceClientRect: () => computeRect(),
            appendTo: () => appendTarget ?? document.body,
            content: component.element,
            showOnCreate: true,
            interactive: true,
            trigger: "manual",
            placement: "bottom-start",
            zIndex: 99999,
            popperOptions: { strategy: "fixed" },
          })
        },
        onUpdate(props) {
          component?.updateProps({
            command: props.command,
            items: props.items,
            onRegister: registerHandler,
          })
          if (!props.clientRect) return
          const computeRect = () => toRelativeClientRect(props.clientRect?.() ?? undefined, appendTarget)
          popup?.setProps({
            getReferenceClientRect: () => computeRect(),
          })
        },
        onKeyDown(props) {
          if (props.event.key === "Escape") {
            popup?.hide()
            return true
          }
          if (keydownHandler && keydownHandler(props.event)) {
            return true
          }
          return false
        },
        onExit() {
          popup?.destroy()
          component?.destroy()
          keydownHandler = null
          appendTarget = null
        },
      }
    },
    items: async ({ query }) => {
      return fetchTicketMentions(query)
    },
  },
})

export function RichTextEditor({
  value,
  onChange,
  className,
  placeholder = "Escreva aqui...",
  disabled,
  minHeight = 120,
  ticketMention,
  onEditorReady,
  onPasteFiles,
}: RichTextEditorProps) {
  const normalizedInitialContent = useMemo(() => {
    if (!ticketMention?.enabled) {
      return value || ""
    }
    return normalizeTicketMentionHtml(value || "")
  }, [ticketMention?.enabled, value])

  const extensions = useMemo(() => {
    const mentionExtensions = ticketMention?.enabled ? [TicketMentionExtension] : []

    const coreExtensions = [
      StarterKit.configure({
        bulletList: { keepMarks: true },
        orderedList: { keepMarks: true },
        link: false,
      }),
      SafeLinkExtension.configure({
        openOnClick: true,
        autolink: true,
        protocols: ["http", "https", "mailto"],
        HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
      }),
      Markdown,
      Placeholder.configure({ placeholder }),
    ]

    return [...mentionExtensions, ...coreExtensions]
  }, [placeholder, ticketMention?.enabled])

  const editor = useEditor({
    extensions,
    editorProps: {
      attributes: {
        class:
          "prose prose-sm max-w-none focus:outline-none text-foreground",
      },
      handlePaste(_, event) {
        if (!onPasteFiles) return false
        const imageFiles = extractImageFilesFromClipboard(event)
        if (imageFiles.length === 0) return false
        event.preventDefault()
        const limited = imageFiles.slice(0, MAX_PASTED_IMAGE_FILES)
        onPasteFiles(limited)
        return true
      },
    },
    content: normalizedInitialContent,
    onUpdate({ editor }) {
      const html = editor.getHTML()
      const normalized = ticketMention?.enabled ? normalizeTicketMentionHtml(html) : html
      onChange?.(normalized)
    },
    editable: !disabled,
    // Avoid SSR hydration mismatches per Tiptap recommendation
    immediatelyRender: false,
  })

  useEffect(() => {
    if (!editor) return
    onEditorReady?.(editor)
    return () => {
      onEditorReady?.(null)
    }
  }, [editor, onEditorReady])

  const [linkPopoverOpen, setLinkPopoverOpen] = useState(false)
  const [linkUrl, setLinkUrl] = useState("")
  const linkInputRef = useRef<HTMLInputElement>(null)

  const closeLinkPopover = useCallback(() => {
    setLinkPopoverOpen(false)
    requestAnimationFrame(() => {
      editor?.commands.focus()
    })
  }, [editor])

  const openLinkPopover = useCallback(() => {
    if (!editor) return
    editor.chain().focus()
    const prev = (editor.getAttributes("link").href as string | undefined) ?? ""
    setLinkUrl(prev)
    setLinkPopoverOpen(true)
    requestAnimationFrame(() => {
      linkInputRef.current?.focus()
      if (prev) {
        linkInputRef.current?.select()
      }
    })
  }, [editor])

  const applyLink = useCallback(() => {
    if (!editor) return
    const trimmed = linkUrl.trim()
    if (!trimmed) {
      editor.chain().focus().extendMarkRange("link").unsetLink().run()
      closeLinkPopover()
      return
    }
    const normalized = /^(https?:\/\/|mailto:)/i.test(trimmed) ? trimmed : `https://${trimmed}`
    editor.chain().focus().extendMarkRange("link").setLink({ href: normalized }).run()
    closeLinkPopover()
  }, [closeLinkPopover, editor, linkUrl])

  const removeLink = useCallback(() => {
    if (!editor) return
    editor.chain().focus().extendMarkRange("link").unsetLink().run()
    closeLinkPopover()
  }, [closeLinkPopover, editor])

  useEffect(() => {
    if (!editor || !linkPopoverOpen) return
    const handler = () => {
      const prev = (editor.getAttributes("link").href as string | undefined) ?? ""
      setLinkUrl(prev)
    }
    editor.on("selectionUpdate", handler)
    return () => {
      editor.off("selectionUpdate", handler)
    }
  }, [editor, linkPopoverOpen])

  // Keep external value in sync when it changes
  useEffect(() => {
    if (!editor) return
    const normalized = ticketMention?.enabled ? normalizeTicketMentionHtml(value || "") : value || ""
    const current = editor.getHTML()
    if (normalized !== current) {
      editor.commands.setContent(normalized, { emitUpdate: false })
    }
  }, [value, editor, ticketMention?.enabled])

  // Reflect disabled prop changes after initialization
  useEffect(() => {
    if (!editor) return
    editor.setEditable(!disabled)
  }, [disabled, editor])

  if (!editor) return null

  return (
    <div className={cn("rounded-md border bg-background", className)}>
      <div className="flex flex-wrap items-center gap-1 border-b px-2 py-1">
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBold().run()}
          active={editor.isActive("bold")}
          ariaLabel="Negrito"
        >
          <Bold className="size-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleItalic().run()}
          active={editor.isActive("italic")}
          ariaLabel="Itálico"
        >
          <Italic className="size-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleStrike().run()}
          active={editor.isActive("strike")}
          ariaLabel="Tachado"
        >
          <Strikethrough className="size-4" />
        </ToolbarButton>
        <Separator orientation="vertical" className="mx-1 h-5" />
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBulletList().run()}
          active={editor.isActive("bulletList")}
          ariaLabel="Lista"
        >
          <List className="size-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleOrderedList().run()}
          active={editor.isActive("orderedList")}
          ariaLabel="Lista ordenada"
        >
          <ListOrdered className="size-4" />
        </ToolbarButton>
        <ToolbarButton
          onClick={() => editor.chain().focus().toggleBlockquote().run()}
          active={editor.isActive("blockquote")}
          ariaLabel="Citação"
        >
          <Quote className="size-4" />
        </ToolbarButton>
        <Popover
          open={linkPopoverOpen}
          onOpenChange={(open) => {
            if (open) {
              openLinkPopover()
            } else {
              closeLinkPopover()
            }
          }}
        >
          <PopoverTrigger asChild>
            <ToolbarButton active={editor.isActive("link")} ariaLabel="Inserir link">
              <LinkIcon className="size-4" />
            </ToolbarButton>
          </PopoverTrigger>
          <PopoverContent className="w-72 space-y-3" align="start">
            <div className="space-y-1">
              <label htmlFor="rich-text-editor-link" className="text-xs font-semibold text-neutral-600">
                URL do link
              </label>
              <Input
                id="rich-text-editor-link"
                ref={linkInputRef}
                placeholder="https://"
                value={linkUrl}
                onChange={(event) => setLinkUrl(event.target.value)}
                onKeyDown={(event) => {
                  if (event.key === "Enter") {
                    event.preventDefault()
                    applyLink()
                  } else if (event.key === "Escape") {
                    event.preventDefault()
                    closeLinkPopover()
                  }
                }}
              />
            </div>
            <div className="flex items-center justify-between gap-2">
              <Button
                type="button"
                variant="outline"
                size="sm"
                className="inline-flex items-center gap-2"
                onClick={removeLink}
                disabled={!editor.isActive("link")}
              >
                <Link2Off className="size-4" />
                Remover
              </Button>
              <Button type="button" size="sm" className="inline-flex items-center gap-2" onClick={applyLink}>
                <Check className="size-4" />
                Aplicar
              </Button>
            </div>
          </PopoverContent>
        </Popover>
        <div className="ms-auto flex items-center gap-1">
          <ToolbarButton onClick={() => editor.chain().focus().undo().run()} ariaLabel="Desfazer">
            <Undo className="size-4" />
          </ToolbarButton>
          <ToolbarButton onClick={() => editor.chain().focus().redo().run()} ariaLabel="Refazer">
            <Redo className="size-4" />
          </ToolbarButton>
        </div>
      </div>
      <div
        style={{ minHeight }}
        className="rich-text p-3"
        onMouseDown={(event) => {
          if (!editor) return
          if (event.target === event.currentTarget) {
            event.preventDefault()
            editor.commands.focus("end")
          }
        }}
      >
        <EditorContent editor={editor} />
      </div>
    </div>
  )
}

type ToolbarButtonProps = {
  onClick?: () => void
  active?: boolean
  ariaLabel?: string
  children: ReactNode
}

const ToolbarButton = forwardRef<HTMLButtonElement, ToolbarButtonProps>(
  ({ onClick, active, ariaLabel, children }, ref) => {
    return (
      <Button
        type="button"
        variant={active ? "default" : "ghost"}
        size="icon"
        className="h-7 w-7"
        onMouseDown={(event) => event.preventDefault()}
        onClick={onClick}
        aria-label={ariaLabel}
        ref={ref}
      >
        {children}
      </Button>
    )
  }
)

ToolbarButton.displayName = "ToolbarButton"

// Utilitário simples para renderização segura do HTML do editor.
// Remove tags <script>/<style> e atributos on*.
export function sanitizeEditorHtml(html: string): string {
  try {
    return sanitize(html || "", {
      allowedTags: [
        "p","br","a","strong","em","u","s","blockquote","ul","ol","li","code","pre","span","h1","h2","h3"
      ],
      allowedAttributes: {
        a: [
          "href",
          "name",
          "target",
          "rel",
          "class",
          "data-ticket-mention",
          "data-ticket-id",
          "data-ticket-reference",
          "data-ticket-status",
          "data-ticket-priority",
          "data-ticket-subject",
          "title",
        ],
        span: [
          "style",
          "class",
          "data-ticket-mention",
          "data-ticket-id",
          "data-ticket-reference",
          "data-ticket-status",
          "data-ticket-priority",
          "data-ticket-subject",
        ],
        code: ["class"],
        pre: ["class"],
      },
      allowedSchemes: ["http","https","mailto"],
      // prevent target=_self phishing
      transformTags: {
        a: (tagName, attribs) => {
          const isMention = attribs["data-ticket-mention"] === "true"
          const nextAttribs = {
            ...attribs,
            rel: attribs.rel ?? "noopener noreferrer",
            target: isMention ? "_self" : "_blank",
          }
          return {
            tagName,
            attribs: nextAttribs,
          }
        },
      },
      // disallow inline event handlers
      allowVulnerableTags: false,
    })
  } catch {
    return ""
  }
}

export function stripLeadingEmptyParagraphs(html: string): string {
  if (!html) return ""
  return html.replace(
    /^(?:\s|&nbsp;|&#160;|<br\s*\/?>|<p>(?:\s|&nbsp;|&#160;|<br\s*\/?>)*<\/p>)+/gi,
    ""
  )
}

export function RichTextContent({ html, className }: { html: string; className?: string }) {
  return (
    <div
      className={cn("rich-text text-sm leading-relaxed", className)}
      dangerouslySetInnerHTML={{ __html: sanitizeEditorHtml(html) }}
    />
  )
}
