<script setup lang="ts">
import { ref, watch, nextTick, onMounted, onUnmounted, computed, inject, type Ref } from 'vue'
import type { Task } from '../models/classes/Task'
import { getPredecessorIds } from '../utils/predecessorUtils'
import { CanvasContextManager } from '../utils/canvasUtils'

// 定义 TaskBar 位置信息类型
interface TaskBarPosition {
  left: number
  top: number
  width: number
  height: number
}

// 定义月份分隔线位置类型
interface VerticalLine {
  left: number
  label?: string
}

// 定义 Props
interface Props {
  tasks: Task[]
  taskBarPositions: Record<number, TaskBarPosition>
  width: number
  height: number
  offsetLeft?: number // Canvas 在全局坐标系中的偏移量（用于虚拟渲染）
  offsetTop?: number // Canvas 在垂直方向的偏移量（用于虚拟渲染）
  highlightedTaskId: number | null
  highlightedTaskIds: Set<number>
  hoveredTaskId: number | string | null // v1.9.0 支持资源视图中的字符串ID
  // 月份分隔线配置
  verticalLines?: VerticalLine[]
  showVerticalLines?: boolean
  // 主题模式通过inject获取，不再需要props
}

const props = withDefaults(defineProps<Props>(), {
  verticalLines: () => [],
  showVerticalLines: true,
  offsetLeft: 0,
  offsetTop: 0,
})

// 通过inject获取主题
const injectedTheme = inject<Ref<'light' | 'dark'>>('gantt-theme', ref('light'))

// Canvas 引用
const canvasRef = ref<HTMLCanvasElement | null>(null)

// Canvas 上下文管理器
const canvasManager = new CanvasContextManager()

// requestAnimationFrame 防抖控制
let rafId: number | null = null
let pendingRedraw = false

// 当前主题（用于分隔线颜色）
const isDarkTheme = computed(() => injectedTheme.value === 'dark')

/**
 * 绘制关系线到 Canvas
 * 性能优势：相比 SVG 提升 18 倍渲染性能
 */
