import { GapCursor } from "prosemirror-gapcursor"
import { TextSelection } from "prosemirror-state"
import type { EditorView } from "prosemirror-view"
import type { CodeBlockNavigationDelegate } from "@/composables/useCodeBlockView"
import { addFences } from "@/composables/useCodeBlockView"
import { isValidGapCursor } from "@/composables/useBlockFocus"

export function createCodeBlockDelegate(options: {
  getView: () => EditorView | null
  onBoundaryExit?: (direction: "up" | "down") => void
  log: ReturnType<typeof import("@/lib/logger").createLogger>
}): CodeBlockNavigationDelegate {
  const { getView, onBoundaryExit, log } = options

  return {
    requestNavigateOut(direction, nodePos) {
      log.info("requestNavigateOut", { direction, nodePos })
      const currentView = getView()
      if (!currentView) return
      const node = currentView.state.doc.nodeAt(nodePos)
      if (!node) return

      const targetPos =
        direction === "up" ? nodePos - 1 : nodePos + node.nodeSize

      if (direction === "up" && targetPos < 0) {
        log.info("requestNavigateOut — doc boundary up")
        onBoundaryExit?.("up")
        return
      }

      if (targetPos > currentView.state.doc.content.size) {
        log.info("requestNavigateOut — doc boundary down")
        onBoundaryExit?.("down")
        return
      }

      const $pos = currentView.state.doc.resolve(targetPos)
      let sel = TextSelection.near($pos, direction === "up" ? -1 : 1)
      if (!sel && isValidGapCursor($pos)) {
        log.info("requestNavigateOut — falling back to GapCursor")
        sel = new GapCursor($pos)
      }
      if (sel) {
        currentView.dispatch(
          currentView.state.tr.setSelection(sel).scrollIntoView(),
        )
        currentView.focus()
        return
      }

      log.info("requestNavigateOut — no valid target, delegating boundary exit")
      onBoundaryExit?.(direction)
    },

    requestRemoveBlock(nodePos) {
      log.info("requestRemoveBlock", { nodePos })
      const currentView = getView()
      if (!currentView) return
      const node = currentView.state.doc.nodeAt(nodePos)
      if (!node) return

      if (currentView.state.doc.childCount === 1) {
        log.info("requestRemoveBlock — single node, replacing with paragraph")
        const para = currentView.state.schema.nodes.paragraph!.create()
        const tr = currentView.state.tr.replaceWith(
          nodePos,
          nodePos + node.nodeSize,
          para,
        )
        currentView.dispatch(tr)
        currentView.focus()
        return
      }

      log.info("requestRemoveBlock — deleting code block")
      const tr = currentView.state.tr.delete(nodePos, nodePos + node.nodeSize)
      const $pos = tr.doc.resolve(nodePos)
      const sel = TextSelection.near($pos, -1)
      if (sel) {
        currentView.dispatch(tr.setSelection(sel).scrollIntoView())
        currentView.focus()
      }
    },

    requestInsertParagraphAround(direction, nodePos) {
      log.info("requestInsertParagraphAround", { direction, nodePos })
      const currentView = getView()
      if (!currentView) return
      const node = currentView.state.doc.nodeAt(nodePos)
      if (!node) return
      const tr = currentView.state.tr
      const para = currentView.state.schema.nodes.paragraph!.create()
      if (direction === "before") {
        tr.insert(nodePos, para)
        const $pos = tr.doc.resolve(nodePos + 1)
        tr.setSelection(TextSelection.near($pos))
      } else {
        const afterPos = nodePos + node.nodeSize
        tr.insert(afterPos, para)
        const $pos = tr.doc.resolve(afterPos + 1)
        tr.setSelection(TextSelection.near($pos))
      }
      currentView.dispatch(tr)
      currentView.focus()
    },

    requestChangeLanguage(newLanguage, body, nodePos) {
      log.info("requestChangeLanguage", { newLanguage, nodePos, bodyLength: body.length })
      const currentView = getView()
      if (!currentView) return
      const node = currentView.state.doc.nodeAt(nodePos)
      if (!node) return

      const fencedContent = addFences(body, newLanguage)
      const textNode = currentView.state.schema.text(fencedContent)
      const tr = currentView.state.tr
        .setNodeAttribute(nodePos, "language", newLanguage)
        .replaceWith(nodePos + 1, nodePos + node.nodeSize - 1, textNode)
      currentView.dispatch(tr)
      requestAnimationFrame(() => currentView.focus())
    },
  }
}
