<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue"
import { EditorContent, Extension, Node, useEditor } from "@tiptap/vue-3"
import StarterKit from "@tiptap/starter-kit"
import TextAlign from "@tiptap/extension-text-align"
import { BackgroundColor, Color, FontFamily, TextStyle } from "@tiptap/extension-text-style"
import Link from "@tiptap/extension-link"
import Image from "@tiptap/extension-image"
import { Table, TableRow, TableCell, TableHeader } from "@tiptap/extension-table"
import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  ArrowDown,
  ArrowLeft,
  ArrowRight,
  ArrowUp,
  Bold,
  Code,
  Combine,
  Heading,
  Heading1,
  Heading2,
  Heading3,
  Heading4,
  Heading5,
  Heading6,
  Highlighter,
  ImageIcon,
  Italic,
  Link2,
  List,
  ListOrdered,
  Minus,
  Palette,
  Quote,
  Redo,
  Scissors,
  Strikethrough,
  Table2,
  Trash2,
  Type,
  Underline as UnderlineIcon,
  Undo,
  X,
} from "@lucide/vue"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Checkbox } from "@/components/ui/checkbox"
import { FieldFrame } from "@/components/ui/field-frame"
import { cn } from "@/lib/utils"

export interface HtmlEditorProps {
  modelValue?: string | null
  id?: string
  name?: string
  label?: string
  hideLabel?: boolean
  placeholder?: string
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  helpText?: string
  errors?: string[]
  minHeight?: string
}

const props = withDefaults(defineProps<HtmlEditorProps>(), {
  errors: () => [],
  minHeight: "14rem",
})

const emit = defineEmits<{
  (e: "update:modelValue", value: string): void
}>()

// ---------------------------------------------------------------------------
// Custom node: preserve <video> tags
// ---------------------------------------------------------------------------

const VideoNode = Node.create({
  name: "video",
  group: "block",
  content: "block*",
  addAttributes() {
    return {
      src: { default: null },
      width: { default: "320" },
      height: { default: "240" },
      controls: {
        default: true,
        parseHTML: (el: HTMLElement) => el.hasAttribute("controls"),
        renderHTML: (a: Record<string, unknown>) => (a.controls ? { controls: "" } : {}),
      },
    }
  },
  parseHTML() {
    return [{ tag: "video" }]
  },
  renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
    return ["video", HTMLAttributes, 0]
  },
})

// Extended Image to pass through style attribute
const ExtendedImage = Image.extend({
  addAttributes() {
    return {
      src: { default: "" },
      alt: { default: undefined },
      title: { default: undefined },
      style: { default: undefined },
    }
  },
})

// ---------------------------------------------------------------------------
// Font families
// ---------------------------------------------------------------------------

const FONTS: { label: string; value: string }[] = [
  { label: "Arial", value: "Arial, sans-serif" },
  { label: "Verdana", value: "Verdana, sans-serif" },
  { label: "Tahoma", value: "Tahoma, sans-serif" },
  { label: "Times New Roman", value: "'Times New Roman', serif" },
  { label: "Georgia", value: "Georgia, serif" },
  { label: "Courier New", value: "'Courier New', monospace" },
]

// ---------------------------------------------------------------------------
// Editor
// ---------------------------------------------------------------------------

const isInert = computed(() => Boolean(props.disabled || props.readonly))

const editor = useEditor({
  content: props.modelValue ?? "",
  editable: !isInert.value,
  extensions: [
    // StarterKit (tiptap v3+) bundles Underline and Link. We disable its
    // built-in Link so we can re-add it with `openOnClick: false`. We
    // keep StarterKit's Underline — it needs no custom config.
    StarterKit.configure({ link: false }),
    TextAlign.configure({ types: ["heading", "paragraph"] }),
    TextStyle,
    Color,
    BackgroundColor,
    FontFamily.configure({ types: ["textStyle"] }),
    Link.configure({ openOnClick: false }),
    ExtendedImage.configure({ inline: false, allowBase64: true }),
    Table.configure({ resizable: true }),
    TableRow,
    TableHeader,
    TableCell,
    VideoNode,
    // Suppress unused Extension import warning — it's used by BackgroundColor above
    Extension.create({ name: "_noop" }),
  ].filter((e) => e.name !== "_noop"),
  editorProps: {
    attributes: { class: "focus:outline-none" },
  },
  onUpdate: () => {
    if (editor.value) emit("update:modelValue", editor.value.getHTML())
  },
  onSelectionUpdate: () => {
    syncFormattingState()
  },
})

