import { Extension, type Editor } from "@tiptap/core"
import { Plugin, PluginKey } from "@tiptap/pm/state"
import type { Node as ProseMirrorNode } from "@tiptap/pm/model"
import { parseGraphicAttrs, type DocsGraphicAttrs } from "@/lib/drive/docs-graphic-types"

export const docsGraphicAnchorSyncKey = new PluginKey("docsGraphicAnchorSync")

function isGraphicNode(node: ProseMirrorNode): boolean {
  return node.type.name === "docsGraphic" || node.type.name === "docsInlineGraphic"
}

function syncMoveWithTextGraphic(
  node: ProseMirrorNode,
  mappedAnchorPos: number
): Partial<DocsGraphicAttrs> | null {
  const attrs = parseGraphicAttrs(node.attrs as Record<string, unknown>)
  if (attrs.positionMode !== "move-with-text") return null
  if (attrs.anchorPos < 0) return { anchorPos: mappedAnchorPos }
  if (mappedAnchorPos < 0) {
    return {
      positionMode: "fixed-on-page",
      placement: "absolute",
      anchorPos: -1,
    }
  }
  if (mappedAnchorPos !== attrs.anchorPos) {
    return { anchorPos: mappedAnchorPos }
  }
  return null
}

/** Select a graphic under the pointer (Alt+click), including behind-text overlays. */
function selectGraphicAtPointer(editor: Editor, event: MouseEvent): boolean {
  if (!event.altKey) return false
  const stack = document.elementsFromPoint(event.clientX, event.clientY)
  for (const el of stack) {
    if (!(el instanceof HTMLElement)) continue
    const graphic = el.classList.contains("docs-graphic")
      ? el
      : (el.closest(".docs-graphic") as HTMLElement | null)
    const host = graphic?.hasAttribute("data-graphic-pos")
      ? graphic
      : (graphic?.closest("[data-graphic-pos]") as HTMLElement | null)
    if (!host) continue
    const posRaw = host.getAttribute("data-graphic-pos")
    const pos = posRaw ? Number(posRaw) : NaN
    if (!Number.isFinite(pos)) continue
    editor.chain().focus().setNodeSelection(pos).run()
    event.preventDefault()
    event.stopPropagation()
    return true
  }
  return false
}

/** Keeps move-with-text anchor positions mapped through document edits. */
export const DocsGraphicAnchorSync = Extension.create({
  name: "docsGraphicAnchorSync",

  addProseMirrorPlugins() {
    const editor = this.editor
    return [
      new Plugin({
        key: docsGraphicAnchorSyncKey,
        props: {
          handleDOMEvents: {
            mousedown(_view, event) {
              return selectGraphicAtPointer(editor, event)
            },
          },
        },
        appendTransaction(transactions, _oldState, newState) {
          if (!transactions.some((tr) => tr.docChanged)) return null

          let tr = newState.tr
          let changed = false

          newState.doc.descendants((node, pos) => {
            if (!isGraphicNode(node)) return
            const attrs = parseGraphicAttrs(node.attrs as Record<string, unknown>)
            if (attrs.positionMode !== "move-with-text" || attrs.anchorPos < 0) return

            const mapped = transactions.reduce((anchor, transaction) => {
              if (!transaction.docChanged) return anchor
              return transaction.mapping.map(anchor)
            }, attrs.anchorPos)

            const patch = syncMoveWithTextGraphic(node, mapped)
            if (patch) {
              tr = tr.setNodeMarkup(pos, undefined, { ...node.attrs, ...patch })
              changed = true
            }
          })

          return changed ? tr : null
        },
      }),
    ]
  },
})