const drawLinks = () => {
  const ctx = canvasManager.getContext({
    canvas: canvasRef.value,
    canvasId: 'gantt-link-canvas',
    width: props.width,
    height: props.height,
    contextOptions: { alpha: true },
  })

  if (!ctx) {
    // eslint-disable-next-line no-console
    console.error('❌ Canvas context 获取失败，可能是尺寸超限')
    return
  }

  const displayWidth = props.width
  const displayHeight = props.height

  // 清空画布（透明）
  ctx.clearRect(0, 0, displayWidth, displayHeight)

  // 绘制月份分隔线（在关系线之前，作为背景）
  if (props.showVerticalLines && props.verticalLines && props.verticalLines.length > 0) {
    ctx.save()

    // 使用与旗帜一致的主题色：浅色模式 #409eff，暗色模式 #66b1ff
    const lineColor = isDarkTheme.value ? '#66b1ff' : '#409eff'
    ctx.strokeStyle = lineColor
    ctx.lineWidth = 1

    // 优化：一次性绘制所有垂直线，避免多次 stroke() 调用
    // 虚拟渲染：减去偏移量，转换为 Canvas 局部坐标
    ctx.beginPath()
    for (const line of props.verticalLines) {
      const localX = line.left - props.offsetLeft
      // 只绘制在 Canvas 可见范围内的线
      if (localX >= 0 && localX <= displayWidth) {
        ctx.moveTo(localX, 0)
        ctx.lineTo(localX, displayHeight)
      }
    }
    ctx.stroke()

    ctx.restore()
  }

  // 获取当前渲染的任务ID集合
  const currentTaskIds = new Set<number>()
  for (const task of props.tasks) {
    currentTaskIds.add(task.id)
  }

  // 是否处于高亮模式
  const isHighlightMode = props.highlightedTaskId !== null

  // 虚拟渲染：计算 Canvas 覆盖的范围
  const canvasStartX = props.offsetLeft
  const canvasEndX = props.offsetLeft + displayWidth
  const canvasStartY = props.offsetTop
  const canvasEndY = props.offsetTop + displayHeight

  // 定义线条数据类型
  interface LineData {
    x1: number
    y1: number
    x2: number
    y2: number
    c1x: number
    c1y: number
    c2x: number
    c2y: number
    arrowAngle: number
  }

  // 按样式分组线条数据（减少状态切换）
  const highlightedLines: LineData[] = []
  const hoveredLines: LineData[] = []
  const normalLines: LineData[] = []
  const fadedLines: LineData[] = [] // 高亮模式下的普通线条（半透明）

  for (const task of props.tasks) {
    if (!task.predecessor || !props.taskBarPositions[task.id]) continue

    const predecessorIds = getPredecessorIds(task.predecessor)

    for (const predecessorId of predecessorIds) {
      const fromBar = props.taskBarPositions[predecessorId]
      const toBar = props.taskBarPositions[task.id]

      if (!fromBar || !toBar || !currentTaskIds.has(predecessorId)) {
        continue
      }

      // 判断高亮状态
      const fromIsPrimary = props.highlightedTaskId === predecessorId
      const toIsPrimary = props.highlightedTaskId === task.id
      const fromIsHighlighted = props.highlightedTaskIds.has(predecessorId)
      const toIsHighlighted = props.highlightedTaskIds.has(task.id)
      const isLineHighlighted = fromIsHighlighted && toIsHighlighted

      // 判断 hover 状态
      const fromIsHovered = props.hoveredTaskId === predecessorId
      const toIsHovered = props.hoveredTaskId === task.id
      const isLineHovered = fromIsHovered || toIsHovered

      // 计算 Y 轴偏移（高亮时的位移）
      const fromYOffset = fromIsPrimary ? -8 : fromIsHighlighted ? -5 : 0
      const toYOffset = toIsPrimary ? -8 : toIsHighlighted ? -5 : 0

      // 计算坐标（全局坐标）
      const globalX1 = fromBar.left + fromBar.width
      const globalY1 = fromBar.top + fromBar.height / 2 + fromYOffset
      const globalX2 = toBar.left
      const globalY2 = toBar.top + toBar.height / 2 + toYOffset

      // 虚拟渲染：跳过不在 Canvas 覆盖范围内的关系线
      // 如果起点和终点都在 Canvas 外，跳过
      const lineMinX = Math.min(globalX1, globalX2)
      const lineMaxX = Math.max(globalX1, globalX2)
      const lineMinY = Math.min(globalY1, globalY2)
      const lineMaxY = Math.max(globalY1, globalY2)
      if (
        lineMaxX < canvasStartX ||
        lineMinX > canvasEndX ||
        lineMaxY < canvasStartY ||
        lineMinY > canvasEndY
      ) {
        continue // 完全在 Canvas 外，跳过
      }

      // 转换为 Canvas 局部坐标
      const x1 = globalX1 - props.offsetLeft
      const y1 = globalY1 - props.offsetTop
      const x2 = globalX2 - props.offsetLeft
      const y2 = globalY2 - props.offsetTop

      // 计算水平和垂直距离
      const dx = x2 - x1
      const dy = y2 - y1
      const distance = Math.sqrt(dx * dx + dy * dy)

      // 动态计算控制点，优化连接线的显示效果
      // 核心策略：避免链接线被 TaskBar 的直角遮挡
      // 1. 出点和入点需要有足够的"间距"，确保不被 TaskBar 直角遮挡
      // 2. 根据 TaskBar 的尺寸（宽度、高度）动态调整水平和垂直偏移

      // TaskBar 尺寸（用于计算绕过直角所需的偏移）
      const barWidth = fromBar.width
      const barHeight = fromBar.height

      // 计算水平距离占总距离的比例
      const horizontalRatio = Math.abs(dx) / (distance + 1) // +1 避免除零

      // ===== 水平偏移计算 =====
      // 策略：确保出点和入点的控制点都在 TaskBar 外侧，不会被角落遮挡
      // 从 A 出发：至少要超过 A 的右边界 + 安全距离
      // 进入 B：至少要在 B 的左边界 - 安全距离
      const horizontalSafetyMargin = 20 // TaskBar 角落安全距离
      let horizontalOffset = barHeight / 2 + horizontalSafetyMargin // 基础偏移

      if (horizontalRatio < 0.3) {
        // 接近垂直的情况：需要更大的水平偏移，确保出入角度清晰
        horizontalOffset = Math.max(barHeight / 2 + 25, 45)
      } else if (horizontalRatio < 0.6) {
        // 中等角度：适度调整
        horizontalOffset = barHeight / 2 + horizontalSafetyMargin
      } else {
        // 水平情况：偏移可以相对较小
        horizontalOffset = barWidth / 3 + 10
      }

      // ===== 垂直偏移计算 =====
      // 策略：根据垂直距离动态调整，确保线条绕过 TaskBar 的上下边界
      let verticalOffset = 0

      if (Math.abs(dy) > 0) {
        // 垂直偏移的最小值：必须大于 TaskBar 半高 + 安全距离，才能绕过直角
        const verticalSafetyMargin = 12 // TaskBar 角落安全距离
        const minVerticalOffset = barHeight / 2 + verticalSafetyMargin // 通常是 32-35px

        // 动态计算：根据两个 TaskBar 之间的垂直距离
        // 如果距离较近，偏移量应该相对较小（形成更陡峭的曲线）
        // 如果距离较远，偏移量应该相对较大（形成更平缓的曲线）
        const verticalGap = Math.abs(dy) - barHeight // 两个 TaskBar 之间的实际距离
        const dynamicOffset = Math.min(verticalGap * 0.5, 100) // 最大 100px

        verticalOffset = Math.max(minVerticalOffset, dynamicOffset)
      } else {
        // 完全水平的连接：垂直偏移为 0（线条直接通过中心）
        verticalOffset = 0
      }

      // ===== 控制点垂直比例调整 =====
      // 根据连接类型调整控制点的垂直位置
      // 接近垂直时：控制点垂直偏移更小，形成平缓的 S 形曲线
      // 接近水平时：控制点垂直偏移接近最大值，形成明显的进出角度
      const isNearVertical = horizontalRatio < 0.3
      const verticalControlRatio = isNearVertical ? 0.4 : 0.8 // 接近垂直时控制点垂直偏移更小

      // 起点控制点：水平向右延伸，垂直偏移较小（形成平缓的出线角度）
      const c1x = x1 + horizontalOffset
      const c1y = y1 + (dy > 0 ? verticalOffset : -verticalOffset) * verticalControlRatio

      // 终点控制点：水平向左延伸，垂直偏移较小（形成平缓的入线角度）
      const c2x = x2 - horizontalOffset
      const c2y = y2 - (dy > 0 ? verticalOffset : -verticalOffset) * verticalControlRatio

      // 预计算箭头角度（使用控制点到终点的角度，更准确）
      const arrowAngle = Math.atan2(y2 - c2y, x2 - c2x)
      const lineData: LineData = { x1, y1, x2, y2, c1x, c1y, c2x, c2y, arrowAngle }

      // 根据状态分组
      if (isLineHighlighted) {
        highlightedLines.push(lineData)
      } else if (isLineHovered) {
        hoveredLines.push(lineData)
      } else if (isHighlightMode) {
        fadedLines.push(lineData)
      } else {
        normalLines.push(lineData)
      }
    }
  }

  // 批量绘制：设置虚线样式（所有线条共用）
  ctx.setLineDash([6, 4])

  // 批量绘制高亮线条
  if (highlightedLines.length > 0) {
    ctx.strokeStyle = '#409eff'
    ctx.fillStyle = '#409eff'
    ctx.lineWidth = 4
    ctx.globalAlpha = 1

    ctx.beginPath()
    for (const line of highlightedLines) {
      ctx.moveTo(line.x1, line.y1)
      ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
    }
    ctx.stroke()

    // 批量绘制箭头
    for (const line of highlightedLines) {
      drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
    }
  }

  // 批量绘制悬停线条
  if (hoveredLines.length > 0) {
    ctx.strokeStyle = '#67c23a'
    ctx.fillStyle = '#67c23a'
    ctx.lineWidth = 3
    ctx.globalAlpha = 1

    ctx.beginPath()
    for (const line of hoveredLines) {
      ctx.moveTo(line.x1, line.y1)
      ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
    }
    ctx.stroke()

    // 批量绘制箭头
    for (const line of hoveredLines) {
      drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
    }
  }

  // 批量绘制普通线条
  if (normalLines.length > 0) {
    ctx.strokeStyle = '#c0c4cc'
    ctx.fillStyle = '#c0c4cc'
    ctx.lineWidth = 2
    ctx.globalAlpha = 1

    ctx.beginPath()
    for (const line of normalLines) {
      ctx.moveTo(line.x1, line.y1)
      ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
    }
    ctx.stroke()

    // 批量绘制箭头
    for (const line of normalLines) {
      drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
    }
  }

  // 批量绘制半透明线条（高亮模式下的普通线条）
  if (fadedLines.length > 0) {
    ctx.strokeStyle = '#c0c4cc'
    ctx.fillStyle = '#c0c4cc'
    ctx.lineWidth = 2
    ctx.globalAlpha = 0.2

    ctx.beginPath()
    for (const line of fadedLines) {
      ctx.moveTo(line.x1, line.y1)
      ctx.bezierCurveTo(line.c1x, line.c1y, line.c2x, line.c2y, line.x2, line.y2)
    }
    ctx.stroke()

    // 批量绘制箭头
    for (const line of fadedLines) {
      drawArrowOptimized(ctx, line.x2, line.y2, line.arrowAngle)
    }

    // 恢复透明度
    ctx.globalAlpha = 1
  }

  // uni-app 环境需要调用 draw() 方法将绘制内容渲染到画布
  canvasManager.draw()
}

