import type { Editor } from "@tiptap/core"
import type { ResizeHandle } from "./docs-graphic-layout.ts"
import { moveInFlowGraphic } from "./docs-graphic-flow-move.ts"
import { usesPageLayer } from "./docs-graphic-position.ts"
import {
  clearDocsGraphicSnapGuides,
  setDocsGraphicSnapGuides,
} from "./docs-graphic-snap-bridge.ts"
import {
  collectOtherPageGraphicRects,
  readPageSnapContextFromStack,
  snapMoveRect,
  snapResizeRect,
} from "./docs-graphic-snap.ts"
import {
  parseGraphicAttrs,
  type DocsGraphicAttrs,
  type DocsGraphicFloatSide,
} from "./docs-graphic-types.ts"

export const GRAPHIC_KEYBOARD_MIN_SIZE = 24

export type GraphicKeyboardAction =
  | { type: "move"; dx: number; dy: number }
  | { type: "resize"; dw: number; dh: number }
  | { type: "rotate"; deltaDeg: number }
  | { type: "layer"; direction: "forward" | "backward" }

export function readActiveGraphicAttrs(editor: Editor): DocsGraphicAttrs | null {
  const name = editor.isActive("docsInlineGraphic")
    ? "docsInlineGraphic"
    : editor.isActive("docsGraphic")
      ? "docsGraphic"
      : null
  if (!name) return null
  return parseGraphicAttrs(editor.getAttributes(name) as Record<string, unknown>)
}

export function isGraphicNodeActive(editor: Editor): boolean {
  return readActiveGraphicAttrs(editor) != null
}

const FLOAT_SIDE_ORDER: DocsGraphicFloatSide[] = ["left", "center", "right"]

/** Graphics anchored in the text flow (mode « Avec le texte »). */
export function isInFlowGraphic(attrs: DocsGraphicAttrs): boolean {
  return attrs.positionMode === "move-with-text" && !usesPageLayer(attrs)
}

export function cycleFloatSide(
  current: DocsGraphicFloatSide,
  direction: "left" | "right"
): DocsGraphicFloatSide {
  const index = FLOAT_SIDE_ORDER.indexOf(current)
  const step = direction === "right" ? 1 : -1
  return FLOAT_SIDE_ORDER[(index + step + FLOAT_SIDE_ORDER.length) % FLOAT_SIDE_ORDER.length]!
}

function clampSize(value: number): number {
  return Math.max(GRAPHIC_KEYBOARD_MIN_SIZE, Math.round(value))
}

let keyboardSnapGuideTimer: ReturnType<typeof setTimeout> | null = null

function keyboardResizeHandle(dw: number, dh: number): ResizeHandle {
  if (dw < 0 && dh < 0) return "nw"
  if (dw > 0 && dh < 0) return "ne"
  if (dw < 0 && dh > 0) return "sw"
  if (dw > 0 && dh > 0) return "se"
  if (dw < 0) return "w"
  if (dw > 0) return "e"
  if (dh < 0) return "n"
  return "s"
}

function readPageLayerSnapContext(editor: Editor, attrs: DocsGraphicAttrs) {
  if (typeof document === "undefined" || !usesPageLayer(attrs)) return null
  const stack = document.querySelector("[data-docs-page-stack]") as HTMLElement | null
  const pos = editor.state.selection.from
  const otherRects = collectOtherPageGraphicRects(
    editor.state.doc,
    attrs.pageIndex,
    pos
  )
  return readPageSnapContextFromStack(stack, otherRects)
}

function flashKeyboardSnapGuides(
  pageIndex: number,
  pageWidth: number,
  pageHeight: number,
  guides: ReturnType<typeof snapMoveRect>["guides"]
) {
  if (guides.length === 0) {
    clearDocsGraphicSnapGuides()
    return
  }
  setDocsGraphicSnapGuides({ pageIndex, pageWidth, pageHeight, guides })
  if (keyboardSnapGuideTimer) clearTimeout(keyboardSnapGuideTimer)
  keyboardSnapGuideTimer = setTimeout(() => {
    clearDocsGraphicSnapGuides()
    keyboardSnapGuideTimer = null
  }, 650)
}

function applyPageLayerSnap(
  attrs: DocsGraphicAttrs,
  patch: Partial<DocsGraphicAttrs>,
  editor: Editor,
  action: Extract<GraphicKeyboardAction, { type: "move" | "resize" }>
): Partial<DocsGraphicAttrs> {
  const ctx = readPageLayerSnapContext(editor, attrs)
  if (!ctx) return patch

  const rect = {
    x: patch.pageX ?? attrs.pageX,
    y: patch.pageY ?? attrs.pageY,
    width: patch.width ?? attrs.width,
    height: patch.height ?? attrs.height,
  }

  const snapped =
    action.type === "resize"
      ? snapResizeRect(keyboardResizeHandle(action.dw, action.dh), rect, ctx)
      : snapMoveRect(rect, ctx)

  flashKeyboardSnapGuides(attrs.pageIndex, ctx.pageWidth, ctx.pageHeight, snapped.guides)

  return {
    ...patch,
    pageX: snapped.rect.x,
    pageY: snapped.rect.y,
    width: snapped.rect.width,
    height: snapped.rect.height,
  }
}