watch(
  () => props.modelValue,
  (value) => {
    const e = editor.value
    if (!e) return
    const next = value ?? ""
    if (next === e.getHTML()) return
    e.commands.setContent(next, { emitUpdate: false })
  },
)

watch(isInert, (v) => {
  editor.value?.setEditable(!v)
})

onBeforeUnmount(() => {
  editor.value?.destroy()
})

onMounted(() => {
  nextTick(() => {
    const e = editor.value
    if (e && props.modelValue && e.isEmpty) {
      e.commands.setContent(props.modelValue, { emitUpdate: false })
    }
  })
})

// ---------------------------------------------------------------------------
// Formatting state tracking
// ---------------------------------------------------------------------------

const currentHeading = ref<number | null>(null)
const currentAlign = ref<"left" | "center" | "right">("left")
const currentFont = ref<string | null>(null)
const currentTextColor = ref<string>("")
const currentBgColor = ref<string>("")

function syncFormattingState(): void {
  const e = editor.value
  if (!e) return

  currentHeading.value = null
  for (let i = 1; i <= 6; i++) {
    if (e.isActive("heading", { level: i })) {
      currentHeading.value = i
      break
    }
  }

  if (e.isActive({ textAlign: "center" })) currentAlign.value = "center"
  else if (e.isActive({ textAlign: "right" })) currentAlign.value = "right"
  else currentAlign.value = "left"

  currentFont.value = FONTS.find((f) => e.isActive("textStyle", { fontFamily: f.value }))?.value ?? null

  const attrs = e.getAttributes("textStyle") as Record<string, unknown>
  currentTextColor.value = typeof attrs.color === "string" ? attrs.color : ""
  currentBgColor.value = typeof attrs.backgroundColor === "string" ? attrs.backgroundColor : ""
}

// ---------------------------------------------------------------------------
// Toolbar helpers
// ---------------------------------------------------------------------------

function isActive(name: string, attrs?: Record<string, unknown>): boolean {
  const e = editor.value
  if (!e) return false
  return attrs ? e.isActive(name, attrs) : e.isActive(name)
}

function setHeading(level: 1 | 2 | 3 | 4 | 5 | 6): void {
  editor.value?.chain().focus().setHeading({ level }).run()
  currentHeading.value = level
}

function clearHeading(): void {
  editor.value?.chain().focus().setParagraph().run()
  currentHeading.value = null
}

const headingIcon = computed(() => {
  switch (currentHeading.value) {
    case 1: return Heading1
    case 2: return Heading2
    case 3: return Heading3
    case 4: return Heading4
    case 5: return Heading5
    case 6: return Heading6
    default: return Heading
  }
})

const alignIcon = computed(() => {
  if (currentAlign.value === "center") return AlignCenter
  if (currentAlign.value === "right") return AlignRight
  return AlignLeft
})

function setAlign(align: "left" | "center" | "right"): void {
  editor.value?.chain().focus().setTextAlign(align).run()
  currentAlign.value = align
}

function setFont(value: string): void {
  editor.value?.chain().focus().unsetFontFamily().setFontFamily(value).run()
  currentFont.value = value
}

function clearFont(): void {
  editor.value?.chain().focus().unsetFontFamily().run()
  currentFont.value = null
}

// Color commands are added by BackgroundColor/Color extensions — cast via unknown
function applyTextColor(color: string): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ;(editor.value?.chain().focus() as any)?.unsetColor()?.setColor(color)?.run()
  currentTextColor.value = color
}

