/**
 * Board Interaction Manager - 白板交互管理器
 *
 * 职责：
 * - 工具系统（select/hand/shape/text/freehand/eraser）
 * - 元素拖拽、Resize
 * - 画布平移/缩放
 * - 框选
 * - 形状绘制
 * - 自由画笔
 *
 * 设计原则：
 * - 与渲染层解耦
 * - 事件驱动
 * - 工具状态机
 */

import type {
  Point, Rect, Viewport, ToolType,
  BoardInteractionEventType, BoardInteractionEvent,
  ResizeHandle, ElementType, FreehandElement, ArrowElement, LineElement,
} from '../core/types'

import type { BoardRenderer } from '../renderer/BoardRenderer'
import type { BoardModel } from '../core/BoardModel'

  /** 交互状态 */
interface InteractionState {
  isDragging: boolean
  isPanning: boolean
  isSelecting: boolean
  isDrawing: boolean       // 正在绘制形状
  isFreehanding: boolean   // 正在自由画笔
  isResizing: boolean      // 正在 resize
  dragStartPos: Point
  lastMousePos: Point
  selectionBox: Rect | null
  // 拖拽相关
  draggedElementId: string | null
  dragStartPositions: Map<string, Point>
  hasDuplicatedOnAltDrag: boolean  // Alt 拖拽复制已触发（防止重复）
  // Resize 相关
  resizeHandle: ResizeHandle | null
  resizeStartElement: { x: number; y: number; width: number; height: number } | null
  resizeStartPoints: { startPoint: Point; endPoint: Point } | null  // 线段端点拖拽时存储原始端点
  resizeStartControlPoints: { cp1: Point; cp2: Point } | null  // 贝塞尔控制点拖拽时存储原始值
  resizeStartFontSize: number | null  // Text 元素初始字体大小
  // 旋转相关
  rotateStartAngle: number | null  // 初始角度（鼠标与元素中心的连线角度）
  rotateStartRotation: number | null  // 元素初始旋转角度
  // 绘制相关
  drawingStartElement: Point | null
  // 自由画笔
  freehandPoints: Point[]
  // 画完一个一次性图形后自动切回选择工具
  wasJustDrawing: boolean
}

/** 当前工具（实例级） */

export class BoardInteractionManager {
  private canvas: HTMLCanvasElement
  private renderer: BoardRenderer
  private board: BoardModel
  private currentTool: ToolType = 'select'

  private state: InteractionState = {
    isDragging: false,
    isPanning: false,
    isSelecting: false,
    isDrawing: false,
    isFreehanding: false,
    isResizing: false,
    dragStartPos: { x: 0, y: 0 },
    lastMousePos: { x: 0, y: 0 },
    selectionBox: null,
    draggedElementId: null,
    dragStartPositions: new Map(),
    hasDuplicatedOnAltDrag: false,
    resizeHandle: null,
    resizeStartElement: null,
    resizeStartPoints: null,
    resizeStartControlPoints: null,
    resizeStartFontSize: null,
    rotateStartAngle: null,
    rotateStartRotation: null,
    drawingStartElement: null,
    freehandPoints: [],
    wasJustDrawing: false,
  }

  // 事件监听器
  private listeners: Map<BoardInteractionEventType, Set<(e: BoardInteractionEvent) => void>> = new Map()

  // 选中元素
  private selectedIds: Set<string> = new Set()

  // 配置
  private config = {
    zoomSpeed: 0.1,
    minZoom: 0.1,
    maxZoom: 5,
    dragThreshold: 3,
    doubleClickDelay: 300,
  }

  // 双击检测
  private lastClickTime = 0
  private lastClickElementId: string | null = null

  constructor(canvas: HTMLCanvasElement, renderer: BoardRenderer, board: BoardModel) {
    this.canvas = canvas
    this.renderer = renderer
    this.board = board
    this.bindEvents()
  }

  // ===== 工具 =====

  getTool(): ToolType {
    return this.currentTool
  }

  setTool(tool: ToolType): void {
    this.currentTool = tool
    this.updateCursor()
    this.emit('tool:change', { tool })
  }

  private updateCursor(): void {
    const cursorMap: Record<ToolType, string> = {
      'select': 'default',
      'hand': 'grab',
      'rectangle': 'crosshair',
      'ellipse': 'crosshair',
      'diamond': 'crosshair',
      'text': 'text',
      'sticky-note': 'crosshair',
      'arrow': 'crosshair',
      'line': 'crosshair',
      'freehand': 'crosshair',
      'eraser': '', // 橡皮擦由 Vue CSS 类处理
    }
    this.canvas.style.cursor = cursorMap[this.currentTool] || 'default'
  }

  // ===== 事件绑定 =====