/**
 * 优化版箭头绘制（减少参数传递，复用已设置的 fillStyle）
 */
const drawArrowOptimized = (
  ctx: CanvasRenderingContext2D,
  x2: number,
  y2: number,
  angle: number,
) => {
  const arrowLength = 8
  const arrowWidth = 4

  ctx.beginPath()
  // fillStyle 已在外部设置，无需重复设置
  ctx.moveTo(x2, y2)
  ctx.lineTo(
    x2 - arrowLength * Math.cos(angle) - arrowWidth * Math.sin(angle),
    y2 - arrowLength * Math.sin(angle) + arrowWidth * Math.cos(angle),
  )
  ctx.lineTo(
    x2 - arrowLength * Math.cos(angle) + arrowWidth * Math.sin(angle),
    y2 - arrowLength * Math.sin(angle) - arrowWidth * Math.cos(angle),
  )
  ctx.closePath()
  ctx.fill()
}

/**
 * 使用 requestAnimationFrame 优化的重绘调度器
 * 合并多个连续的重绘请求为单次绘制
 */
const scheduleRedraw = () => {
  if (pendingRedraw) {
    // 已有待处理的重绘请求，跳过
    return
  }

  pendingRedraw = true

  // 取消之前的 RAF（如果有）
  if (rafId !== null) {
    cancelAnimationFrame(rafId)
  }

  // 在下一帧绘制
  rafId = requestAnimationFrame(() => {
    pendingRedraw = false
    rafId = null
    drawLinks()
  })
}