function clearTextColor(): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ;(editor.value?.chain().focus() as any)?.unsetColor()?.run()
  currentTextColor.value = ""
}

function applyBgColor(color: string): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ;(editor.value?.chain().focus() as any)?.unsetBackgroundColor()?.setBackgroundColor(color)?.run()
  currentBgColor.value = color
}

function clearBgColor(): void {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ;(editor.value?.chain().focus() as any)?.unsetBackgroundColor()?.run()
  currentBgColor.value = ""
}

function toggleList(type: "bullet" | "ordered"): void {
  if (type === "bullet") editor.value?.chain().focus().toggleBulletList().run()
  else editor.value?.chain().focus().toggleOrderedList().run()
}

// ---------------------------------------------------------------------------
// Table
// ---------------------------------------------------------------------------

const tableHoverRows = ref(2)
const tableHoverCols = ref(2)

/** Flat list for the 4×6 table creation grid */
const tableGridCells = computed(() => {
  const cells: { r: number; c: number }[] = []
  for (let r = 1; r <= 4; r++) {
    for (let c = 1; c <= 6; c++) {
      cells.push({ r, c })
    }
  }
  return cells
})

function insertTable(): void {
  editor.value
    ?.chain()
    .focus()
    .insertTable({ rows: tableHoverRows.value, cols: tableHoverCols.value, withHeaderRow: true })
    .run()
}

// ---------------------------------------------------------------------------
// Link dialog
// ---------------------------------------------------------------------------

const linkDialogOpen = ref(false)
const linkUrl = ref("")
const linkText = ref("")
const linkTargetBlank = ref(true)
const isEditingLink = ref(false)

function openLinkDialog(): void {
  const e = editor.value
  if (!e) return
  isEditingLink.value = e.isActive("link")
  if (isEditingLink.value) {
    e.chain().focus().extendMarkRange("link").run()
    const attrs = e.getAttributes("link") as Record<string, unknown>
    linkUrl.value = typeof attrs.href === "string" ? attrs.href : ""
    linkTargetBlank.value = attrs.target === "_blank"
    const { from, to } = e.state.selection
    linkText.value = e.state.doc.textBetween(from, to, " ")
  } else {
    const { from, to } = e.state.selection
    linkText.value = e.state.doc.textBetween(from, to, " ")
    linkUrl.value = "https://"
    linkTargetBlank.value = true
  }
  if (!linkText.value) linkText.value = "Link"
  linkDialogOpen.value = true
}

function applyLink(): void {
  const e = editor.value
  if (!e || !linkUrl.value) return
  if (e.isActive("link")) e.chain().focus().unsetLink().run()
  const target = linkTargetBlank.value ? "_blank" : "_self"
  const { from, to } = e.state.selection
  if (from === to) {
    e.chain().focus().insertContent(`<a href="${linkUrl.value}" target="${target}">${linkText.value}</a>`).run()
  } else {
    e.chain().focus().extendMarkRange("link").setLink({ href: linkUrl.value, target }).run()
  }
  linkDialogOpen.value = false
}

function removeLink(): void {
  editor.value?.chain().focus().extendMarkRange("link").unsetLink().run()
  linkDialogOpen.value = false
}

// ---------------------------------------------------------------------------
// Image dialog
// ---------------------------------------------------------------------------

const imageDialogOpen = ref(false)
const imageUrl = ref("")
const imageAlt = ref("")
const imageStyle = ref("")
const isEditingImage = ref(false)

function openImageDialog(): void {
  const e = editor.value
  if (!e) return
  isEditingImage.value = e.isActive("image")
  if (isEditingImage.value) {
    const attrs = e.getAttributes("image") as Record<string, unknown>
    imageUrl.value = typeof attrs.src === "string" ? attrs.src : ""
    imageAlt.value = typeof attrs.alt === "string" ? attrs.alt : ""
    imageStyle.value = typeof attrs.style === "string" ? attrs.style : ""
  } else {
    imageUrl.value = ""
    imageAlt.value = ""
    imageStyle.value = "width: auto; height: auto; max-width: 100%;"
  }
  imageDialogOpen.value = true
}