  private bindEvents(): void {
    this.canvas.addEventListener('mousedown', this.onMouseDown)
    this.canvas.addEventListener('mousemove', this.onMouseMove)
    this.canvas.addEventListener('mouseup', this.onMouseUp)
    this.canvas.addEventListener('mouseleave', this.onMouseLeave)
    this.canvas.addEventListener('wheel', this.onWheel, { passive: false })
    this.canvas.addEventListener('dblclick', this.onDoubleClick)
    this.canvas.addEventListener('contextmenu', this.onContextMenu)
    // 触摸事件
    this.canvas.addEventListener('touchstart', this.onTouchStart, { passive: false })
    this.canvas.addEventListener('touchmove', this.onTouchMove, { passive: false })
    this.canvas.addEventListener('touchend', this.onTouchEnd, { passive: false })
  }

  unbindEvents(): void {
    this.canvas.removeEventListener('mousedown', this.onMouseDown)
    this.canvas.removeEventListener('mousemove', this.onMouseMove)
    this.canvas.removeEventListener('mouseup', this.onMouseUp)
    this.canvas.removeEventListener('mouseleave', this.onMouseLeave)
    this.canvas.removeEventListener('wheel', this.onWheel)
    this.canvas.removeEventListener('dblclick', this.onDoubleClick)
    this.canvas.removeEventListener('contextmenu', this.onContextMenu)
    this.canvas.removeEventListener('touchstart', this.onTouchStart)
    this.canvas.removeEventListener('touchmove', this.onTouchMove)
    this.canvas.removeEventListener('touchend', this.onTouchEnd)
  }

  // ===== 事件处理 =====

  private onMouseDown = (e: MouseEvent): void => {
    const pos = this.getMousePos(e)
    const worldPos = this.renderer.screenToWorld(pos)
    this.state.lastMousePos = pos
    this.state.dragStartPos = pos

    if (e.button === 1 || (e.button === 0 && this.currentTool === 'hand')) {
      // 中键或手型工具 → 平移
      this.state.isPanning = true
      this.canvas.style.cursor = 'grabbing'
      return
    }

    if (e.button === 0) {
      // 左键
      if (this.currentTool === 'select') {
        this.handleSelectDown(worldPos, e)
      } else if (this.currentTool === 'freehand') {
        this.handleFreehandDown(worldPos)
      } else if (this.currentTool === 'eraser') {
        this.handleEraserDown(worldPos)
      } else if (this.currentTool === 'text') {
        this.handleTextDown(worldPos)
      } else if (['rectangle', 'ellipse', 'diamond', 'sticky-note', 'arrow', 'line'].includes(this.currentTool)) {
        this.handleShapeDown(worldPos)
      }
    }
  }

  private onMouseMove = (e: MouseEvent): void => {
    const pos = this.getMousePos(e)
    const worldPos = this.renderer.screenToWorld(pos)
    const delta = {
      x: pos.x - this.state.lastMousePos.x,
      y: pos.y - this.state.lastMousePos.y,
    }

    if (this.state.isPanning) {
      const viewport = this.renderer.getViewport()
      this.renderer.setViewport({
        panX: viewport.panX + delta.x,
        panY: viewport.panY + delta.y,
      })
    } else if (this.state.isDragging) {
      this.handleDragMove(worldPos, e)
    } else if (this.state.isResizing) {
      this.handleResizeMove(worldPos, e)
    } else if (this.state.isSelecting && this.state.selectionBox) {
      const startWorld = this.renderer.screenToWorld(this.state.dragStartPos)
      this.state.selectionBox = {
        x: Math.min(startWorld.x, worldPos.x),
        y: Math.min(startWorld.y, worldPos.y),
        width: Math.abs(worldPos.x - startWorld.x),
        height: Math.abs(worldPos.y - startWorld.y),
      }
      this.selectElementsInBox(this.state.selectionBox)
      this.updateRendererSelection()
    } else if (this.state.isDrawing && this.state.drawingStartElement) {
      // 更新正在绘制的形状大小
      this.handleDrawingMove(worldPos, e)
    } else if (this.state.isFreehanding) {
      this.handleFreehandMove(worldPos)
    } else if (this.currentTool === 'select' || this.currentTool === 'eraser') {
      // 悬停光标（选择工具和橡皮擦工具都支持悬停检测）
      this.updateHoverCursor(worldPos)
    }

    this.state.lastMousePos = pos
  }

