/**
 * Canvas Renderer - Canvas 渲染层
 * 
 * 职责：
 * - 绘制节点和边
 * - 处理视口变换（缩放、平移）
 * - 提供渲染上下文
 * 
 * 设计原则：
 * - 纯渲染逻辑，不处理交互
 * - 高性能（只重绘变化区域）
 * - 支持自定义渲染器
 */

import type { Node, Edge, Point, Viewport, RendererConfig, LayoutType, Rect } from '../core/types'
import type { IGraphModel } from '../core/GraphModel'
import type { ThemeConfig } from '../theme'
import { PRESET_THEMES } from '../theme'
import { NODE_STYLE, getTextMeasurer, type NodeTextLayout } from '../core/TextNodeMeasurer'

// 默认主题
const DEFAULT_THEME = PRESET_THEMES[0]

/** 默认渲染配置 */
const DEFAULT_CONFIG: RendererConfig = {
  width: 800,
  height: 600,
  backgroundColor: '#FFFFFF',
  pixelRatio: window.devicePixelRatio || 1,
  showGrid: false,
  gridSize: 20
}

/** 默认节点样式（共享尺寸/文本常量 + 渲染绘制属性） */
const S = {
  ...NODE_STYLE,
  // 渲染绘制专用属性
  fill: '#64748b',
  stroke: '#94a3b8',
  color: '#ffffff',
  borderRadius: 8,
  descIconOffset: 12
}

/** 添加按钮方向 */
type AddButtonDirection = 'left' | 'right' | 'top' | 'bottom'

/**
 * Canvas 渲染器
 */
export class CanvasRenderer {
  private canvas: HTMLCanvasElement
  private ctx: CanvasRenderingContext2D
  private config: RendererConfig
  private viewport: Viewport = { zoom: 1, panX: 0, panY: 0 }
  private layoutType: LayoutType = 'radial-mindmap'
  private themeConfig: ThemeConfig = DEFAULT_THEME
  
// 选中状态（支持多选）
  private selectedNodeId: string | null = null
  private selectedNodeIds: Set<string> = new Set()
  // 节点方向缓存（用于确定添加按钮位置）
  private nodeDirections: Map<string, AddButtonDirection> = new Map()
  // 描述图标位置缓存
  private descriptionIconPositions: Map<string, { x: number; y: number; nodeId: string; description: string }> = new Map()
  // 选框状态
  private selectionBox: Rect | null = null
  
  // 文本测量器（全局单例，不重复创建）
  private measurer = getTextMeasurer()
  // 节点换行缓存（避免每帧重复计算）
  private nodeTextCache: Map<string, NodeTextLayout> = new Map()
  
  private needsRedraw = true

  constructor(canvas: HTMLCanvasElement, config: Partial<RendererConfig> = {}) {
    this.canvas = canvas
    this.config = { ...DEFAULT_CONFIG, ...config }
    
    const ctx = canvas.getContext('2d')
    if (!ctx) throw new Error('Cannot get 2D context')
    this.ctx = ctx

    this.setupCanvas()
  }