function applyImage(): void {
  const e = editor.value
  if (!e || !imageUrl.value) return
  const attrs = {
    src: imageUrl.value,
    alt: imageAlt.value || undefined,
    style: imageStyle.value || undefined,
  }
  if (isEditingImage.value) {
    e.chain().focus().updateAttributes("image", attrs).run()
  } else {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ;(e.chain().focus() as any).setImage(attrs).run()
  }
  imageDialogOpen.value = false
}

// ---------------------------------------------------------------------------
// HTML source dialog
// ---------------------------------------------------------------------------

const sourceDialogOpen = ref(false)
const sourceHtml = ref("")

function openSourceDialog(): void {
  if (!editor.value) return
  sourceHtml.value = editor.value.getHTML()
  sourceDialogOpen.value = true
}

function applySource(): void {
  if (!editor.value) return
  editor.value.commands.setContent(sourceHtml.value)
  emit("update:modelValue", sourceHtml.value)
  sourceDialogOpen.value = false
}

// ---------------------------------------------------------------------------
// Misc
// ---------------------------------------------------------------------------

const hasErrors = computed(() => props.errors.length > 0)
const contentStyle = computed(() => ({ minHeight: props.minHeight }))

function btnClass(active: boolean): string {
  return cn("h-7 w-7 p-0 shrink-0", active ? "bg-muted text-foreground" : "text-muted-foreground")
}
</script>