  private onMouseUp = (e: MouseEvent): void => {
    const pos = this.getMousePos(e)
    const worldPos = this.renderer.screenToWorld(pos)

    if (this.state.isDragging) {
      this.emit('element:drag:end', {
        elementId: this.state.draggedElementId || undefined,
        position: worldPos,
        selectedIds: this.getSelectedIds(),
      })
    }

    if (this.state.isResizing) {
      this.emit('element:resize:end', {
        elementId: this.state.draggedElementId || undefined,
        position: worldPos,
      })
    }

    if (this.state.isSelecting) {
      this.state.selectionBox = null
      this.emit('selection:change', {
        selectedIds: this.getSelectedIds(),
      })
    }

    if (this.state.isFreehanding) {
      this.emit('element:drag:end', { position: worldPos })
    }

    // 重置所有状态
    const wasJustDrawing = this.state.wasJustDrawing
    this.state.isDragging = false
    this.state.isPanning = false
    this.state.isSelecting = false
    this.state.isDrawing = false
    this.state.isFreehanding = false
    this.state.isResizing = false
    this.state.draggedElementId = null
    this.state.dragStartPositions.clear()
    this.state.hasDuplicatedOnAltDrag = false
    this.state.resizeHandle = null
    this.state.resizeStartElement = null
    this.state.resizeStartPoints = null
    this.state.resizeStartControlPoints = null
    this.state.resizeStartFontSize = null
    this.state.rotateStartAngle = null
    this.state.rotateStartRotation = null
    this.state.drawingStartElement = null
    this.state.freehandPoints = []
    this.state.wasJustDrawing = false

    // 一次性放置的图形工具画完后自动切回选择工具（对齐 Excalidraw 行为）
    if (wasJustDrawing) {
      this.currentTool = 'select'
      this.emit('tool:change', { tool: 'select' })
      this.emit('selection:change', {
        selectedIds: this.getSelectedIds(),
      })
    }

    this.updateCursor()
  }

  private onMouseLeave = (): void => {
    this.state.isDragging = false
    this.state.isPanning = false
    this.state.isSelecting = false
    this.state.isDrawing = false
    this.state.isFreehanding = false
    this.state.isResizing = false
    this.state.selectionBox = null
    this.state.hasDuplicatedOnAltDrag = false
  }

  private onWheel = (e: WheelEvent): void => {
    e.preventDefault()

    const pos = this.getMousePos(e)
    const viewport = this.renderer.getViewport()

    const zoomDelta = e.deltaY > 0 ? -this.config.zoomSpeed : this.config.zoomSpeed
    const newZoom = Math.max(
      this.config.minZoom,
      Math.min(this.config.maxZoom, viewport.zoom + zoomDelta)
    )

    // 以鼠标位置为中心缩放
    const worldPos = this.renderer.screenToWorld(pos)
    const newPanX = pos.x - worldPos.x * newZoom
    const newPanY = pos.y - worldPos.y * newZoom

    this.renderer.setViewport({ zoom: newZoom, panX: newPanX, panY: newPanY })

    this.emit('zoom', {
      position: worldPos,
      viewport: this.renderer.getViewport(),
    })
  }

  private onDoubleClick = (e: MouseEvent): void => {
    const pos = this.getMousePos(e)
    const worldPos = this.renderer.screenToWorld(pos)
    const el = this.board.getElementAtPoint(worldPos, this.board.activePageId, this.renderer.getViewport().zoom)

    if (el) {
      this.emit('element:dblclick', {
        elementId: el.id,
        position: worldPos,
      })
    }
  }

  private onContextMenu = (e: MouseEvent): void => {
    e.preventDefault()
    const pos = this.getMousePos(e)
    const worldPos = this.renderer.screenToWorld(pos)
    const el = this.board.getElementAtPoint(worldPos, this.board.activePageId, this.renderer.getViewport().zoom)

    if (el) {
      this.emit('element:contextmenu', {
        elementId: el.id,
        position: worldPos,
      })
    }
  }

  // ===== 触摸事件 =====

  private onTouchStart = (e: TouchEvent): void => {
    if (e.touches.length === 1) {
      e.preventDefault()
      const touch = e.touches[0]
      const mouseEvent = new MouseEvent('mousedown', {
        clientX: touch.clientX,
        clientY: touch.clientY,
        button: 0,
      })
      this.onMouseDown(mouseEvent)
    } else if (e.touches.length === 2) {
      // 双指平移
      e.preventDefault()
      this.state.isPanning = true
    }
  }

  private onTouchMove = (e: TouchEvent): void => {
    if (e.touches.length === 1) {
      e.preventDefault()
      const touch = e.touches[0]
      const mouseEvent = new MouseEvent('mousemove', {
        clientX: touch.clientX,
        clientY: touch.clientY,
      })
      this.onMouseMove(mouseEvent)
    } else if (e.touches.length === 2 && this.state.isPanning) {
      e.preventDefault()
      // 简单双指平移：用两指中心点移动距离
      const t0 = e.touches[0]
      const t1 = e.touches[1]
      const centerX = (t0.clientX + t1.clientX) / 2
      const centerY = (t0.clientY + t1.clientY) / 2
      const dx = centerX - this.state.lastMousePos.x
      const dy = centerY - this.state.lastMousePos.y
      const viewport = this.renderer.getViewport()
      this.renderer.setViewport({
        panX: viewport.panX + dx,
        panY: viewport.panY + dy,
      })
      this.state.lastMousePos = { x: centerX, y: centerY }
    }
  }

  private onTouchEnd = (e: TouchEvent): void => {
    e.preventDefault()
    const mouseEvent = new MouseEvent('mouseup', {
      clientX: this.state.lastMousePos.x,
      clientY: this.state.lastMousePos.y,
      button: 0,
    })
    this.onMouseUp(mouseEvent)
  }