  /** 设置 Canvas 尺寸 */
  private setupCanvas(): void {
    const { width, height, pixelRatio } = this.config
    
    this.canvas.width = width * pixelRatio
    this.canvas.height = height * pixelRatio
    this.canvas.style.width = `${width}px`
    this.canvas.style.height = `${height}px`
    
    // 重置变换后再 scale，避免叠加
    this.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0)
  }

  /** 构建字体字符串 */
  private buildFontStr(weight: string, size: number): string {
    return this.measurer.buildFontStr(weight, size)
  }

  /** 设置字体 */
  private setFont(weight: string, size: number): void {
    this.ctx.font = this.buildFontStr(weight, size)
  }

  /** 计算节点尺寸（委托给 TextNodeMeasurer） */
  calculateNodeSize(label: string, isRoot: boolean = false, hasChildren: boolean = false, collapsed: boolean = false, descendantCount: number = 0): { width: number; height: number } {
    return this.measurer.measureNodeSize(label, isRoot, hasChildren, collapsed, descendantCount)
  }

  /**
   * 获取节点换行文本缓存（避免重复计算）
   */
  getNodeTextLayout(nodeId: string, label: string, isRoot: boolean, hasChildren: boolean, collapsed: boolean, descendantCount: number): NodeTextLayout {
    const cacheKey = `${nodeId}:${label}:${isRoot}:${hasChildren}:${collapsed}:${descendantCount}`
    const cached = this.nodeTextCache.get(cacheKey)
    if (cached) return cached

    const size = this.measurer.measureNodeSize(label, isRoot, hasChildren, collapsed, descendantCount)
    const fontSize = isRoot ? S.rootFontSize : S.fontSize
    const fontWeight = S.fontWeight
    
    // 计算文本可用宽度
    const nodeWidth = size.width  // 使用自适应宽度
    let availableWidth = nodeWidth - S.paddingX * 2
    if (hasChildren) availableWidth -= S.collapseIconWidth + S.collapseIconGap
    if (collapsed && hasChildren && descendantCount > 0) {
      availableWidth -= this.measurer.measureTextWidth(`(${descendantCount})`, fontSize, fontWeight) + S.countGap
    }
    availableWidth -= S.descIconReserved
    availableWidth = Math.max(availableWidth, S.minTextWidth)
    
    const lines = this.measurer.wrapText(label, availableWidth, fontSize, fontWeight, S.maxLines)
    
    const result: NodeTextLayout = { lines, size }
    this.nodeTextCache.set(cacheKey, result)
    return result
  }

  /** 清除文本缓存（标签变更时调用） */
  clearTextCache(): void {
    this.nodeTextCache.clear()
  }

  /** 设置布局类型 */
  setLayoutType(type: LayoutType): void {
    this.layoutType = type
    this.needsRedraw = true
  }

  /** 设置主题配置 */
  setThemeConfig(theme: ThemeConfig): void {
    this.themeConfig = theme
    this.needsRedraw = true
  }

  /** 获取主题配置 */
  getThemeConfig(): ThemeConfig {
    return this.themeConfig
  }

  /** 设置选中节点 */
  setSelectedNodeId(nodeId: string | null): void {
    this.selectedNodeId = nodeId
    // 同时更新多选集合
    if (nodeId) {
      this.selectedNodeIds = new Set([nodeId])
    } else {
      this.selectedNodeIds.clear()
    }
    this.needsRedraw = true
  }

  /** 获取选中节点 */
  getSelectedNodeId(): string | null {
    return this.selectedNodeId
  }

  /** 设置多选节点 */
  setSelectedNodeIds(ids: string[]): void {
    this.selectedNodeIds = new Set(ids)
    // 设置主选中节点为第一个
    this.selectedNodeId = ids.length > 0 ? ids[0] : null
    this.needsRedraw = true
  }

  /** 获取多选节点 */
  getSelectedNodeIds(): string[] {
    return Array.from(this.selectedNodeIds)
  }

  /** 设置选框 */
  setSelectionBox(box: Rect | null): void {
    this.selectionBox = box
    this.needsRedraw = true
  }

  /** 获取选框 */
  getSelectionBox(): Rect | null {
    return this.selectionBox
  }

  /** 设置节点方向（用于确定添加按钮位置） */
  setNodeDirections(directions: Map<string, AddButtonDirection>): void {
    this.nodeDirections = directions
    this.needsRedraw = true
  }

  /** 更新视口 */
  setViewport(viewport: Partial<Viewport>): void {
    this.viewport = { ...this.viewport, ...viewport }
    this.needsRedraw = true
  }

  /** 获取视口 */
  getViewport(): Viewport {
    return { ...this.viewport }
  }

  /** 屏幕坐标转世界坐标 */
  screenToWorld(screenPoint: Point): Point {
    return {
      x: (screenPoint.x - this.viewport.panX) / this.viewport.zoom,
      y: (screenPoint.y - this.viewport.panY) / this.viewport.zoom
    }
  }

  /** 世界坐标转屏幕坐标 */
  worldToScreen(worldPoint: Point): Point {
    return {
      x: worldPoint.x * this.viewport.zoom + this.viewport.panX,
      y: worldPoint.y * this.viewport.zoom + this.viewport.panY
    }
  }

  /** 主渲染方法 */
  render(graph: IGraphModel): void {
    if (!this.needsRedraw) return
    
    const { width, height } = this.config
    
    // 清空画布
    this.ctx.fillStyle = this.config.backgroundColor!
    this.ctx.fillRect(0, 0, width, height)

    // 绘制网格
    if (this.config.showGrid) {
      this.drawGrid()
    }

    // 应用视口变换
    this.ctx.save()
    this.ctx.translate(this.viewport.panX, this.viewport.panY)
    this.ctx.scale(this.viewport.zoom, this.viewport.zoom)

    // 根据布局类型选择渲染方式
    if (this.layoutType === 'fishbone') {
      this.renderFishbone(graph)
    } else {
      this.renderDefault(graph)
    }

    this.ctx.restore()
    
    // 绘制选框（在视口变换外，使用屏幕坐标）
    if (this.selectionBox) {
      this.drawSelectionBox()
    }
    
    this.needsRedraw = false
  }

  /** 绘制网格 */
  private drawGrid(): void {
    const { width, height, gridSize = 20 } = this.config
    const { zoom, panX, panY } = this.viewport

    this.ctx.strokeStyle = '#e2e8f0'
    this.ctx.lineWidth = 0.5

    // 计算网格偏移
    const offsetX = panX % (gridSize * zoom)
    const offsetY = panY % (gridSize * zoom)

    // 绘制垂直线
    for (let x = offsetX; x < width; x += gridSize * zoom) {
      this.ctx.beginPath()
      this.ctx.moveTo(x, 0)
      this.ctx.lineTo(x, height)
      this.ctx.stroke()
    }

    // 绘制水平线
    for (let y = offsetY; y < height; y += gridSize * zoom) {
      this.ctx.beginPath()
      this.ctx.moveTo(0, y)
      this.ctx.lineTo(width, y)
      this.ctx.stroke()
    }
  }

  /** 默认渲染（思维导图） */
  private renderDefault(graph: IGraphModel): void {
    // 时间轴布局：先绘制时间轴主线
    if (this.layoutType === 'timeline-horizontal' || this.layoutType === 'timeline-vertical') {
      this.drawTimelineAxis(graph)
    }

    // 先绘制边
    graph.edges.forEach(edge => {
      this.drawEdge(edge, graph)
    })

    // 再绘制节点
    graph.nodes.forEach(node => {
      if (!node.hidden) {
        this.drawNode(node, graph)
      }
    })
  }

  /** 鱼骨图渲染 - 核心优势 */
  private renderFishbone(graph: IGraphModel): void {
    const root = graph.root
    if (!root) return

    const rootPos = root.position

    // 1. 绘制主干线（水平线）
    this.drawBackbone(rootPos, graph)

    // 2. 绘制斜线分支
    graph.getChildren(graph.root?.id || "").forEach((child, index) => {
      const childPos = child.position
      const branchPoint = this.getBranchPoint(rootPos, index)
      
      // 绘制从主干线到子节点的斜线
      this.drawFishboneBranch(branchPoint, childPos, index)

      // 绘制子节点的边（子节点之间的连线）
      this.renderBranchEdges(child, graph)
    })

    // 3. 绘制所有节点
    graph.nodes.forEach(node => {
      if (!node.hidden) {
        this.drawNode(node, graph)
      }
    })
  }

  /** 绘制主干线 */
  private drawBackbone(rootPos: Point, graph: IGraphModel): void {
    const children = graph.getChildren(graph.root?.id || "")
    if (children.length === 0) {
      // 只有根节点，画短线
      this.drawLine(
        { x: rootPos.x, y: rootPos.y },
        { x: rootPos.x + 100, y: rootPos.y },
        this.themeConfig.edgeColor,
        this.themeConfig.edgeWidth
      )
      return
    }

    // 找到最远的分支点
    const lastChild = children[children.length - 1]
    const lastChildPos = lastChild.position
    
    // 主干线终点
    const endX = lastChildPos.x + 50

    // 绘制主干线
    this.drawLine(
      { x: rootPos.x, y: rootPos.y },
      { x: endX, y: rootPos.y },
      this.themeConfig.edgeColor,
      this.themeConfig.edgeWidth
    )
  }

  /** 获取分支点位置 */
  private getBranchPoint(rootPos: Point, index: number): Point {
    const branchSpacing = 120 // 与布局引擎保持一致
    return {
      x: rootPos.x + 100 + index * branchSpacing,
      y: rootPos.y
    }
  }

  /** 绘制鱼骨图分支 */
  private drawFishboneBranch(from: Point, to: Point, index: number): void {
    const isGoingUp = index % 2 === 0

    this.ctx.strokeStyle = this.themeConfig.edgeColor
    this.ctx.lineWidth = this.themeConfig.edgeWidth
    this.ctx.beginPath()
    
    // 从主干线上的分支点开始
    this.ctx.moveTo(from.x, from.y)
    
    // 直线连接到子节点（45度斜线）
    this.ctx.lineTo(to.x, to.y)
    
    this.ctx.stroke()
  }

  /** 渲染分支内的边 */
  private renderBranchEdges(parent: Node, graph: IGraphModel): void {
    const children = graph.getChildren(parent.id)
    
    children.forEach(child => {
      if (child.hidden) return
      
      const parentPos = parent.position
      const childPos = child.position

      // 绘制边
      this.drawLine(
        parentPos,
        childPos,
        this.themeConfig.edgeColor,
        this.themeConfig.edgeWidth
      )

      // 递归
      this.renderBranchEdges(child, graph)
    })
  }

  /** 绘制节点 */
  private drawNode(node: Node, graph: IGraphModel): void {
    const pos = node.position
    const style = { ...S, ...node.style }
    
    // 根节点特殊样式
    if (node.isRoot) {
      style.fontSize = S.rootFontSize
    }
    
    // 根据节点层级设置颜色（使用主题配置）
    const level = node.level ?? 0
    const colorIndex = Math.min(level, this.themeConfig.levels.length - 1)
    const levelColor = this.themeConfig.levels[colorIndex]
    style.fill = node.style?.fill || levelColor.fill
    style.stroke = node.style?.stroke || levelColor.stroke
    style.color = node.style?.color || levelColor.color

    const children = graph.getChildren(node.id)
    const hasChildren = children.length > 0
    const isSelected = this.selectedNodeIds.has(node.id)

    // 计算折叠时的子孙节点数
    let allDescendants = 0
    if (node.collapsed && hasChildren) {
      allDescendants = this.getDescendantCount(node.id, graph)
    }

    // 从缓存获取换行文本 + 节点尺寸（避免重复计算）
    const textLayout = this.getNodeTextLayout(node.id, node.label, node.isRoot, hasChildren, node.collapsed, allDescendants)
    const textLines = textLayout.lines
    const actualWidth = textLayout.size.width
    const actualHeight = textLayout.size.height
    const halfWidth = actualWidth / 2
    const halfHeight = actualHeight / 2

    const fontWeight = S.fontWeight
    const fontSize = style.fontSize!

    // 计算内容总宽度（折叠图标 + 第一行文字 + 子节点数量）
    const iconWidth = hasChildren ? S.collapseIconWidth : 0
    const iconGap = hasChildren ? S.collapseIconGap : 0
    
    let countWidth = 0
    let countGap = 0
    if (node.collapsed && hasChildren && allDescendants > 0) {
      this.setFont(fontWeight, fontSize)
      countWidth = this.ctx.measureText(`(${allDescendants})`).width
      countGap = S.countGap
    }
    
    // 第一行宽度用于水平居中
    this.setFont(fontWeight, fontSize)
    const firstLineWidth = this.ctx.measureText(textLines[0]).width
    const totalContentWidth = iconWidth + iconGap + firstLineWidth + countGap + countWidth
    
    let contentX = pos.x - totalContentWidth / 2

    // 绘制背景（透明主题用画布背景色填充，遮挡边线）
    const bgColor = this.config.backgroundColor || this.themeConfig.backgroundColor
    this.ctx.fillStyle = style.fill === 'transparent' ? bgColor : style.fill!
    this.ctx.strokeStyle = isSelected ? this.themeConfig.selectedStroke : style.stroke!
    this.ctx.lineWidth = isSelected ? 2.5 : this.themeConfig.edgeWidth
    
    this.ctx.beginPath()
    this.roundRect(pos.x - halfWidth, pos.y - halfHeight, actualWidth, actualHeight, style.borderRadius!)
    this.ctx.fill()
    this.ctx.stroke()

    // 绘制折叠/展开图标
    if (hasChildren) {
      const iconX = contentX + iconWidth / 2
      this.drawCollapseIcon(iconX, pos.y, node.collapsed, style.color!)
      contentX += iconWidth + iconGap
    }

    // 绘制多行文本
    const lineHeight = fontSize * S.lineHeight
    const textBlockHeight = textLines.length * lineHeight
    const textStartY = pos.y - textBlockHeight / 2 + lineHeight / 2 + fontSize * 0.15

    this.setFont(fontWeight, fontSize)
    this.ctx.textAlign = 'left'
    this.ctx.textBaseline = 'middle'
    this.ctx.fillStyle = style.color!

    for (let i = 0; i < textLines.length; i++) {
      this.ctx.fillText(textLines[i], contentX, textStartY + i * lineHeight)
    }

    // 折叠时，子节点数量跟在第一行末尾
    if (node.collapsed && hasChildren && allDescendants > 0) {
      const isLightContext = style.color !== '#ffffff' && style.color !== '#fff'
      this.ctx.fillStyle = isLightContext ? 'rgba(0,0,0,0.35)' : 'rgba(255,255,255,0.6)'
      this.ctx.textBaseline = 'middle'
      this.ctx.fillText(`(${allDescendants})`, contentX + firstLineWidth + countGap, textStartY)
    }

    // 绘制描述图标
    if (node.description) {
      const descIconX = pos.x + halfWidth - S.descIconOffset
      const descIconY = pos.y - halfHeight + S.descIconOffset
      const isLightCtx = style.color !== '#ffffff' && style.color !== '#fff'
      this.drawDescriptionIcon(descIconX, descIconY, isLightCtx)
      
      this.descriptionIconPositions.set(node.id, { x: descIconX, y: descIconY, nodeId: node.id, description: node.description })
    } else {
      this.descriptionIconPositions.delete(node.id)
    }

    // 选中时绘制添加按钮（多选时不显示）
    if (isSelected && this.selectedNodeIds.size <= 1) {
      const direction = this.nodeDirections.get(node.id) || 'right'
      this.drawAddButton(pos, halfWidth, halfHeight, direction)
    }
  }

  /** 绘制折叠/展开图标 */
  private drawCollapseIcon(x: number, y: number, collapsed: boolean, textColor: string = '#ffffff'): void {
    const size = 6
    
    // 保存当前状态
    this.ctx.save()
    
    // 判断是否为透明填充主题（背景浅色时用浅灰图标背景）
    const isLightContext = textColor !== '#ffffff' && textColor !== '#fff'
    const iconBg = isLightContext ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.2)'
    const iconStroke = isLightContext ? 'rgba(0,0,0,0.5)' : 'rgba(255,255,255,0.9)'
    
    // 绘制圆形背景
    this.ctx.fillStyle = iconBg
    this.ctx.beginPath()
    this.ctx.arc(x, y, size + 2, 0, Math.PI * 2)
    this.ctx.fill()
    
    // 绘制箭头
    this.ctx.strokeStyle = iconStroke
    this.ctx.lineWidth = 1.5
    this.ctx.lineCap = 'round'
    this.ctx.lineJoin = 'round'
    
    this.ctx.beginPath()
    if (collapsed) {
      // 展开箭头：向右的三角形 >
      this.ctx.moveTo(x - 2, y - 2.5)
      this.ctx.lineTo(x + 2.5, y)
      this.ctx.lineTo(x - 2, y + 2.5)
    } else {
      // 折叠箭头：向下的三角形 v
      this.ctx.moveTo(x - 2.5, y - 2)
      this.ctx.lineTo(x, y + 2.5)
      this.ctx.lineTo(x + 2.5, y - 2)
    }
    this.ctx.stroke()
    
    // 恢复状态
    this.ctx.restore()
  }

  /** 绘制添加子节点按钮 */
  private drawAddButton(nodePos: Point, halfWidth: number, halfHeight: number, direction: AddButtonDirection): void {
    // 保存状态
    this.ctx.save()
    
    const offset = 20  // 按钮距离节点的距离
    const buttonSize = 11  // 按钮半径
    
    let btnX: number, btnY: number
    
    switch (direction) {
      case 'left':
        btnX = nodePos.x - halfWidth - offset
        btnY = nodePos.y
        break
      case 'top':
        btnX = nodePos.x
        btnY = nodePos.y - halfHeight - offset
        break
      case 'bottom':
        btnX = nodePos.x
        btnY = nodePos.y + halfHeight + offset
        break
      case 'right':
      default:
        btnX = nodePos.x + halfWidth + offset
        btnY = nodePos.y
        break
    }
    
    // 绘制按钮背景 - 使用主题配置的颜色
    this.ctx.fillStyle = this.themeConfig.addButtonColor
    this.ctx.beginPath()
    this.ctx.arc(btnX, btnY, buttonSize, 0, Math.PI * 2)
    this.ctx.fill()
    
    // 绘制阴影效果
    this.ctx.shadowColor = `${this.themeConfig.addButtonColor}66`
    this.ctx.shadowBlur = 6
    this.ctx.shadowOffsetX = 0
    this.ctx.shadowOffsetY = 2
    this.ctx.fill()
    this.ctx.shadowColor = 'transparent'
    this.ctx.shadowBlur = 0
    
    // 绘制 + 图标
    this.ctx.strokeStyle = '#ffffff'
    this.ctx.lineWidth = 2
    this.ctx.lineCap = 'round'
    this.ctx.beginPath()
    this.ctx.moveTo(btnX - 5, btnY)
    this.ctx.lineTo(btnX + 5, btnY)
    this.ctx.moveTo(btnX, btnY - 5)
    this.ctx.lineTo(btnX, btnY + 5)
    this.ctx.stroke()
    
    // 恢复状态
    this.ctx.restore()
  }

  /** 绘制描述图标（info 图标） */
  private drawDescriptionIcon(x: number, y: number, isLightContext: boolean = false): void {
    this.ctx.save()
    
    const size = 8
    const bg = isLightContext ? 'rgba(0,0,0,0.06)' : 'rgba(255, 255, 255, 0.3)'
    const fg = isLightContext ? 'rgba(0,0,0,0.5)' : 'rgba(255, 255, 255, 0.9)'
    
    // 绘制圆形背景
    this.ctx.fillStyle = bg
    this.ctx.beginPath()
    this.ctx.arc(x, y, size, 0, Math.PI * 2)
    this.ctx.fill()
    
    // 绘制 i 图标
    this.ctx.fillStyle = fg
    this.ctx.font = `bold 10px ${S.fontFamily}`
    this.ctx.textAlign = 'center'
    this.ctx.textBaseline = 'middle'
    this.ctx.fillText('i', x, y + 0.5)
    
    this.ctx.restore()
  }

  /** 获取描述图标位置信息 */
  getDescriptionIconPositions(): Map<string, { x: number; y: number; nodeId: string; description: string }> {
    return this.descriptionIconPositions
  }

  /** 获取子孙节点数量 */
  private getDescendantCount(nodeId: string, graph: IGraphModel): number {
    let count = 0
    const children = graph.getChildren(nodeId)
    for (const child of children) {
      count++
      count += this.getDescendantCount(child.id, graph)
    }
    return count
  }

  /** 绘制折叠指示器（旧版，保留兼容） */
  private drawCollapseIndicator(x: number, y: number): void {
    this.ctx.fillStyle = '#94a3b8'
    this.ctx.beginPath()
    this.ctx.arc(x, y, 4, 0, Math.PI * 2)
    this.ctx.fill()
  }

  /** 绘制边 */
  private drawEdge(edge: Edge, graph: IGraphModel): void {
    const source = graph.getNode(edge.source)
    const target = graph.getNode(edge.target)
    
    if (!source || !target || source.hidden || target.hidden) return

    const sourcePos = source.position
    const targetPos = target.position
    // 使用主题配置的边样式（edge.style 仅作为显式覆盖）
    const style = {
      stroke: edge.style?.stroke || this.themeConfig.edgeColor,
      strokeWidth: edge.style?.strokeWidth || this.themeConfig.edgeWidth
    }

    // 根据布局类型和边类型选择绘制方式
    if (this.layoutType === 'org-chart' || this.layoutType === 'tree') {
      // 组织结构图和树形图使用正交折线
      this.drawOrthogonalEdge(sourcePos, targetPos, style.stroke, style.strokeWidth, source.size, target.size)
    } else if (this.layoutType === 'timeline-horizontal' || this.layoutType === 'timeline-vertical') {
      // 时间轴布局使用专用的正交连线
      this.drawTimelineEdge(sourcePos, targetPos, style.stroke, style.strokeWidth, source.size, target.size)
    } else if (this.layoutType === 'force-directed') {
      // 力导向布局使用直线连接
      this.drawLine(sourcePos, targetPos, style.stroke, style.strokeWidth)
    } else if (edge.type === 'straight') {
      this.drawLine(sourcePos, targetPos, style.stroke, style.strokeWidth)
    } else {
      this.drawBezierEdge(sourcePos, targetPos, style.stroke, style.strokeWidth)
    }
  }

  /** 绘制时间轴主线 */
  private drawTimelineAxis(graph: IGraphModel): void {
    const root = graph.root
    if (!root) return

    const rootPos = root.position
    const mainNodes = graph.getChildren(root.id).filter(n => !n.hidden)
    
    if (mainNodes.length === 0) return

    // 时间轴主线样式：比普通边稍粗
    const axisStyle = {
      stroke: this.themeConfig.edgeColor,
      strokeWidth: this.themeConfig.edgeWidth + 1
    }

    this.ctx.strokeStyle = axisStyle.stroke
    this.ctx.lineWidth = axisStyle.strokeWidth
    this.ctx.lineCap = 'round'
    this.ctx.beginPath()

    // 从根节点开始绘制
    this.ctx.moveTo(rootPos.x, rootPos.y)

    // 连接到所有主时间节点
    mainNodes.forEach((node, index) => {
      const nodePos = node.position
      this.ctx.lineTo(nodePos.x, nodePos.y)
    })

    this.ctx.stroke()

    // 在每个主节点位置绘制时间点标记（小圆点）
    this.ctx.fillStyle = axisStyle.stroke
    mainNodes.forEach(node => {
      const nodePos = node.position
      this.ctx.beginPath()
      this.ctx.arc(nodePos.x, nodePos.y, 4, 0, Math.PI * 2)
      this.ctx.fill()
    })
  }

  /** 绘制直线 */
  private drawLine(from: Point, to: Point, color: string, width: number): void {
    this.ctx.strokeStyle = color
    this.ctx.lineWidth = width
    this.ctx.beginPath()
    this.ctx.moveTo(from.x, from.y)
    this.ctx.lineTo(to.x, to.y)
    this.ctx.stroke()
  }

  /** 绘制贝塞尔曲线边 */
  private drawBezierEdge(from: Point, to: Point, color: string, width: number): void {
    const dx = to.x - from.x
    const controlX = from.x + dx * 0.5

    this.ctx.strokeStyle = color
    this.ctx.lineWidth = width
    this.ctx.beginPath()
    this.ctx.moveTo(from.x, from.y)
    this.ctx.bezierCurveTo(controlX, from.y, controlX, to.y, to.x, to.y)
    this.ctx.stroke()
  }

  /**
   * 绘制正交折线边（用于组织结构图和树形图）
   * 从父节点底部中心出发，垂直向下到中间点，然后水平延伸到子节点上方，最后垂直连接到子节点顶部中心
   */
  private drawOrthogonalEdge(from: Point, to: Point, color: string, width: number, fromSize?: { width: number; height: number }, toSize?: { width: number; height: number }): void {
    const sourceHalfHeight = (fromSize?.height || 36) / 2
    const targetHalfHeight = (toSize?.height || 36) / 2
    
    // 起点：父节点底部中心
    const startX = from.x
    const startY = from.y + sourceHalfHeight
    
    // 终点：子节点顶部中心
    const endX = to.x
    const endY = to.y - targetHalfHeight
    
    // 中间转折点：在父节点和子节点之间的中间高度
    const midY = startY + (endY - startY) / 2

    this.ctx.strokeStyle = color
    this.ctx.lineWidth = width
    this.ctx.lineCap = 'round'
    this.ctx.lineJoin = 'round'
    this.ctx.beginPath()
    
    // 绘制圆角正交折线
    this.ctx.moveTo(startX, startY)
    
    if (Math.abs(endX - startX) < 1) {
      // 垂直对齐：直接画直线
      this.ctx.lineTo(endX, endY)
    } else {
      // 使用圆角的正交折线
      const cornerRadius = 8
      
      // 第一段：从起点垂直向下到中间点附近
      this.ctx.lineTo(startX, midY - cornerRadius)
      
      // 第一个圆角
      if (endX > startX) {
        // 子节点在右边
        this.ctx.arcTo(startX, midY + cornerRadius, startX + cornerRadius, midY + cornerRadius, cornerRadius)
        // 第二段：水平向右
        this.ctx.lineTo(endX - cornerRadius, midY + cornerRadius)
        // 第二个圆角
        this.ctx.arcTo(endX, midY + cornerRadius, endX, midY + cornerRadius + cornerRadius, cornerRadius)
      } else {
        // 子节点在左边
        this.ctx.arcTo(startX, midY + cornerRadius, startX - cornerRadius, midY + cornerRadius, cornerRadius)
        // 第二段：水平向左
        this.ctx.lineTo(endX + cornerRadius, midY + cornerRadius)
        // 第二个圆角
        this.ctx.arcTo(endX, midY + cornerRadius, endX, midY + cornerRadius + cornerRadius, cornerRadius)
      }
      
      // 第三段：垂直向下到终点
      this.ctx.lineTo(endX, endY)
    }
    
    this.ctx.stroke()
  }

  /**
   * 绘制时间轴布局的边
   * 特点：子节点在时间轴的垂直方向展开，连线使用简单的正交线
   */
  private drawTimelineEdge(from: Point, to: Point, color: string, width: number, fromSize?: { width: number; height: number }, toSize?: { width: number; height: number }): void {
    const sourceHalfHeight = (fromSize?.height || 36) / 2
    const sourceHalfWidth = (fromSize?.width || 120) / 2
    const targetHalfHeight = (toSize?.height || 36) / 2
    const targetHalfWidth = (toSize?.width || 120) / 2
    
    const dx = to.x - from.x
    const dy = to.y - from.y
    
    this.ctx.strokeStyle = color
    this.ctx.lineWidth = width
    this.ctx.lineCap = 'round'
    this.ctx.lineJoin = 'round'
    this.ctx.beginPath()
    
    // 判断连线方向
    if (Math.abs(dx) < 5) {
      // 垂直连线：子节点在正上方或正下方（横向时间轴）
      if (dy < 0) {
        // 子节点在上方
        this.ctx.moveTo(from.x, from.y - sourceHalfHeight)
        this.ctx.lineTo(to.x, to.y + targetHalfHeight)
      } else {
        // 子节点在下方
        this.ctx.moveTo(from.x, from.y + sourceHalfHeight)
        this.ctx.lineTo(to.x, to.y - targetHalfHeight)
      }
    } else if (Math.abs(dy) < 5) {
      // 水平连线：子节点在正左方或正右方（纵向时间轴）
      if (dx < 0) {
        // 子节点在左边
        this.ctx.moveTo(from.x - sourceHalfWidth, from.y)
        this.ctx.lineTo(to.x + targetHalfWidth, to.y)
      } else {
        // 子节点在右边
        this.ctx.moveTo(from.x + sourceHalfWidth, from.y)
        this.ctx.lineTo(to.x - targetHalfWidth, to.y)
      }
    } else {
      // 斜向连线：根据布局类型选择折线方向
      const isVerticalTimeline = this.layoutType === 'timeline-vertical'
      const cornerRadius = 6
      
      if (isVerticalTimeline) {
        // 纵向时间轴：从父节点水平出发，然后垂直转弯
        const startX = from.x + (dx > 0 ? sourceHalfWidth : -sourceHalfWidth)
        const startY = from.y
        const endX = to.x
        const endY = to.y
        
        this.ctx.moveTo(startX, startY)
        
        // 中间转折点
        const midX = startX + (endX - startX) / 2
        
        if (Math.abs(endY - startY) > 1) {
          this.ctx.lineTo(midX - cornerRadius, startY)
          
          if (endY > startY) {
            this.ctx.arcTo(midX + cornerRadius, startY, midX + cornerRadius, startY + cornerRadius, cornerRadius)
            this.ctx.lineTo(midX + cornerRadius, endY - cornerRadius)
            this.ctx.arcTo(midX + cornerRadius, endY, midX - cornerRadius, endY, cornerRadius)
          } else {
            this.ctx.arcTo(midX + cornerRadius, startY, midX + cornerRadius, startY - cornerRadius, cornerRadius)
            this.ctx.lineTo(midX + cornerRadius, endY + cornerRadius)
            this.ctx.arcTo(midX + cornerRadius, endY, midX - cornerRadius, endY, cornerRadius)
          }
          this.ctx.lineTo(endX, endY)
        } else {
          this.ctx.lineTo(endX, endY)
        }
      } else {
        // 横向时间轴：从父节点垂直出发，然后水平转弯
        const startX = from.x
        const startY = from.y + (dy > 0 ? sourceHalfHeight : -sourceHalfHeight)
        const endX = to.x
        const endY = to.y + (dy > 0 ? -targetHalfHeight : targetHalfHeight)
        
        this.ctx.moveTo(startX, startY)
        
        const midY = startY + (endY - startY) / 2
        
        if (Math.abs(endX - startX) > 1) {
          this.ctx.lineTo(startX, midY - cornerRadius)
          
          if (endX > startX) {
            this.ctx.arcTo(startX, midY + cornerRadius, startX + cornerRadius, midY + cornerRadius, cornerRadius)
            this.ctx.lineTo(endX - cornerRadius, midY + cornerRadius)
            this.ctx.arcTo(endX, midY + cornerRadius, endX, midY + cornerRadius + cornerRadius, cornerRadius)
          } else {
            this.ctx.arcTo(startX, midY + cornerRadius, startX - cornerRadius, midY + cornerRadius, cornerRadius)
            this.ctx.lineTo(endX + cornerRadius, midY + cornerRadius)
            this.ctx.arcTo(endX, midY + cornerRadius, endX, midY + cornerRadius + cornerRadius, cornerRadius)
          }
        }
        
        this.ctx.lineTo(endX, endY)
      }
    }
    
    this.ctx.stroke()
  }

  /** 绘制圆角矩形 */
  private roundRect(x: number, y: number, width: number, height: number, radius: number): void {
    this.ctx.beginPath()
    this.ctx.moveTo(x + radius, y)
    this.ctx.lineTo(x + width - radius, y)
    this.ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
    this.ctx.lineTo(x + width, y + height - radius)
    this.ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
    this.ctx.lineTo(x + radius, y + height)
    this.ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
    this.ctx.lineTo(x, y + radius)
    this.ctx.quadraticCurveTo(x, y, x + radius, y)
    this.ctx.closePath()
  }

  /** 绘制选框 */
  private drawSelectionBox(): void {
    if (!this.selectionBox) return
    
    // 将世界坐标转换为屏幕坐标
    const x = this.selectionBox.x * this.viewport.zoom + this.viewport.panX
    const y = this.selectionBox.y * this.viewport.zoom + this.viewport.panY
    const width = this.selectionBox.width * this.viewport.zoom
    const height = this.selectionBox.height * this.viewport.zoom
    
    // 绘制半透明填充
    this.ctx.fillStyle = 'rgba(59, 130, 246, 0.1)'  // 蓝色半透明
    this.ctx.fillRect(x, y, width, height)
    
    // 绘制边框
    this.ctx.strokeStyle = 'rgba(59, 130, 246, 0.8)'
    this.ctx.lineWidth = 1.5
    this.ctx.setLineDash([5, 3])  // 虚线效果
    this.ctx.strokeRect(x, y, width, height)
    this.ctx.setLineDash([])  // 重置虚线
  }

  /** 强制重绘 */
  forceRedraw(): void {
    this.needsRedraw = true
  }

  /** 调整尺寸 */
  resize(width: number, height: number): void {
    this.config.width = width
    this.config.height = height
    this.setupCanvas()
    this.needsRedraw = true
  }

  /** 导出为图片 */
  toDataURL(type: string = 'image/png'): string {
    return this.canvas.toDataURL(type)
  }
}

export default CanvasRenderer