<template>
  <FieldFrame
    :label="label"
    :required="required"
    :hide-label="hideLabel"
    :for="id"
    :help-text="helpText"
    :errors="errors"
    data-testid="html-editor"
  >
    <div
      :class="cn(
        'rounded-md border border-input bg-background shadow-sm overflow-hidden',
        hasErrors ? 'border-destructive ring-1 ring-destructive' : '',
        isInert ? 'opacity-60' : '',
      )"
      :data-element-name="name ?? undefined"
    >
      <!-- ----------------------------------------------------------------- -->
      <!-- Toolbar                                                            -->
      <!-- ----------------------------------------------------------------- -->
      <div
        class="flex flex-col border-b bg-muted/30"
        :class="isInert ? 'pointer-events-none' : ''"
        role="toolbar"
        aria-label="HTML formatting"
        data-testid="html-editor-toolbar"
      >
        <!-- Row 1: headings · inline formatting · blockquote/hr · undo/redo · source -->
        <div class="flex flex-wrap items-center gap-0.5 border-b border-border/40 px-1.5 py-1">
          <!-- Heading -->
          <DropdownMenu>
            <DropdownMenuTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(currentHeading !== null)" title="Heading">
                <component :is="headingIcon" class="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" class="w-44">
              <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">Heading</DropdownMenuLabel>
              <DropdownMenuSeparator />
              <DropdownMenuItem :class="currentHeading === 1 ? 'bg-muted' : ''" @click="setHeading(1)">
                <Heading1 class="mr-2 h-4 w-4" />Heading 1
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentHeading === 2 ? 'bg-muted' : ''" @click="setHeading(2)">
                <Heading2 class="mr-2 h-4 w-4" />Heading 2
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentHeading === 3 ? 'bg-muted' : ''" @click="setHeading(3)">
                <Heading3 class="mr-2 h-4 w-4" />Heading 3
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentHeading === 4 ? 'bg-muted' : ''" @click="setHeading(4)">
                <Heading4 class="mr-2 h-4 w-4" />Heading 4
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentHeading === 5 ? 'bg-muted' : ''" @click="setHeading(5)">
                <Heading5 class="mr-2 h-4 w-4" />Heading 5
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentHeading === 6 ? 'bg-muted' : ''" @click="setHeading(6)">
                <Heading6 class="mr-2 h-4 w-4" />Heading 6
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem @click="clearHeading">
                <X class="mr-2 h-4 w-4" />Clear heading
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <Button type="button" variant="ghost" :class="btnClass(isActive('bold'))" title="Bold"
            @click="editor?.chain().focus().toggleBold().run()"><Bold class="h-4 w-4" /></Button>
          <Button type="button" variant="ghost" :class="btnClass(isActive('italic'))" title="Italic"
            @click="editor?.chain().focus().toggleItalic().run()"><Italic class="h-4 w-4" /></Button>
          <Button type="button" variant="ghost" :class="btnClass(isActive('underline'))" title="Underline"
            @click="editor?.chain().focus().toggleUnderline().run()"><UnderlineIcon class="h-4 w-4" /></Button>
          <Button type="button" variant="ghost" :class="btnClass(isActive('strike'))" title="Strikethrough"
            @click="editor?.chain().focus().toggleStrike().run()"><Strikethrough class="h-4 w-4" /></Button>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <Button type="button" variant="ghost" :class="btnClass(isActive('blockquote'))" title="Blockquote"
            @click="editor?.chain().focus().toggleBlockquote().run()"><Quote class="h-4 w-4" /></Button>
          <Button type="button" variant="ghost" :class="btnClass(false)" title="Horizontal rule"
            @click="editor?.chain().focus().setHorizontalRule().run()"><Minus class="h-4 w-4" /></Button>

          <div class="flex-1" />

          <Button type="button" variant="ghost" :class="btnClass(false)" title="Undo"
            :disabled="!editor?.can().chain().focus().undo().run()"
            @click="editor?.chain().focus().undo().run()"><Undo class="h-4 w-4" /></Button>
          <Button type="button" variant="ghost" :class="btnClass(false)" title="Redo"
            :disabled="!editor?.can().chain().focus().redo().run()"
            @click="editor?.chain().focus().redo().run()"><Redo class="h-4 w-4" /></Button>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <Button type="button" variant="ghost" :class="btnClass(false)" title="Edit HTML source"
            @click="openSourceDialog"><Code class="h-4 w-4" /></Button>
        </div>

        <!-- Row 2: align · font · colors · link/image · lists · table -->
        <div class="flex flex-wrap items-center gap-0.5 px-1.5 py-1">
          <!-- Align -->
          <DropdownMenu>
            <DropdownMenuTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(currentAlign !== 'left')" title="Text alignment">
                <component :is="alignIcon" class="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" class="w-36">
              <DropdownMenuItem :class="currentAlign === 'left' ? 'bg-muted' : ''" @click="setAlign('left')">
                <AlignLeft class="mr-2 h-4 w-4" />Left
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentAlign === 'center' ? 'bg-muted' : ''" @click="setAlign('center')">
                <AlignCenter class="mr-2 h-4 w-4" />Center
              </DropdownMenuItem>
              <DropdownMenuItem :class="currentAlign === 'right' ? 'bg-muted' : ''" @click="setAlign('right')">
                <AlignRight class="mr-2 h-4 w-4" />Right
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <!-- Font family -->
          <DropdownMenu>
            <DropdownMenuTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(currentFont !== null)" title="Font family">
                <Type class="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" class="w-52">
              <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">Font family</DropdownMenuLabel>
              <DropdownMenuSeparator />
              <DropdownMenuItem
                v-for="font in FONTS"
                :key="font.value"
                :class="currentFont === font.value ? 'bg-muted' : ''"
                :style="`font-family: ${font.value}`"
                @click="setFont(font.value)"
              >
                {{ font.label }}
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem @click="clearFont">
                <X class="mr-2 h-4 w-4" />Use default
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          <!-- Text color -->
          <Popover>
            <PopoverTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(!!currentTextColor)" title="Text color">
                <Palette class="h-4 w-4" :style="currentTextColor ? { color: currentTextColor } : {}" />
              </Button>
            </PopoverTrigger>
            <PopoverContent class="w-52 p-3" align="start">
              <p class="mb-2 text-xs font-medium text-muted-foreground">Text color</p>
              <div class="flex items-center gap-2">
                <input
                  type="color"
                  :value="currentTextColor || '#000000'"
                  class="h-8 w-8 cursor-pointer rounded border border-input p-0.5"
                  @input="applyTextColor(($event.target as HTMLInputElement).value)"
                />
                <Input
                  :model-value="currentTextColor"
                  placeholder="#000000"
                  class="h-7 font-mono text-xs"
                  maxlength="7"
                  @blur="applyTextColor(($event.target as HTMLInputElement).value)"
                />
              </div>
              <Button type="button" variant="ghost" size="sm" class="mt-2 w-full text-xs" @click="clearTextColor">
                <X class="mr-1 h-3 w-3" />Clear color
              </Button>
            </PopoverContent>
          </Popover>

          <!-- Highlight / background color -->
          <Popover>
            <PopoverTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(!!currentBgColor)" title="Highlight color">
                <Highlighter class="h-4 w-4" :style="currentBgColor ? { color: currentBgColor } : {}" />
              </Button>
            </PopoverTrigger>
            <PopoverContent class="w-52 p-3" align="start">
              <p class="mb-2 text-xs font-medium text-muted-foreground">Highlight color</p>
              <div class="flex items-center gap-2">
                <input
                  type="color"
                  :value="currentBgColor || '#ffff00'"
                  class="h-8 w-8 cursor-pointer rounded border border-input p-0.5"
                  @input="applyBgColor(($event.target as HTMLInputElement).value)"
                />
                <Input
                  :model-value="currentBgColor"
                  placeholder="#ffff00"
                  class="h-7 font-mono text-xs"
                  maxlength="7"
                  @blur="applyBgColor(($event.target as HTMLInputElement).value)"
                />
              </div>
              <Button type="button" variant="ghost" size="sm" class="mt-2 w-full text-xs" @click="clearBgColor">
                <X class="mr-1 h-3 w-3" />Clear highlight
              </Button>
            </PopoverContent>
          </Popover>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <!-- Link -->
          <Button type="button" variant="ghost" :class="btnClass(isActive('link'))" title="Insert / edit link"
            @click="openLinkDialog"><Link2 class="h-4 w-4" /></Button>

          <!-- Image -->
          <Button type="button" variant="ghost" :class="btnClass(isActive('image'))" title="Insert / edit image"
            @click="openImageDialog"><ImageIcon class="h-4 w-4" /></Button>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <!-- Lists -->
          <DropdownMenu>
            <DropdownMenuTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(isActive('bulletList') || isActive('orderedList'))" title="Lists">
                <component :is="isActive('orderedList') ? ListOrdered : List" class="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" class="w-40">
              <DropdownMenuItem :class="isActive('bulletList') ? 'bg-muted' : ''" @click="toggleList('bullet')">
                <List class="mr-2 h-4 w-4" />Bullet list
              </DropdownMenuItem>
              <DropdownMenuItem :class="isActive('orderedList') ? 'bg-muted' : ''" @click="toggleList('ordered')">
                <ListOrdered class="mr-2 h-4 w-4" />Ordered list
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          <div class="mx-0.5 h-5 w-px bg-border/60 shrink-0" />

          <!-- Table -->
          <DropdownMenu>
            <DropdownMenuTrigger as-child>
              <Button type="button" variant="ghost" :class="btnClass(isActive('table'))" title="Table">
                <Table2 class="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" class="w-52">
              <!-- Create-table grid (only when not inside a table) -->
              <template v-if="!isActive('table')">
                <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">
                  Insert {{ tableHoverRows }}×{{ tableHoverCols }} table
                </DropdownMenuLabel>
                <div class="px-2 pb-2">
                  <div class="grid gap-0.5" style="grid-template-columns: repeat(6, 1.25rem)">
                    <button
                      v-for="cell in tableGridCells"
                      :key="`${cell.r}-${cell.c}`"
                      type="button"
                      class="h-5 w-5 rounded-sm border transition-colors"
                      :class="cell.r <= tableHoverRows && cell.c <= tableHoverCols
                        ? 'bg-primary border-primary'
                        : 'bg-muted/50 border-border hover:bg-muted'"
                      @mouseover="tableHoverRows = cell.r; tableHoverCols = cell.c"
                      @click="insertTable"
                    />
                  </div>
                </div>
                <DropdownMenuSeparator />
              </template>

              <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">Column</DropdownMenuLabel>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().addColumnBefore().run()">
                <ArrowLeft class="mr-2 h-4 w-4" />Add before
              </DropdownMenuItem>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().addColumnAfter().run()">
                <ArrowRight class="mr-2 h-4 w-4" />Add after
              </DropdownMenuItem>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().deleteColumn().run()">
                <Trash2 class="mr-2 h-4 w-4" />Delete column
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">Row</DropdownMenuLabel>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().addRowBefore().run()">
                <ArrowUp class="mr-2 h-4 w-4" />Add before
              </DropdownMenuItem>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().addRowAfter().run()">
                <ArrowDown class="mr-2 h-4 w-4" />Add after
              </DropdownMenuItem>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().deleteRow().run()">
                <Trash2 class="mr-2 h-4 w-4" />Delete row
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuLabel class="text-xs uppercase text-muted-foreground">Cell</DropdownMenuLabel>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().mergeCells().run()">
                <Combine class="mr-2 h-4 w-4" />Merge cells
              </DropdownMenuItem>
              <DropdownMenuItem :disabled="!isActive('table')" @click="editor?.chain().focus().splitCell().run()">
                <Scissors class="mr-2 h-4 w-4" />Split cell
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem
                :disabled="!isActive('table')"
                class="text-destructive focus:text-destructive"
                @click="editor?.chain().focus().deleteTable().run()"
              >
                <Trash2 class="mr-2 h-4 w-4" />Delete table
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
      </div>

      <!-- ----------------------------------------------------------------- -->
      <!-- Editor content area                                                -->
      <!-- ----------------------------------------------------------------- -->
      <EditorContent
        v-if="editor"
        :editor="editor"
        :style="contentStyle"
        class="html-editor-content prose prose-sm max-w-none px-3 py-2 dark:prose-invert
               [&_.ProseMirror]:min-h-full [&_.ProseMirror]:outline-none"
        data-testid="html-editor-content"
        @click="editor?.chain().focus().run()"
      />
    </div>

    <!-- ------------------------------------------------------------------- -->
    <!-- Link dialog                                                          -->
    <!-- ------------------------------------------------------------------- -->
    <Dialog v-model:open="linkDialogOpen">
      <DialogContent class="max-w-md">
        <DialogHeader>
          <DialogTitle>{{ isEditingLink ? 'Edit link' : 'Insert link' }}</DialogTitle>
        </DialogHeader>
        <div class="grid gap-4 py-2">
          <div class="grid gap-1.5">
            <Label for="html-editor-link-url">URL</Label>
            <Input id="html-editor-link-url" v-model="linkUrl" placeholder="https://example.com" />
          </div>
          <div class="grid gap-1.5">
            <Label for="html-editor-link-text">Link text</Label>
            <Input id="html-editor-link-text" v-model="linkText" placeholder="Link text" />
          </div>
          <div class="flex items-center gap-2">
            <Checkbox
              id="html-editor-link-target"
              :checked="linkTargetBlank"
              @update:checked="linkTargetBlank = !!$event"
            />
            <Label for="html-editor-link-target" class="cursor-pointer font-normal">Open in new tab</Label>
          </div>
        </div>
        <DialogFooter class="flex-row items-center justify-between gap-2">
          <Button v-if="isEditingLink" type="button" variant="destructive" size="sm" @click="removeLink">
            <Trash2 class="mr-1.5 h-3.5 w-3.5" />Remove link
          </Button>
          <div class="flex gap-2">
            <Button type="button" variant="outline" size="sm" @click="linkDialogOpen = false">Cancel</Button>
            <Button type="button" size="sm" @click="applyLink">Apply</Button>
          </div>
        </DialogFooter>
      </DialogContent>
    </Dialog>

    <!-- ------------------------------------------------------------------- -->
    <!-- Image dialog                                                         -->
    <!-- ------------------------------------------------------------------- -->
    <Dialog v-model:open="imageDialogOpen">
      <DialogContent class="max-w-md">
        <DialogHeader>
          <DialogTitle>{{ isEditingImage ? 'Edit image' : 'Insert image' }}</DialogTitle>
        </DialogHeader>
        <div class="grid gap-4 py-2">
          <div class="grid gap-1.5">
            <Label for="html-editor-img-url">Image URL</Label>
            <Input id="html-editor-img-url" v-model="imageUrl" placeholder="https://example.com/image.jpg" />
          </div>
          <div class="grid gap-1.5">
            <Label for="html-editor-img-alt">Alt text</Label>
            <Input id="html-editor-img-alt" v-model="imageAlt" placeholder="Describe the image" />
          </div>
          <div class="grid gap-1.5">
            <Label for="html-editor-img-style">
              Style <span class="ml-1 text-xs font-normal text-muted-foreground">(optional)</span>
            </Label>
            <Input id="html-editor-img-style" v-model="imageStyle" placeholder="width: 100%; height: auto;" />
          </div>
        </div>
        <DialogFooter>
          <Button type="button" variant="outline" size="sm" @click="imageDialogOpen = false">Cancel</Button>
          <Button type="button" size="sm" @click="applyImage">
            {{ isEditingImage ? 'Update' : 'Insert' }}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>

    <!-- ------------------------------------------------------------------- -->
    <!-- HTML source dialog                                                   -->
    <!-- ------------------------------------------------------------------- -->
    <Dialog v-model:open="sourceDialogOpen">
      <DialogContent class="max-w-3xl">
        <DialogHeader>
          <DialogTitle>Edit HTML source</DialogTitle>
        </DialogHeader>
        <textarea
          v-model="sourceHtml"
          class="h-96 w-full resize-y rounded-md border border-input bg-background p-3 font-mono text-xs
                 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
          spellcheck="false"
          data-testid="html-editor-source"
        />
        <DialogFooter>
          <Button type="button" variant="outline" size="sm" @click="sourceDialogOpen = false">Cancel</Button>
          <Button type="button" size="sm" @click="applySource">Apply changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  </FieldFrame>