  // ===== 工具处理 =====

  /** 选择工具 - 按下 */
  private handleSelectDown(worldPos: Point, e: MouseEvent): void {
    // 正在绘制形状中（鼠标按下后正在拖动创建）→ 不触发选择
    if (this.state.isDrawing) return

    // 记录本次点击前已选中的元素（只对这些元素测试 resize 手柄，避免
    // 误将未选中元素的控制点（如贝塞尔 cp1/cp2）识别为 resize 操作）
    const preSelectedIds = new Set(this.selectedIds)

    // 优先检测已选中元素的 resize 手柄
    for (const id of preSelectedIds) {
      const selEl = this.board.getElement(id)
      if (!selEl) continue
      const hit = this.renderer.hitTestResizeHandle(selEl, worldPos)
      if (!hit) continue

      // 点击在线条本身上（非手柄区域）→ 整条拖拽
      if (hit === 'drag') {
        this.state.isDragging = true
        this.state.draggedElementId = id
        this.state.dragStartPositions.clear()
        if (selEl.type === 'arrow' || selEl.type === 'line') {
          this.state.dragStartPositions.set(id, { x: (selEl as any).startPoint?.x ?? selEl.x, y: (selEl as any).startPoint?.y ?? selEl.y })
        } else {
          this.state.dragStartPositions.set(id, { x: selEl.x, y: selEl.y })
        }
        this.updateRendererSelection()
        return
      }

      // 点击在 resize 手柄上 → resize
      this.state.isResizing = true
      this.state.resizeHandle = hit
      this.state.draggedElementId = id
      this.state.resizeStartElement = { x: selEl.x, y: selEl.y, width: selEl.width, height: selEl.height }
      // 记录 Text 元素初始字体大小
      if (selEl.type === 'text') {
        this.state.resizeStartFontSize = (selEl as any).style?.fontSize ?? 20
      }
      // 旋转手柄：记录初始角度
      if (hit === 'rotate') {
        const cx = selEl.x + selEl.width / 2
        const cy = selEl.y + selEl.height / 2
        this.state.rotateStartAngle = Math.atan2(worldPos.y - cy, worldPos.x - cx)
        this.state.rotateStartRotation = selEl.rotation ?? 0
      }
      if ((selEl.type === 'arrow' || selEl.type === 'line') && 'startPoint' in selEl) {
        this.state.resizeStartPoints = {
          startPoint: { ...(selEl as any).startPoint },
          endPoint: { ...(selEl as any).endPoint },
        }
        // 存储贝塞尔控制点原始值
        if ((hit === 'cp1' || hit === 'cp2') && (selEl as any).connectionStyle?.route === 'bezier') {
          const sp = (selEl as any).startPoint
          const ep = (selEl as any).endPoint
          const override = (selEl as ArrowElement | LineElement).controlPoints
          const { cp1, cp2 } = this.renderer.computeBezierControlPoints(sp, ep, override)
          this.state.resizeStartControlPoints = { cp1: { ...cp1 }, cp2: { ...cp2 } }
        }
      }
      return
    }

    const el = this.board.getElementAtPoint(worldPos, this.board.activePageId, this.renderer.getViewport().zoom)

    if (el) {
      // 多选
      const isMultiSelect = e.ctrlKey || e.metaKey
      if (isMultiSelect) {
        if (this.selectedIds.has(el.id)) {
          this.selectedIds.delete(el.id)
        } else {
          this.selectedIds.add(el.id)
        }
      } else {
        if (!this.selectedIds.has(el.id)) {
          this.selectedIds.clear()
          this.selectedIds.add(el.id)
        }
      }

      // 开始拖拽
      this.state.isDragging = true
      this.state.draggedElementId = el.id
      this.state.dragStartPositions.clear()
      this.selectedIds.forEach(id => {
        const element = this.board.getElement(id)
        if (element) {
          this.state.dragStartPositions.set(id, { x: element.x, y: element.y })
        }
      })

      this.updateRendererSelection()
      this.emit('element:click', {
        elementId: el.id,
        position: worldPos,
        selectedIds: this.getSelectedIds(),
      })
      this.emit('selection:change', {
        selectedIds: this.getSelectedIds(),
      })
    } else {
      // 点击空白区域
      if (!e.ctrlKey && !e.metaKey) {
        this.selectedIds.clear()
        this.emit('selection:change', { selectedIds: [] })
      }
      this.state.isSelecting = true
      this.state.selectionBox = {
        x: worldPos.x,
        y: worldPos.y,
        width: 0,
        height: 0,
      }
      this.updateRendererSelection()
      this.emit('canvas:click', { position: worldPos })
    }
  }

