<template>
  <div class="collab-graph">
    <div class="collab-graph__header">
      <span class="collab-graph__title">协作关系图</span>
      <span v-if="contractEdges.length > 0" class="collab-graph__count">
        {{ contractEdges.length }} 项契约 · {{ noticeEdges.length }} 项数据流
      </span>
    </div>

    <div class="collab-graph__legend">
      <span class="legend-item">
        <span class="legend-line legend-line--solid"></span>协作契约
      </span>
      <span class="legend-item">
        <span class="legend-line legend-line--dashed"></span>数据流向
      </span>
      <span class="legend-item">
        <span class="legend-dot legend-dot--passed"></span>验收通过
      </span>
      <span class="legend-item">
        <span class="legend-dot legend-dot--rework"></span>返工/失败
      </span>
    </div>

    <svg
      v-if="nodes.length > 0"
      :viewBox="`0 0 ${SVG_WIDTH} ${SVG_HEIGHT}`"
      class="collab-graph__svg"
      preserveAspectRatio="xMidYMid meet"
    >
      <defs>
        <marker
          id="collab-arrow-contract"
          viewBox="0 0 10 10"
          refX="8"
          refY="5"
          markerWidth="6"
          markerHeight="6"
          orient="auto-start-reverse"
        >
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#8c8c8c" />
        </marker>
        <marker
          id="collab-arrow-notice"
          viewBox="0 0 10 10"
          refX="8"
          refY="5"
          markerWidth="6"
          markerHeight="6"
          orient="auto-start-reverse"
        >
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#1890ff" />
        </marker>
      </defs>

      <!-- Contract edges (solid lines) -->
      <g class="collab-graph__edges">
        <line
          v-for="(edge, i) in contractEdges"
          :key="`contract-${i}`"
          :x1="edge.x1"
          :y1="edge.y1"
          :x2="edge.x2"
          :y2="edge.y2"
          class="collab-edge collab-edge--contract"
          :class="{ 'collab-edge--delivered': edge.delivered }"
          marker-end="url(#collab-arrow-contract)"
        >
          <title>{{ edge.from }} → {{ edge.to }}: {{ edge.content }}</title>
        </line>
      </g>

      <!-- Notice edges (dashed animated lines) -->
      <g class="collab-graph__edges">
        <line
          v-for="(edge, i) in noticeEdges"
          :key="`notice-${i}`"
          :x1="edge.x1"
          :y1="edge.y1"
          :x2="edge.x2"
          :y2="edge.y2"
          class="collab-edge collab-edge--notice"
          marker-end="url(#collab-arrow-notice)"
        >
          <title>{{ edge.from }} → {{ edge.to }}: {{ edge.content }}</title>
        </line>
      </g>

      <!-- Nodes -->
      <g class="collab-graph__nodes">
        <g
          v-for="node in nodes"
          :key="node.name"
          :transform="`translate(${node.x}, ${node.y})`"
        >
          <circle
            :r="NODE_RADIUS"
            class="collab-node"
            :class="`collab-node--${node.status}`"
          />
          <text
            class="collab-node__initial"
            text-anchor="middle"
            dy="0.35em"
          >{{ node.initial }}</text>
          <text
            class="collab-node__name"
            text-anchor="middle"
            :y="NODE_RADIUS + 14"
          >{{ node.name }}</text>
          <text
            v-if="node.hasRisk"
            class="collab-node__risk"
            text-anchor="middle"
            :y="-(NODE_RADIUS + 4)"
          >!</text>
        </g>
      </g>
    </svg>

    <div v-else class="collab-graph__empty">
      <span class="collab-graph__empty-text">暂无协作关系</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { ICollaborationGraphData, IReviewResult } from '@/api/types'

const props = defineProps<{
  graphData: ICollaborationGraphData
}>()

// SVG dimensions and layout constants
const SVG_WIDTH = 340
const SVG_HEIGHT = 280
const NODE_RADIUS = 22

type NodeStatus = 'default' | 'passed' | 'rework' | 'failed'

interface IGraphNode {
  name: string
  initial: string
  x: number
  y: number
  status: NodeStatus
  hasRisk: boolean
}

interface IGraphEdge {
  x1: number
  y1: number
  x2: number
  y2: number
  from: string
  to: string
  content: string
  delivered?: boolean
}

/** Collect unique expert names from all data sources (contracts, notices, reviews, risks). */
const experts = computed<string[]>(() => {
  const names = new Set<string>()
  for (const c of props.graphData.contracts) {
    if (c.from_expert) names.add(c.from_expert)
    if (c.to_expert) names.add(c.to_expert)
  }
  for (const n of props.graphData.notices) {
    if (n.from_expert) names.add(n.from_expert)
    if (n.to_expert) names.add(n.to_expert)
  }
  for (const r of props.graphData.reviews) {
    if (r.expert) names.add(r.expert)
  }
  for (const r of props.graphData.risks) {
    if (r.expert) names.add(r.expert)
    if (r.expert_name) names.add(r.expert_name)
  }
  return Array.from(names)
})

/** Determine the review status of an expert based on review_result events. */
function getExpertStatus(name: string, reviews: IReviewResult[]): NodeStatus {
  const expertReviews = reviews.filter((r) => r.expert === name)
  if (expertReviews.length === 0) return 'default'
  // If any review failed with final_status='failed' → failed
  if (expertReviews.some((r) => !r.passed && r.final_status === 'failed')) return 'failed'
  // If any review failed (rework in progress) → rework
  if (expertReviews.some((r) => !r.passed)) return 'rework'
  // If all reviews passed → passed
  if (expertReviews.every((r) => r.passed)) return 'passed'
  return 'default'
}