</template>

<style scoped>
/* Table chrome */
:deep(.html-editor-content table) {
  border-collapse: collapse;
  margin: 0.5rem 0;
  overflow: hidden;
  table-layout: fixed;
  width: 100%;
}

:deep(.html-editor-content table td),
:deep(.html-editor-content table th) {
  border: 2px solid var(--border);
  box-sizing: border-box;
  min-width: 1em;
  padding: 0.375rem 0.5rem;
  position: relative;
  vertical-align: top;
}

:deep(.html-editor-content table th) {
  background-color: var(--muted);
  font-weight: 600;
  text-align: left;
}

:deep(.html-editor-content table .selectedCell::after) {
  background: color-mix(in oklch, var(--primary) 15%, transparent);
  content: "";
  inset: 0;
  pointer-events: none;
  position: absolute;
  z-index: 2;
}

:deep(.html-editor-content .tableWrapper) {
  overflow-x: auto;
}

:deep(.html-editor-content .column-resize-handle) {
  background-color: var(--primary);
  bottom: -2px;
  pointer-events: none;
  position: absolute;
  right: -2px;
  top: 0;
  width: 4px;
}

:deep(.html-editor-content .resize-cursor) {
  cursor: col-resize;
}

/* Content styling */
:deep(.html-editor-content a) {
  color: var(--primary);
  text-decoration: underline;
}

:deep(.html-editor-content img) {
  display: block;
  margin: 0.75rem 0;
}

:deep(.html-editor-content img.ProseMirror-selectednode) {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

:deep(.html-editor-content blockquote) {
  border-left: 3px solid var(--border);
  color: var(--muted-foreground);
  font-style: italic;
  margin-left: 0;
  padding-left: 1rem;
}

:deep(.html-editor-content .ProseMirror-focused) {
  outline: none;
}
</style>