  /** 拖拽移动 */
  private handleDragMove(worldPos: Point, e: MouseEvent): void {
    const startWorld = this.renderer.screenToWorld(this.state.dragStartPos)
    const dx = worldPos.x - startWorld.x
    const dy = worldPos.y - startWorld.y

    // 移动距离超过阈值才认为真正在拖动，Alt 复制也只在这时触发
    const moved = Math.sqrt(dx * dx + dy * dy)
    const threshold = this.config.dragThreshold

    if (e.altKey && !this.state.hasDuplicatedOnAltDrag && moved >= threshold) {
      this.state.hasDuplicatedOnAltDrag = true
      const idsToDuplicate = Array.from(this.selectedIds)
      const newIds: string[] = []
      idsToDuplicate.forEach(id => {
        const copy = this.board.duplicateElement(id)
        if (copy) newIds.push(copy.id)
      })
      // 原元素留在原地，选中副本并重建拖拽基准（避免跳变）
      this.selectedIds.clear()
      newIds.forEach(id => this.selectedIds.add(id))
      this.state.draggedElementId = newIds[0] || null
      this.state.dragStartPositions.clear()
      this.selectedIds.forEach(id => {
        const element = this.board.getElement(id)
        if (element) {
          this.state.dragStartPositions.set(id, { x: element.x, y: element.y })
        }
      })
      // dragStartPos 保持不变（仍然是原始鼠标按下位置），
      // 这样同一帧内的 dx/dy 能正确计算出从原位置到当前位置的偏移量，
      // 复制出来的元素立即跟随鼠标（没有这一帧的延迟/不动问题）。
      // 注意：dragStartPos 不会在 onMouseUp 以外的任何地方被改写，
      // 所以这里直接保持原值即可，无需手动重置。
      // 继续执行下面的移动逻辑，不 return。
    }

    this.state.dragStartPositions.forEach((startPos, id) => {
      // updateElementPosition 内部已同步 arrow/line 的 startPoint/endPoint，无需重复处理
      this.board.updateElementPosition(id, startPos.x + dx, startPos.y + dy)
    })

    this.renderer.forceRedraw()
    this.emit('element:drag:move', {
      elementId: this.state.draggedElementId || undefined,
      position: worldPos,
      selectedIds: this.getSelectedIds(),
    })
  }

