import { useCallback, useRef, useEffect } from 'react'
import { useDominoStore, useDominoStoreInstance } from './domino-hooks'
import type { DominoEvents, SceneElement, ArtboardElement } from './domino'
import { DOMINO_EL_PREFIX } from './constants'
import { useTransform } from './use-transform'
import { getElementWorldRect } from './math'
import { calculateSnap } from './interactions/snapping'
import { calculateResize } from './interactions/resizing'

function isTargetInputOrEditable(target: HTMLElement | null) {
  if (!target) return false

  // 1. 标准输入元素
  const tagName = target.tagName.toLowerCase()
  if (tagName === 'input' || tagName === 'textarea') return true

  // 2. 可编辑元素
  return target.isContentEditable
}

type InteractionStatus =
  | 'none'
  | 'idle'
  | 'panning'
  | 'selecting'
  | 'dragging'
  | 'resizing'
  | 'rotating'

interface InteractionOptions extends DominoEvents {}

export function useInteractions(
  containerRef: React.RefObject<HTMLElement | null>,
  options: InteractionOptions,
) {
  const { onClick, onElementClick, onTransformEnd } = options
  const store = useDominoStoreInstance()
  const statusRef = useRef<InteractionStatus>('none')
  const startPos = useRef({ x: 0, y: 0 }) // Screen coordinates
  const lastPos = useRef({ x: 0, y: 0 }) // Screen coordinates

  const { screenToWorld } = useTransform()
  const moveViewport = useDominoStore(state => state.moveViewport)
  const setSelectionBox = useDominoStore(state => state.setSelectionBox)
  const setSnapLines = useDominoStore(state => state.setSnapLines)
  const setFocusedElementId = useDominoStore(state => state.setFocusedElementId)
  const setSelectedIds = useDominoStore(state => state.setSelectedIds)

  /**
   * 直接通过 DOM API 更新容器状态，避免 React 频繁重绘
   */
  const updateDOMOverlay = useCallback(
    (gestureStatus: InteractionStatus = 'none', updateCursor = true) => {
      const container = containerRef.current
      if (!container) return

      const state = store.getState()
      const mainStatus = statusRef.current

      // 更新类名：支持主状态和手势状态叠加
      const statuses: InteractionStatus[] = [
        'panning',
        'selecting',
        'dragging',
        'resizing',
        'rotating',
      ]
      statuses.forEach(s => {
        const isActive = s === mainStatus || s === gestureStatus
        container.classList.toggle(`is-${s}`, isActive)
      })

      // 更新光标：仅由主状态 (PointerDown) 和工具模式 (Mode) 决定
      if (updateCursor) {
        if (mainStatus === 'panning') {
          container.style.cursor = 'grabbing'
        } else if (mainStatus === 'none') {
          // 恢复到工具模式对应的光标
          container.style.cursor =
            state.mode === 'pan' ? 'grab' : 'url(/arrow-cursor.svg), auto'
        }
      }
    },
    [containerRef, store],
  )

  const setStatus = useCallback(
    (newStatus: InteractionStatus, updateCursor = true) => {
      statusRef.current = newStatus
      updateDOMOverlay('none', updateCursor)
    },
    [updateDOMOverlay],
  )

  // 监听交互模式切换（手动订阅以保持 updateDOMOverlay 恒定）
  useEffect(() => {
    // 初始执行一次
    updateDOMOverlay('none', true)

    // 监听 store 中的模式变化
    let lastMode = store.getState().mode
    const unsubscribe = store.subscribe(state => {
      if (state.mode !== lastMode) {
        lastMode = state.mode
        updateDOMOverlay('none', true)
      }
    })
    return unsubscribe
  }, [updateDOMOverlay, store])

  const resizeHandle = useRef<string | null>(null) // 'nw', 'ne', 'sw', 'se', 'ml', 'mr'
  const startRotation = useRef(0)
  const startAngle = useRef(0)
  const dragStartElements = useRef<
    Record<
      string,
      {
        x: number
        y: number
        width: number
        height: number
        fontSize?: number
        originalParentId?: string
        originalIndex?: number
      }
    >
  >({})
  const isSnappedToOriginal = useRef(false)
  const hasMoved = useRef(false)

  const onDoubleClickCapture = useCallback((e: React.MouseEvent) => {
    const target = e.target as HTMLElement
    if (isTargetInputOrEditable(target)) return

    const state = store.getState()
    const { elements: currentElements, selectedIds, focusedElementId } = state
    const elementId = focusedElementId || selectedIds[0]
    const element = elementId ? currentElements[elementId] : null

    if (elementId && element?.type === 'text') {
      setFocusedElementId(elementId)
      setSelectedIds(selectedIds.filter(id => id !== elementId))
    }
  }, [])

  const onPointerDown = useCallback(
    (e: React.PointerEvent) => {
      // Ignore right click for interactions to prevent conflict with browser context menu
      if (e.button === 2) return

      const target = e.target as HTMLElement
      if (isTargetInputOrEditable(target)) return

      const state = store.getState()
      const {
        elements: currentElements,
        placeholders: currentPlaceholders,
        selectedIds: currentSelectedIds,
        mode: currentMode,
        readOnly: isReadOnly,
      } = state

      const elementEl = target.closest('.domino-element') as HTMLElement | null
      const rawId = elementEl?.id
      const elementId = rawId?.startsWith(DOMINO_EL_PREFIX)
        ? rawId.slice(DOMINO_EL_PREFIX.length)
        : rawId
      const element = elementId
        ? currentElements[elementId] || currentPlaceholders[elementId]
        : null
      const isElement = !!(element && element.selectable !== false)

      const handleEl = target.closest(
        '.domino-resize-handle',
      ) as HTMLElement | null
      const rotateEl = target.closest(
        '.domino-rotate-handle',
      ) as HTMLElement | null
      const multiSelectEl = target.closest(
        '.domino-multi-select-area',
      ) as HTMLElement | null
      const isBackground = target === containerRef.current

      // Determine interaction type based on target and current state
      let interactionType: InteractionStatus = 'idle'

      if (isReadOnly) {
        // If read-only, only allow panning or element focus/click
        if (
          e.button === 1 ||
          (e.button === 0 && e.altKey) ||
          currentMode === 'pan'
        ) {
          interactionType = 'panning'
        } else if (isElement && elementId) {
          // Allow focusing/clicking elements even if read-only
          state.setFocusedElementId(elementId)
          if (!e.shiftKey) {
            state.setSelectedIds([elementId])
          }
          // Stay in 'idle' for click detection in onPointerUp
          interactionType = 'idle'
        } else {
          // No interaction for other cases in read-only mode
          return
        }
      } else {
        // Not read-only
        if (
          e.button === 1 ||
          (e.button === 0 && e.altKey) ||
          currentMode === 'pan'
        ) {
          interactionType = 'panning'
        } else if (isElement && elementId) {
          const id = elementId
          const uiState = state.elementUIStates[id]

          // CRITICAL: If the element is already focused (editing text),
          // we MUST BALE OUT to allow native cursor movement and avoid clearing focus.
          if (
            id === state.focusedElementId &&
            element.type === 'text' &&
            !store.getState().selectedIds.includes(id)
          ) {
            setStatus('none')
            return
          }

          if (uiState?.status === 'readonly') {
            state.setFocusedElementId(id)
            if (!e.shiftKey) {
              state.setSelectedIds([id])
            }
            // Stay in 'idle' for click detection in onPointerUp
            interactionType = 'idle'
          } else if (element.type === 'placeholder') {
            // Placeholder: Focusable but not selectable
            state.setFocusedElementId(id)
            if (!e.shiftKey) {
              state.setSelectedIds([])
            }
            // Stay in 'idle' for click detection in onPointerUp
            interactionType = 'idle'
          } else {
            // Element interaction: clicking selects it (shows dots)
            state.setFocusedElementId(null)

            let targetIds = currentSelectedIds
            if (e.shiftKey) {
              if (currentSelectedIds.includes(id)) {
                // Toggle off
                targetIds = currentSelectedIds.filter(sid => sid !== id)
              } else {
                // Toggle on
                targetIds = [...currentSelectedIds, id]
              }
              state.setSelectedIds(targetIds)
            } else {
              if (!currentSelectedIds.includes(id)) {
                // New single selection
                targetIds = [id]
                state.setSelectedIds(targetIds)
              }
              // If already selected, we keep targetIds = selectedIds to allow group drag
            }

            // Only start dragging if the clicked element is part of the final selection
            if (targetIds.includes(id)) {
              const draggableIds = targetIds.filter(tid => {
                const el = currentElements[tid] || currentPlaceholders[tid]
                return el?.draggable !== false
              })
              if (draggableIds.length === 0) {
                setStatus('none')
                return
              }
              // Store initial positions for all elements to be moved
              const startPositions: Record<
                string,
                {
                  x: number
                  y: number
                  width: number
                  height: number
                  originalParentId?: string
                  originalIndex?: number
                }
              > = {}
              draggableIds.forEach(did => {
                const el = currentElements[did] || currentPlaceholders[did]
                if (el) {
                  startPositions[did] = {
                    x: el.x,
                    y: el.y,
                    width: el.width,
                    height: el.height,
                    originalParentId: el.parentId,
                    originalIndex: el.parentId
                      ? (
                          currentElements[el.parentId] as ArtboardElement
                        )?.childrenIds?.indexOf(did)
                      : state.elementOrder.indexOf(did),
                  }
                }
              })
              dragStartElements.current = startPositions
              interactionType = 'dragging'
            } else {
              interactionType = 'none'
            }
          }
        } else if (handleEl) {
          // Handle resizing
          interactionType = 'resizing'
          resizeHandle.current = handleEl.dataset.handle || ''
          const id = currentSelectedIds[0]
          const el = currentElements[id] || currentPlaceholders[id]
          if (el) {
            dragStartElements.current = {
              [id]: {
                x: el.x,
                y: el.y,
                width: el.width,
                height: el.height,
                fontSize: el.type === 'text' ? el.fontSize : undefined,
              },
            }
            isSnappedToOriginal.current = false
          }
        } else if (rotateEl) {
          // Handle rotating
          interactionType = 'rotating'
          const id = currentSelectedIds[0]
          const el = currentElements[id] || currentPlaceholders[id]
          if (el) {
            startRotation.current = el.rotation || 0
            const rect = containerRef.current?.getBoundingClientRect()
            const worldPos = screenToWorld(e.clientX, e.clientY, rect)
            const worldRect = getElementWorldRect(el, currentElements)
            const centerX = worldRect.x + worldRect.width / 2
            const centerY = worldRect.y + worldRect.height / 2
            startAngle.current = Math.atan2(
              worldPos.y - centerY,
              worldPos.x - centerX,
            )
          }
        } else if (multiSelectEl && currentSelectedIds.length > 1) {
          // Handle dragging for multiple selected elements
          const draggableIds = currentSelectedIds.filter(id => {
            const el = currentElements[id] || currentPlaceholders[id]
            return el?.draggable !== false
          })
          if (draggableIds.length > 0) {
            const startPositions: Record<
              string,
              {
                x: number
                y: number
                width: number
                height: number
                originalParentId?: string
                originalIndex?: number
              }
            > = {}
            draggableIds.forEach(id => {
              const el = currentElements[id] || currentPlaceholders[id]
              if (el) {
                startPositions[id] = {
                  x: el.x,
                  y: el.y,
                  width: el.width,
                  height: el.height,
                  originalParentId: el.parentId,
                  originalIndex: el.parentId
                    ? (
                        currentElements[el.parentId] as ArtboardElement
                      )?.childrenIds?.indexOf(id)
                    : state.elementOrder.indexOf(id),
                }
              }
            })
            dragStartElements.current = startPositions
            interactionType = 'dragging'
          } else {
            interactionType = 'none'
          }
        } else if (isBackground) {
          // Background interaction: only selection, no panning by default
          state.setFocusedElementId(null)
          if (!e.shiftKey) {
            state.setSelectedIds([])
          }
          interactionType = 'selecting'
        } else {
          // If not an element, not background, and not a special UI handle, then bail out.
          // This covers clicks on overlays that are not part of the canvas interaction.
          return
        }
      }

      startPos.current = { x: e.clientX, y: e.clientY }
      lastPos.current = { x: e.clientX, y: e.clientY }
      hasMoved.current = false

      if (containerRef.current) {
        containerRef.current.setPointerCapture(e.pointerId)
        containerRef.current.focus()
      }

      setStatus(interactionType)
    },
    [setStatus, containerRef, screenToWorld, store],
  )

  const onPointerMove = useCallback(
    (e: React.PointerEvent) => {
      const status = statusRef.current
      if (status === 'none') return

      const dx = e.clientX - lastPos.current.x
      const dy = e.clientY - lastPos.current.y

      // 距离阈值检测：防止微小手抖触发误操作
      const dist = Math.sqrt(
        (e.clientX - startPos.current.x) ** 2 +
          (e.clientY - startPos.current.y) ** 2,
      )
      const THRESHOLD = 3
      const isThresholdMet = dist >= THRESHOLD || hasMoved.current

      // 仅针对拖拽、缩放、旋转、框选应用阈值；平移（panning）保持即时响应
      if (status !== 'panning' && status !== 'idle' && !isThresholdMet) {
        return
      }

      e.preventDefault()

      const state = store.getState()
      const { scale } = state.viewport

      const {
        selectedIds: currentSelectedIds,
        elements: currentElements,
        placeholders: currentPlaceholders,
      } = state

      if (status === 'panning') {
        moveViewport(dx, dy)
        lastPos.current = { x: e.clientX, y: e.clientY }
      } else if (status === 'dragging') {
        const totalDx = (e.clientX - startPos.current.x) / scale
        const totalDy = (e.clientY - startPos.current.y) / scale

        // Only snap if not holding Command (Mac) or Control (Windows/Linux)
        const shouldSnap = !e.metaKey && !e.ctrlKey
        const { snapOffsetX, snapOffsetY, finalSnapLines } = calculateSnap(
          currentSelectedIds,
          dragStartElements.current,
          currentElements,
          currentPlaceholders,
          totalDx,
          totalDy,
          scale,
          shouldSnap,
        )

        state.setSnapLines(finalSnapLines)

        // 脱离/吸附画板逻辑
        const rect = containerRef.current?.getBoundingClientRect()
        const mouseWorld = screenToWorld(e.clientX, e.clientY, rect)

        // 查找当前鼠标下方的画板（排除当前选中的元素本身及其子树，避免循环嵌套）
        const artboardUnderMouse = Object.values(currentElements)
          .filter(
            el => el.type === 'artboard' && !currentSelectedIds.includes(el.id),
          )
          .reverse()
          .find(ab => {
            const abWorld = getElementWorldRect(ab, currentElements)
            return (
              mouseWorld.x >= abWorld.x &&
              mouseWorld.x <= abWorld.x + abWorld.width &&
              mouseWorld.y >= abWorld.y &&
              mouseWorld.y <= abWorld.y + abWorld.height
            )
          })

        // 按照当前的视觉层级顺序（从小到大）进行排序，避免在批量移动到同一个父级时层级被打乱
        const sortedSelectedIds = [...currentSelectedIds].sort((a, b) => {
          const elA = currentElements[a]
          const elB = currentElements[b]
          if (elA?.parentId === elB?.parentId) {
            const siblings = elA?.parentId
              ? (currentElements[elA.parentId] as ArtboardElement)?.childrenIds
              : state.elementOrder
            return siblings.indexOf(a) - siblings.indexOf(b)
          }
          return 0
        })

        sortedSelectedIds.forEach(id => {
          const el = currentElements[id]
          if (!el || el.type === 'artboard') return

          // 仅处理选中项中的顶层元素
          let isTopSelected = true
          let pId = el.parentId
          while (pId) {
            if (currentSelectedIds.includes(pId)) {
              isTopSelected = false
              break
            }
            pId = currentElements[pId]?.parentId || ''
          }
          if (!isTopSelected) return

          // 获取元素当前所属的顶层画板
          let currentArtboard: SceneElement | null = null
          let curr = el
          while (curr.parentId) {
            const p = currentElements[curr.parentId]
            if (!p) break
            if (p.type === 'artboard') {
              currentArtboard = p
              break
            }
            curr = p
          }

          const targetArtboardId = artboardUnderMouse?.id || null

          // 情况 1: 如果当前在某个画板内
          if (currentArtboard) {
            const cbWorld = getElementWorldRect(
              currentArtboard,
              currentElements,
            )
            const isInsideCurrent =
              mouseWorld.x >= cbWorld.x &&
              mouseWorld.x <= cbWorld.x + cbWorld.width &&
              mouseWorld.y >= cbWorld.y &&
              mouseWorld.y <= cbWorld.y + cbWorld.height

            // 如果鼠标移出了当前所在的画板，则执行切换（移入新画板或移回根级）
            if (!isInsideCurrent) {
              const worldRect = getElementWorldRect(el, currentElements)
              const initialWorldX = worldRect.x - totalDx
              const initialWorldY = worldRect.y - totalDy

              const startMeta = dragStartElements.current[id]
              const targetIndex =
                targetArtboardId === startMeta?.originalParentId
                  ? startMeta.originalIndex
                  : undefined

              state.moveElementToParent(id, targetArtboardId, targetIndex)

              // 关键：同步更新拖拽起始点引用，使其在新的坐标系下保持一致
              if (dragStartElements.current[id]) {
                let relX = initialWorldX
                let relY = initialWorldY
                if (targetArtboardId && artboardUnderMouse) {
                  const parentWorld = getElementWorldRect(
                    artboardUnderMouse,
                    currentElements,
                  )
                  relX -= parentWorld.x
                  relY -= parentWorld.y
                }
                dragStartElements.current[id].x = relX
                dragStartElements.current[id].y = relY
              }
            }
          } else if (targetArtboardId && el.parentId !== targetArtboardId) {
            // 情况 2: 如果当前不在画板内，且鼠标进入了一个画板
            const worldRect = getElementWorldRect(el, currentElements)
            const initialWorldX = worldRect.x - totalDx
            const initialWorldY = worldRect.y - totalDy

            const startMeta = dragStartElements.current[id]
            const targetIndex =
              targetArtboardId === startMeta?.originalParentId
                ? startMeta.originalIndex
                : undefined

            state.moveElementToParent(id, targetArtboardId, targetIndex)

            if (dragStartElements.current[id]) {
              let relX = initialWorldX
              let relY = initialWorldY
              if (artboardUnderMouse) {
                const parentWorld = getElementWorldRect(
                  artboardUnderMouse,
                  currentElements,
                )
                relX -= parentWorld.x
                relY -= parentWorld.y
              }
              dragStartElements.current[id].x = relX
              dragStartElements.current[id].y = relY
            }
          }
        })

        // Apply movement with snap offset
        currentSelectedIds.forEach(id => {
          const start = dragStartElements.current[id]
          if (start) {
            // Record history before first movement
            if (!hasMoved.current && currentElements[id]) {
              state.takeSnapshot()
              hasMoved.current = true
            }

            state.updateElement(id, {
              x: start.x + totalDx + snapOffsetX,
              y: start.y + totalDy + snapOffsetY,
            })
          }
        })
      } else if (status === 'resizing' && currentSelectedIds.length > 0) {
        const id = currentSelectedIds[0]
        const startEl = dragStartElements.current[id]
        if (!startEl) return

        const handle = resizeHandle.current
        if (!handle) return

        const el = currentElements[id] || currentPlaceholders[id]
        if (!el) return

        const totalWdx = (e.clientX - startPos.current.x) / scale
        const totalWdy = (e.clientY - startPos.current.y) / scale

        const { updates, isSnappedToOriginal: snapped } = calculateResize(
          id,
          startEl,
          el,
          handle,
          totalWdx,
          totalWdy,
          scale,
          e.shiftKey,
          e.altKey,
        )

        isSnappedToOriginal.current = snapped

        // Record history before first movement
        if (!hasMoved.current && currentElements[id]) {
          state.takeSnapshot()
          hasMoved.current = true
        }

        state.updateElement(id, updates)
      } else if (status === 'rotating' && currentSelectedIds.length > 0) {
        const id = currentSelectedIds[0]
        const el = currentElements[id] || currentPlaceholders[id]
        if (!el) return

        const rect = containerRef.current?.getBoundingClientRect()
        const worldPos = screenToWorld(e.clientX, e.clientY, rect)
        const worldRect = getElementWorldRect(el, currentElements)
        const centerX = worldRect.x + worldRect.width / 2
        const centerY = worldRect.y + worldRect.height / 2

        const currentAngle = Math.atan2(
          worldPos.y - centerY,
          worldPos.x - centerX,
        )
        const angleDiff = ((currentAngle - startAngle.current) * 180) / Math.PI

        let newRotation = startRotation.current + angleDiff

        // Snap to 15 degree increments if Shift is held
        if (e.shiftKey) {
          newRotation = Math.round(newRotation / 15) * 15
        } else {
          // Magnetic snapping to cardinal angles
          const snapAngles = [0, 45, 90, 135, 180, 225, 270, 315]
          const snapThreshold = 3
          const normalized = ((newRotation % 360) + 360) % 360
          for (const target of snapAngles) {
            let diff = normalized - target
            if (diff > 180) diff -= 360
            if (diff < -180) diff += 360
            if (Math.abs(diff) < snapThreshold) {
              newRotation -= diff
              break
            }
          }
        }

        // Record history before first movement
        if (!hasMoved.current && currentElements[id]) {
          state.takeSnapshot()
          hasMoved.current = true
        }

        state.updateElement(id, { rotation: newRotation })
      } else if (status === 'selecting') {
        const rect = containerRef.current?.getBoundingClientRect()
        const currentWorld = screenToWorld(e.clientX, e.clientY, rect)
        const startWorld = screenToWorld(
          startPos.current.x,
          startPos.current.y,
          rect,
        )

        const x = Math.min(startWorld.x, currentWorld.x)
        const y = Math.min(startWorld.y, currentWorld.y)
        const width = Math.abs(startWorld.x - currentWorld.x)
        const height = Math.abs(startWorld.y - currentWorld.y)

        const box = { x, y, width, height }
        state.setSelectionBox(box)
        state.setFocusedElementId(null)

        // Update selection in real-time
        const newSelectedIds = Object.values(currentElements)
          .filter(el => {
            if (
              el.type === 'artboard' ||
              el.type === 'placeholder' ||
              el.selectable === false
            )
              return false
            const worldRect = getElementWorldRect(el, currentElements)
            const elXMax = worldRect.x + worldRect.width
            const elYMax = worldRect.y + worldRect.height
            const boxXMax = x + width
            const boxYMax = y + height

            return !(
              elXMax < x ||
              worldRect.x > boxXMax ||
              elYMax < y ||
              worldRect.y > boxYMax
            )
          })
          .map(el => el.id)

        state.setSelectedIds(newSelectedIds)

        // Manual hover detection during selection (pointer is captured)
        const hovered = Object.values(currentElements)
          .reverse() // Check from top to bottom
          .find(el => {
            if (el.type === 'artboard' || el.selectable === false) return false
            const worldRect = getElementWorldRect(el, currentElements)
            return (
              currentWorld.x >= worldRect.x &&
              currentWorld.x <= worldRect.x + worldRect.width &&
              currentWorld.y >= worldRect.y &&
              currentWorld.y <= worldRect.y + worldRect.height
            )
          })
        state.setHoveredElementId(hovered ? hovered.id : null)
      }

      lastPos.current = { x: e.clientX, y: e.clientY }
    },
    [moveViewport, screenToWorld, store, containerRef],
  )

  const onPointerUp = useCallback(
    (e: React.PointerEvent) => {
      // ONLY handle if we started an interaction in onPointerDown
      if (statusRef.current === 'none') return

      const state = store.getState()
      const dist = Math.sqrt(
        (e.clientX - startPos.current.x) ** 2 +
          (e.clientY - startPos.current.y) ** 2,
      )

      // Click detection: distance moved is small
      if (dist < 5) {
        const target = e.target as HTMLElement
        const elementEl = target.closest('.domino-element')
        const id = elementEl?.id

        onClick?.(e, id)

        if (id) {
          const element = state.elements[id] || state.placeholders[id]
          if (element) {
            onElementClick?.(id, element)
          }
        }
      }

      // Transform end detection
      if (hasMoved.current) {
        onTransformEnd?.(state.selectedIds)
      }

      setStatus('none')
      hasMoved.current = false
      setSelectionBox(null)
      setSnapLines(null)
      if (containerRef.current) {
        containerRef.current.releasePointerCapture(e.pointerId)
      }
    },
    [
      containerRef,
      setSnapLines,
      setSelectionBox,
      setStatus,
      onClick,
      onElementClick,
      onTransformEnd,
      store,
    ],
  )

  const onPointerCancel = useCallback(
    (e: React.PointerEvent) => {
      setStatus('none')
      setSelectionBox(null)
      setSnapLines(null)
      if (containerRef.current) {
        containerRef.current.releasePointerCapture(e.pointerId)
      }
    },
    [setStatus, setSelectionBox, setSnapLines, containerRef],
  )

  return {
    onDoubleClickCapture,
    onPointerDown,
    onPointerMove,
    onPointerUp,
    onPointerCancel,
  }
}