export function applyGraphicKeyboardAction(
  attrs: DocsGraphicAttrs,
  action: GraphicKeyboardAction
): Partial<DocsGraphicAttrs> | null {
  const onPage = usesPageLayer(attrs)

  if (action.type === "move") {
    if (onPage) {
      return {
        pageX: Math.round(attrs.pageX + action.dx),
        pageY: Math.round(attrs.pageY + action.dy),
      }
    }
    if (attrs.placement === "absolute") {
      return {
        x: Math.round(attrs.x + action.dx),
        y: Math.round(attrs.y + action.dy),
      }
    }
    // In-flow graphics use document move / float-side cycling (see runGraphicKeyboardAction).
    return null
  }

  if (action.type === "rotate") {
    return { rotationDeg: attrs.rotationDeg + action.deltaDeg }
  }

  if (action.type === "resize") {
    let width = attrs.width
    let height = attrs.height
    let shiftX = 0
    let shiftY = 0

    if (action.dw !== 0) {
      const nextW = clampSize(width + action.dw)
      if (action.dw < 0) shiftX = width - nextW
      width = nextW
    }
    if (action.dh !== 0) {
      const nextH = clampSize(height + action.dh)
      if (action.dh < 0) shiftY = height - nextH
      height = nextH
    }

    const patch: Partial<DocsGraphicAttrs> = { width, height }

    if (onPage) {
      if (shiftX !== 0) patch.pageX = Math.round(attrs.pageX + shiftX)
      if (shiftY !== 0) patch.pageY = Math.round(attrs.pageY + shiftY)
    } else if (shiftX !== 0 || shiftY !== 0) {
      if (shiftX !== 0) patch.x = Math.round(attrs.x + shiftX)
      if (shiftY !== 0) patch.y = Math.round(attrs.y + shiftY)
    }
    return patch
  }

  return null
}

export function graphicKeyboardActionFromKey(event: KeyboardEvent): GraphicKeyboardAction | null {
  const step = event.shiftKey ? 10 : 1
  const resizeStep = event.shiftKey ? 20 : 5
  const mod = event.metaKey || event.ctrlKey

  if (mod && !event.altKey) {
    if (event.key === "]" || event.key === "}") {
      return { type: "layer", direction: "forward" }
    }
    if (event.key === "[" || event.key === "{") {
      return { type: "layer", direction: "backward" }
    }
  }

  if (event.altKey && !mod) {
    switch (event.key) {
      case "ArrowLeft":
        return { type: "resize", dw: -resizeStep, dh: 0 }
      case "ArrowRight":
        return { type: "resize", dw: resizeStep, dh: 0 }
      case "ArrowUp":
        return { type: "resize", dw: 0, dh: -resizeStep }
      case "ArrowDown":
        return { type: "resize", dw: 0, dh: resizeStep }
      default:
        return null
    }
  }

  if (!event.altKey && !mod) {
    switch (event.key) {
      case "ArrowLeft":
        return { type: "move", dx: -step, dy: 0 }
      case "ArrowRight":
        return { type: "move", dx: step, dy: 0 }
      case "ArrowUp":
        return { type: "move", dx: 0, dy: -step }
      case "ArrowDown":
        return { type: "move", dx: 0, dy: step }
      case "[":
        return { type: "rotate", deltaDeg: event.shiftKey ? -15 : -5 }
      case "]":
        return { type: "rotate", deltaDeg: event.shiftKey ? 15 : 5 }
      default:
        return null
    }
  }

  return null
}

export function runGraphicKeyboardAction(editor: Editor, event: KeyboardEvent): boolean {
  if (typeof document !== "undefined" && document.querySelector(".docs-graphic--cropping")) {
    return false
  }

  const attrs = readActiveGraphicAttrs(editor)
  if (!attrs) return false

  const action = graphicKeyboardActionFromKey(event)
  if (!action) return false

  if (action.type === "layer") {
    const ok =
      action.direction === "forward"
        ? editor.chain().focus().bringDocsGraphicForward().run()
        : editor.chain().focus().sendDocsGraphicBackward().run()
    if (ok) event.preventDefault()
    return ok
  }

  if (isInFlowGraphic(attrs) && action.type === "move") {
    if (action.dy !== 0) {
      moveInFlowGraphic(editor, action.dy < 0 ? "up" : "down")
      // Always consume ↑↓ so the caret does not leave the selected graphic.
      event.preventDefault()
      return true
    }
    if (action.dx !== 0) {
      const nextSide = cycleFloatSide(attrs.floatSide, action.dx > 0 ? "right" : "left")
      const ok = editor.chain().focus().setDocsGraphicFloatSide(nextSide).run()
      if (ok) event.preventDefault()
      return ok
    }
  }

  const patch = applyGraphicKeyboardAction(attrs, action)
  if (!patch) return false

  const snappedPatch =
    usesPageLayer(attrs) && (action.type === "move" || action.type === "resize")
      ? applyPageLayerSnap(attrs, patch, editor, action)
      : patch

  const ok = editor.chain().focus().updateDocsGraphic(snappedPatch).run()
  if (ok) event.preventDefault()
  return ok
}