  /** Resize 移动 */
  private handleResizeMove(worldPos: Point, e: MouseEvent): void {
    if (!this.state.resizeHandle || !this.state.resizeStartElement || !this.state.draggedElementId) return

    const start = this.state.resizeStartElement
    const startWorld = this.renderer.screenToWorld(this.state.dragStartPos)
    const dx = worldPos.x - startWorld.x
    const dy = worldPos.y - startWorld.y
    const handle = this.state.resizeHandle
    const el = this.board.getElement(this.state.draggedElementId)
    if (!el) return

    // 旋转手柄：计算新角度
    if (handle === 'rotate') {
      const cx = start.x + start.width / 2
      const cy = start.y + start.height / 2
      const currentAngle = Math.atan2(worldPos.y - cy, worldPos.x - cx)
      const deltaAngle = currentAngle - (this.state.rotateStartAngle ?? 0)
      const initialRotation = this.state.rotateStartRotation ?? 0
      const newRotation = initialRotation + deltaAngle
      this.board.updateElementRotation(el.id, newRotation)
      this.renderer.forceRedraw()
      this.emit('element:resize:move', { elementId: el.id, position: worldPos })
      return
    }

    // Text 元素：拖动角点时改变字体大小（基于包围盒尺寸比例缩放）
    if (el.type === 'text') {
      const startFontSize = this.state.resizeStartFontSize ?? (el as any).style?.fontSize ?? 20
      const textEl = el as any
      const cornerHandles = ['nw', 'ne', 'se', 'sw']
      const isCorner = cornerHandles.includes(handle!)

      // 计算当前拖动后的新包围盒尺寸
      let newX = start.x, newY = start.y, newW = start.width, newH = start.height

      if (isCorner) {
        // 角点拖动：计算新尺寸
        if (handle!.includes('e')) { newW = Math.max(20, start.width + dx) }
        else if (handle!.includes('w')) { newW = Math.max(20, start.width - dx); newX = start.x + start.width - newW }

        if (handle!.includes('s')) { newH = Math.max(20, start.height + dy) }
        else if (handle!.includes('n')) { newH = Math.max(20, start.height - dy); newY = start.y + start.height - newH }
      } else {
        // 边中点拖动：只改变尺寸，不改字体
        if (handle === 'e') { newW = Math.max(20, start.width + dx) }
        else if (handle === 'w') { newW = Math.max(20, start.width - dx); newX = start.x + start.width - newW }
        if (handle === 's') { newH = Math.max(20, start.height + dy) }
        else if (handle === 'n') { newH = Math.max(20, start.height - dy); newY = start.y + start.height - newH }
      }

      // 根据尺寸变化比例计算新字体大小（仅角点拖动时）
      let newFontSize = startFontSize
      if (isCorner) {
        const scaleX = start.width > 0 ? newW / start.width : 1
        const scaleY = start.height > 0 ? newH / start.height : 1
        const scale = Math.max(scaleX, scaleY)
        newFontSize = Math.max(8, Math.min(200, Math.round(startFontSize * scale)))
      }

      // 计算文本内容的自然尺寸
      const text = textEl.text || ''
      const fontWeight = textEl.style?.fontWeight || '400'
      const padding = 16
      const lineHeight = newFontSize * 1.4

      let contentWidth = 0
      let lineCount = 1
      if (text) {
        const lines = text.split('\n')
        lineCount = lines.length
        for (const line of lines) {
          contentWidth = Math.max(contentWidth, this.renderer.measureTextWidth(line, newFontSize, fontWeight))
        }
      } else {
        contentWidth = this.renderer.measureTextWidth('Text', newFontSize, fontWeight)
      }

      // 内容自然尺寸 + padding
      const minW = contentWidth + padding
      const minH = lineCount * lineHeight + padding

      // 如果计算出的尺寸小于内容所需，强制使用内容尺寸
      if (newW < minW) { newW = minW; newX = handle!.includes('w') ? start.x + start.width - minW : start.x }
      if (newH < minH) { newH = minH; newY = handle!.includes('n') ? start.y + start.height - minH : start.y }

      // 更新元素
      this.board.updateElementPosition(el.id, newX, newY)
      this.board.updateElementSize(el.id, newW, newH)
      this.board.updateElementStyle(el.id, { fontSize: newFontSize })

      this.renderer.forceRedraw()
      this.emit('element:resize:move', { elementId: el.id, position: worldPos })
      return
    }

    // 线段端点拖拽（arrow/line）
    if ((el.type === 'arrow' || el.type === 'line') && this.state.resizeStartPoints) {
      const sp = this.state.resizeStartPoints.startPoint
      const ep = this.state.resizeStartPoints.endPoint

      if (handle === 'start') {
        ;(el as any).startPoint = { x: sp.x + dx, y: sp.y + dy }
      } else if (handle === 'end') {
        ;(el as any).endPoint = { x: ep.x + dx, y: ep.y + dy }
      } else if (handle === 'n') {
        // 中点拖拽 → 平移整条线
        const origDx = worldPos.x - (sp.x + ep.x) / 2
        const origDy = worldPos.y - (sp.y + ep.y) / 2
        ;(el as any).startPoint = { x: sp.x + origDx, y: sp.y + origDy }
        ;(el as any).endPoint = { x: ep.x + origDx, y: ep.y + origDy }
      } else if ((handle === 'cp1' || handle === 'cp2') && this.state.resizeStartControlPoints) {
        // 贝塞尔控制点拖拽 → 调整曲度
        ;(el as any).controlPoints = {
          cp1: handle === 'cp1' ? { x: worldPos.x, y: worldPos.y } : { ...this.state.resizeStartControlPoints!.cp1 },
          cp2: handle === 'cp2' ? { x: worldPos.x, y: worldPos.y } : { ...this.state.resizeStartControlPoints!.cp2 },
        }
      }

      this.renderer.forceRedraw()
      this.emit('element:resize:move', { elementId: el.id, position: worldPos })
      return
    }

    // 包围盒 resize（普通形状）
    let newX = start.x, newY = start.y, newW = start.width, newH = start.height
    const cornerHandles = ['nw', 'ne', 'se', 'sw']

    if (cornerHandles.includes(handle) && e.shiftKey) {
      // Shift + 角点拖拽 → 比例缩放
      // 永远以 |dx| 为准派生 newH，保持宽高比不变
      // 这是 Excalidraw / Figma 的标准做法
      const ratio = start.height / start.width
      const deltaW = handle.includes('e') ? dx : -dx

      newW = Math.max(1, start.width + deltaW)
      newH = Math.max(1, newW * ratio)

      // 对齐到被拖动的角（该角不动，其他三边跟随）
      newX = handle.includes('e') ? start.x : start.x + start.width - newW
      newY = handle.includes('s') ? start.y : start.y + start.height - newH
    } else {
      // 根据 handle 方向计算新的位置和尺寸
      if (handle.includes('w')) { newX = start.x + dx; newW = start.width - dx }
      if (handle.includes('e')) { newW = start.width + dx }
      if (handle.includes('n')) { newY = start.y + dy; newH = start.height - dy }
      if (handle.includes('s')) { newH = start.height + dy }

      // 最小尺寸
      const minSize = 20
      if (newW < minSize) { if (handle.includes('w')) newX = start.x + start.width - minSize; newW = minSize }
      if (newH < minSize) { if (handle.includes('n')) newY = start.y + start.height - minSize; newH = minSize }
    }

    this.board.updateElementPosition(el.id, newX, newY)
    this.board.updateElementSize(el.id, newW, newH)

    this.emit('element:resize:move', {
      elementId: el.id,
      position: worldPos,
    })
  }