// 监听相关状态变化，自动重绘 Canvas
watch(
  [
    () => props.taskBarPositions,
    () => props.tasks,
    () => props.highlightedTaskId,
    () => props.highlightedTaskIds,
    () => props.hoveredTaskId,
    () => props.width,
    () => props.height,
    () => props.verticalLines,
    () => props.showVerticalLines,
    () => props.offsetLeft, // 监听虚拟渲染的偏移量变化
    () => props.offsetTop,
    injectedTheme, // 监听inject的主题变化（computed会自动响应）
  ],
  () => {
    // 使用 RAF 调度重绘，合并连续的多次变化为单次绘制
    scheduleRedraw()
  },
  { deep: false }, // shallowRef 不需要 deep
)

// 组件挂载后初始化绘制
onMounted(() => {
  // 不再监听 document.documentElement，直接使用props.theme
  nextTick(() => {
    drawLinks()
  })
})

// 组件卸载时清理
onUnmounted(() => {
  // 取消待处理的 RAF
  if (rafId !== null) {
    cancelAnimationFrame(rafId)
    rafId = null
  }
})

// 暴露方法供父组件调用
defineExpose({
  redraw: drawLinks,
})
</script>

<template>
  <canvas
    ref="canvasRef"
    canvas-id="gantt-link-canvas"
    class="gantt-links-canvas"
    type="2d"
    :hidpi="true"
    :style="{
      position: 'absolute',
      left: `${offsetLeft}px`,
      top: `${offsetTop}px`,
      width: `${width}px`,
      height: `${height}px`,
      zIndex: highlightedTaskId !== null ? 2000 : 500,
      pointerEvents: 'none',
    }"
  />
</template>

<style scoped>
.gantt-links-canvas {
  display: block;
  background: transparent; /* 确保背景透明 */
  opacity: 1; /* 确保不透明度为 100% */
}
</style>