/** Compute node positions using circular layout. */
const nodes = computed<IGraphNode[]>(() => {
  const names = experts.value
  const n = names.length
  if (n === 0) return []
  const cx = SVG_WIDTH / 2
  const cy = SVG_HEIGHT / 2
  const radius = Math.min(SVG_WIDTH, SVG_HEIGHT) / 2 - 55
  return names.map((name, i) => {
    const angle = (i / n) * 2 * Math.PI - Math.PI / 2 // start from top
    return {
      name,
      initial: name.charAt(0) || '?',
      x: cx + radius * Math.cos(angle),
      y: cy + radius * Math.sin(angle),
      status: getExpertStatus(name, props.graphData.reviews),
      hasRisk: props.graphData.risks.some(
        (r) => r.expert === name || r.expert_name === name,
      ),
    }
  })
})

/** Clip an edge to start/end at circle boundaries so arrowheads are visible. */
function clipEdge(
  fromX: number,
  fromY: number,
  toX: number,
  toY: number,
): { x1: number; y1: number; x2: number; y2: number } {
  const dx = toX - fromX
  const dy = toY - fromY
  const dist = Math.sqrt(dx * dx + dy * dy)
  if (dist === 0) return { x1: fromX, y1: fromY, x2: toX, y2: toY }
  const ux = dx / dist
  const uy = dy / dist
  return {
    x1: fromX + ux * NODE_RADIUS,
    y1: fromY + uy * NODE_RADIUS,
    x2: toX - ux * (NODE_RADIUS + 4), // extra offset for arrowhead
    y2: toY - uy * (NODE_RADIUS + 4),
  }
}

/** Build edge list from collaboration contracts (solid lines). */
const contractEdges = computed<IGraphEdge[]>(() => {
  return props.graphData.contracts
    .filter((c) => c.from_expert && c.to_expert)
    .flatMap((c): IGraphEdge[] => {
      const from = nodes.value.find((n) => n.name === c.from_expert)
      const to = nodes.value.find((n) => n.name === c.to_expert)
      if (!from || !to) return []
      const clipped = clipEdge(from.x, from.y, to.x, to.y)
      return [{
        ...clipped,
        from: c.from_expert,
        to: c.to_expert,
        content: c.content_description,
        delivered: c.status === 'delivered',
      }]
    })
})

/** Build edge list from collaboration notices (dashed animated lines). */
const noticeEdges = computed<IGraphEdge[]>(() => {
  return props.graphData.notices
    .filter((n) => n.from_expert && n.to_expert)
    .flatMap((n): IGraphEdge[] => {
      const from = nodes.value.find((node) => node.name === n.from_expert)
      const to = nodes.value.find((node) => node.name === n.to_expert)
      if (!from || !to) return []
      const clipped = clipEdge(from.x, from.y, to.x, to.y)
      return [{
        ...clipped,
        from: n.from_expert,
        to: n.to_expert,
        content: n.content_description,
      }]
    })
})
</script>

<style scoped>
.collab-graph {
  padding: 12px 16px;
  border-left: 3px solid #1890ff;
  background: #f0f8ff;
  border-radius: 4px;
}

.collab-graph__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.collab-graph__title {
  font-weight: 600;
  color: #1890ff;
  font-size: 14px;
}

.collab-graph__count {
  font-size: 12px;
  color: #666;
}

.collab-graph__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 8px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: #666;
}

.legend-line {
  display: inline-block;
  width: 20px;
  height: 0;
  border-top: 2px solid #8c8c8c;
}

.legend-line--solid {
  border-top-style: solid;
}

.legend-line--dashed {
  border-top: 2px dashed #1890ff;
}

.legend-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

.legend-dot--passed {
  background: #52c41a;
}

.legend-dot--rework {
  background: #ff4d4f;
}

.collab-graph__svg {
  width: 100%;
  max-height: 300px;
}

/* Edges */
.collab-edge--contract {
  stroke: #8c8c8c;
  stroke-width: 1.5;
  fill: none;
  opacity: 0.7;
}

.collab-edge--contract.collab-edge--delivered {
  stroke: #52c41a;
  opacity: 0.9;
}

.collab-edge--notice {
  stroke: #1890ff;
  stroke-width: 2;
  stroke-dasharray: 6 4;
  fill: none;
  animation: collab-dash-flow 1s linear infinite;
}

@keyframes collab-dash-flow {
  to {
    stroke-dashoffset: -10;
  }
}

/* Nodes */
.collab-node {
  stroke-width: 2;
  transition: fill 0.3s, stroke 0.3s;
}

.collab-node--default {
  fill: #f5f5f5;
  stroke: #d9d9d9;
}

.collab-node--passed {
  fill: #f6ffed;
  stroke: #52c41a;
}

.collab-node--rework {
  fill: #fff2f0;
  stroke: #ff4d4f;
}

.collab-node--failed {
  fill: #fff2f0;
  stroke: #ff4d4f;
  stroke-dasharray: 4 2;
}

.collab-node__initial {
  font-size: 14px;
  font-weight: 600;
  fill: #333;
  pointer-events: none;
  user-select: none;
}

.collab-node__name {
  font-size: 11px;
  fill: #666;
  pointer-events: none;
  user-select: none;
}

.collab-node__risk {
  font-size: 16px;
  font-weight: bold;
  fill: #fa8c16;
}

.collab-graph__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 120px;
}

.collab-graph__empty-text {
  font-size: 13px;
  color: #999;
}
</style>