  /** 形状绘制 - 按下 */
  private handleShapeDown(worldPos: Point): void {
    this.state.isDrawing = true
    this.state.wasJustDrawing = true
    this.state.drawingStartElement = { ...worldPos }

    const shapeTypeMap: Record<string, ElementType> = {
      'rectangle': 'rectangle',
      'ellipse': 'ellipse',
      'diamond': 'diamond',
      'sticky-note': 'sticky-note',
      'arrow': 'arrow',
      'line': 'line',
    }

    const type = shapeTypeMap[this.currentTool]
    if (type) {
      // 初始化起点位置（所有形状都用此基准）
      const startX = worldPos.x
      const startY = worldPos.y

      const el = this.board.addElement(type, {
        x: startX,
        y: startY,
        width: 1,
        height: 1,
        // 箭头/线条：用 startPoint/endPoint 管理端点，x/y/width/height 保持与 startPoint 一致
        ...(type === 'arrow' || type === 'line'
          ? { startPoint: { x: startX, y: startY }, endPoint: { x: startX, y: startY } }
          : {}),
      })

      this.state.draggedElementId = el.id
      this.selectedIds.clear()
      this.selectedIds.add(el.id)
      this.updateRendererSelection()
    }
  }

  /** 形状绘制 - 移动 */
  private handleDrawingMove(worldPos: Point, e: MouseEvent): void {
    if (!this.state.drawingStartElement || !this.state.draggedElementId) return
    const start = this.state.drawingStartElement
    const el = this.board.getElement(this.state.draggedElementId)
    if (!el) return

    if (el.type === 'arrow' || el.type === 'line') {
      // 箭头/直线：端点跟随鼠标，x/y 保持为起点，width/height 为线段尺寸
      const arrow = el as any
      arrow.startPoint = { ...start }
      arrow.endPoint = { ...worldPos }
      arrow.x = Math.min(start.x, worldPos.x)
      arrow.y = Math.min(start.y, worldPos.y)
      arrow.width = Math.max(Math.abs(worldPos.x - start.x), 1)
      arrow.height = Math.max(Math.abs(worldPos.y - start.y), 1)
    } else if (el.type === 'rectangle' || el.type === 'ellipse' || el.type === 'diamond' || el.type === 'sticky-note') {
      // 矩形/椭圆/菱形/便利贴：标准包围盒绘制
      let rawW = Math.abs(worldPos.x - start.x)
      let rawH = Math.abs(worldPos.y - start.y)
      let x = Math.min(start.x, worldPos.x)
      let y = Math.min(start.y, worldPos.y)

      if (e.shiftKey && rawW > 0 && rawH > 0) {
        // Shift 约束长宽比 → 正方形/正圆
        const size = Math.max(rawW, rawH)
        rawW = size
        rawH = size
        // 保持起点为包围盒左上角
        x = start.x <= worldPos.x ? start.x : start.x - size
        y = start.y <= worldPos.y ? start.y : start.y - size
      }

      this.board.updateElementPosition(el.id, x, y)
      this.board.updateElementSize(el.id, rawW, rawH)
    }

    this.renderer.forceRedraw()
  }

  /** 自由画笔 - 按下 */
  private handleFreehandDown(worldPos: Point): void {
    this.state.isFreehanding = true
    this.state.freehandPoints = [{ x: 0, y: 0 }]

    const el = this.board.addElement('freehand', {
      x: worldPos.x,
      y: worldPos.y,
      width: 0,
      height: 0,
      points: [{ x: 0, y: 0 }],
    } as any)
    this.state.draggedElementId = el.id
  }

  /** 自由画笔 - 移动 */
  private handleFreehandMove(worldPos: Point): void {
    if (!this.state.draggedElementId) return
    const el = this.board.getElement(this.state.draggedElementId)
    if (!el || el.type !== 'freehand') return

    const freehand = el as FreehandElement
    const localPoint = {
      x: worldPos.x - freehand.x,
      y: worldPos.y - freehand.y,
    }
    freehand.points.push(localPoint)

    // 必须手动触发重绘
    this.renderer.forceRedraw()
  }

  /** 橡皮擦 - 按下 */
  private handleEraserDown(worldPos: Point): void {
    const el = this.board.getElementAtPoint(worldPos, this.board.activePageId, this.renderer.getViewport().zoom)
    if (el) {
      this.board.removeElement(el.id)
      this.selectedIds.delete(el.id)
      this.updateRendererSelection()
      // 擦除后清除悬停高亮
      this.emit('eraser:hover', { eraserHoverElement: null })
    }
  }

  /** 文本工具 - 按下（Excalidraw 行为）
   *  单击创建文本元素，初始宽度基于 placeholder 文字测量结果
   */
  private handleTextDown(worldPos: Point): void {
    const fontSize = 20
    const fontWeight = '400'
    const padding = 16
    const placeholderText = 'Text'
    const textWidth = this.renderer.measureTextWidth(placeholderText, fontSize, fontWeight)
    const initialWidth = Math.max(textWidth + padding, 60)
    const initialHeight = fontSize + padding

    const el = this.board.addElement('text', {
      x: worldPos.x,
      y: worldPos.y,
      width: initialWidth,
      height: initialHeight,
      text: '',
      style: {
        fill: 'transparent',
        stroke: 'transparent',
        fontSize,
        fontWeight,
      },
    })
    this.selectedIds.clear()
    this.selectedIds.add(el.id)
    this.updateRendererSelection()

    // 立即发射 dblclick 事件让 Vue 层打开文本编辑器
    this.emit('element:dblclick', {
      elementId: el.id,
      position: worldPos,
    })
  }

  // ===== 辅助方法 =====

  private getMousePos(e: MouseEvent): Point {
    const rect = this.canvas.getBoundingClientRect()
    return { x: e.clientX - rect.left, y: e.clientY - rect.top }
  }

  private selectElementsInBox(box: Rect): void {
    this.selectedIds.clear()
    for (const el of this.board.activePageElements) {
      if (
        el.x >= box.x &&
        el.x + el.width <= box.x + box.width &&
        el.y >= box.y &&
        el.y + el.height <= box.y + box.height
      ) {
        this.selectedIds.add(el.id)
      }
    }
    this.updateRendererSelection()
  }

  private updateRendererSelection(): void {
    this.renderer.setSelectionInfo({
      selectedIds: this.selectedIds,
      activeHandle: this.state.resizeHandle,
      selectionBox: this.state.selectionBox,
    })
    this.renderer.forceRedraw()
  }

  private updateHoverCursor(worldPos: Point): void {
    // 检查是否在已选中元素的 resize 手柄上（避免误高亮未选中元素的控制点）
    for (const id of this.selectedIds) {
      const el = this.board.getElement(id)
      if (el) {
        const handle = this.renderer.hitTestResizeHandle(el, worldPos)
        if (handle) {
          const cursorMap: Record<ResizeHandle, string> = {
            'nw': 'nw-resize', 'n': 'n-resize', 'ne': 'ne-resize',
            'e': 'e-resize', 'se': 'se-resize', 's': 's-resize',
            'sw': 'sw-resize', 'w': 'w-resize', 'rotate': 'grab',
            'start': 'crosshair', 'end': 'crosshair',
            'drag': 'move',
            'cp1': 'move', 'cp2': 'move',
          }
          this.canvas.style.cursor = cursorMap[handle]
          this.emit('eraser:hover', { eraserHoverElement: null })
          return
        }
      }
    }

    // 检查是否在元素上
    const el = this.board.getElementAtPoint(worldPos, this.board.activePageId, this.renderer.getViewport().zoom)
    if (el) {
      if (this.currentTool === 'eraser') {
        // 橡皮擦模式不设置 cursor，由 Vue CSS 类处理
        this.emit('eraser:hover', {
          eraserHoverElement: { x: el.x, y: el.y, width: el.width, height: el.height },
        })
      } else {
        this.canvas.style.cursor = 'move'
        this.emit('eraser:hover', { eraserHoverElement: null })
      }
    } else {
      // 橡皮擦模式不设置 cursor，由 Vue CSS 类处理
      if (this.currentTool !== 'eraser') {
        this.canvas.style.cursor = 'default'
      }
      this.emit('eraser:hover', { eraserHoverElement: null })
    }
  }

  // ===== 事件发射 =====

  private emit(type: BoardInteractionEventType, data: Partial<BoardInteractionEvent>): void {
    const listeners = this.listeners.get(type)
    if (listeners) {
      const event: BoardInteractionEvent = {
        type,
        viewport: this.renderer.getViewport(),
        position: { x: 0, y: 0 },
        ...data,
      } as BoardInteractionEvent
      listeners.forEach(listener => listener(event))
    }
  }

  // ===== 公共 API =====

  on(type: BoardInteractionEventType, callback: (e: BoardInteractionEvent) => void): () => void {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, new Set())
    }
    this.listeners.get(type)!.add(callback)
    return () => this.off(type, callback)
  }

  off(type: BoardInteractionEventType, callback: (e: BoardInteractionEvent) => void): void {
    this.listeners.get(type)?.delete(callback)
  }

  getSelectedIds(): string[] {
    return Array.from(this.selectedIds)
  }

  setSelectedIds(ids: string[]): void {
    this.selectedIds = new Set(ids)
    this.updateRendererSelection()
  }

  clearSelection(): void {
    this.selectedIds.clear()
    this.updateRendererSelection()
  }

  setBoard(board: BoardModel): void {
    this.board = board
  }

  /** 取消正在绘制的形状（ESC 调用） */
  cancelDrawing(): void {
    if (!this.state.isDrawing) return
    // 删除正在绘制的元素
    if (this.state.draggedElementId) {
      this.board.removeElement(this.state.draggedElementId)
      this.selectedIds.delete(this.state.draggedElementId)
    }
    this.state.isDrawing = false
    this.state.wasJustDrawing = false
    this.state.drawingStartElement = null
    this.state.draggedElementId = null
    this.currentTool = 'select'
    this.emit('tool:change', { tool: 'select' })
    this.updateRendererSelection()
  }
}

export default BoardInteractionManager
