<script setup lang="ts">
/**
 * FlowCanvas — SVG-Kanten + Auto-Layout + AgentNode-Karten
 *
 * Props:
 *   agents     – Liste der AgentNodeData
 *   positions  – Record<id, {x,y}> mit aktuellen Positionen
 *
 * Emits:
 *   select     – Agent ausgewählt (id)
 *   add        – Neuen Agent hinzufügen
 *   updatePositions – Positionsänderung
 */
import { computed, onMounted, onUnmounted, ref, nextTick, watch } from 'vue'
import type { AgentNodeData } from '../../../composables/useFlowLayout'
import { autoLayout, buildEdges, curve } from '../../../composables/useFlowLayout'
import { icons } from '../../../composables/icons'
import AgentNode from './AgentNode.vue'

const props = defineProps<{
  agents: AgentNodeData[]
  positions: Record<string, { x: number; y: number }>
  enteringIds: string[]
}>()

const emit = defineEmits<{
  select: [id: string]
  add: []
  resetLayout: []
  updatePositions: [positions: Record<string, { x: number; y: number }>]
}>()

/* ── Refs ───────────────────────────────────────── */
const flowRef = ref<HTMLElement | null>(null)
const svgRef = ref<SVGSVGElement | null>(null)
const edgesDefs = ref('')
const edgesPaths = ref('')
const edgesPulses = ref('')

/* ── Computed ───────────────────────────────────── */
const agentCount = computed(() => props.agents.length)

const autoPositions = computed(() => autoLayout(props.agents))

// Layout label
const layoutLabel = computed(() => {
  const n = props.agents.length - 1
  if (n <= 0) return `${props.agents.length} Agents`
  const maxPerRow = n <= 2 ? 2 : n <= 6 ? 3 : n <= 9 ? 3 : 4
  const rows = Math.ceil(n / maxPerRow)
  const hasCustom = Object.keys(props.positions).length > 0
  if (hasCustom) {
    return `✦ Eigenes Layout · ${props.agents.length} Agents gesamt`
  }
  return `Layout: ${rows} ${rows === 1 ? 'Reihe' : 'Reihen'} × ${maxPerRow} · ${props.agents.length} Agents gesamt`
})

/* ── Edge Rendering ────────────────────────────── */
function isActive(status: string) {
  return status === 'work' || status === 'think'
}

function renderEdges() {
  const flow = flowRef.value
  if (!flow) return

  const fr = flow.getBoundingClientRect()
  const svg = svgRef.value
  if (!svg) return

  svg.setAttribute('width', String(fr.width))
  svg.setAttribute('height', String(fr.height))
  svg.setAttribute('viewBox', `0 0 ${fr.width} ${fr.height}`)

  // Node centers in pixel coordinates
  function center(id: string): { x: number; y: number } | null {
    const el = flow.querySelector(`.node[data-id="${id}"]`) as HTMLElement | null
    if (!el) return null
    const nr = el.getBoundingClientRect()
    return {
      x: nr.left - fr.left + nr.width / 2,
      y: nr.top - fr.top + nr.height / 2,
    }
  }

  const edgeList = buildEdges(props.agents)

  let defs = `<defs><linearGradient id="eg2" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#4f7cff"/><stop offset="1" stop-color="#b557f6"/></linearGradient></defs>`
  let paths = ''
  let pulses = ''
  let idCounter = 0

  edgeList.forEach((e) => {
    const c1 = center(e.a)
    const c2 = center(e.b)
    if (!c1 || !c2) return

    const d = curve(c1, c2)
    const a1Status = props.agents.find(a => a.id === e.a)?.status || 'idle'
    const a2Status = props.agents.find(a => a.id === e.b)?.status || 'idle'
    const live = isActive(a1Status) && isActive(a2Status)
    const pathId = `ep${idCounter++}`

    if (e.kind === 'flow' && live) {
      // Active flow: gradient stroke + animate pulse
      paths += `<path id="${pathId}" d="${d}" fill="none" stroke="url(#eg2)" stroke-width="2.2" opacity="0.85"/>`
      paths += `<path d="${d}" fill="none" stroke="#3ddc97" stroke-width="2.2" stroke-dasharray="5 20" opacity="0.8" style="animation:dashmove 1.1s linear infinite"/>`
      pulses += `<circle r="3.4" fill="#eafff6"><animateMotion dur="2s" repeatCount="indefinite" rotate="auto"><mpath href="#${pathId}"/></animateMotion></circle>`
    } else if (e.kind === 'flow') {
      // Inactive flow
      paths += `<path id="${pathId}" d="${d}" fill="none" stroke="url(#eg2)" stroke-width="1.8" opacity="0.45"/>`
      pulses += `<circle r="2.8" fill="#c9b8ff" opacity="0.7"><animateMotion dur="3s" repeatCount="indefinite"><mpath href="#${pathId}"/></animateMotion></circle>`
    } else {
      // Orchestration (Iris → Agent)
      const targetAgent = props.agents.find(a => a.id === e.b)
      const op = targetAgent && isActive(targetAgent.status) ? 0.45 : 0.18
      paths += `<path d="${d}" fill="none" stroke="#7c6cff" stroke-width="1.2" stroke-dasharray="2 6" opacity="${op}"/>`
    }
  })

  edgesDefs.value = defs
  edgesPaths.value = paths
  edgesPulses.value = pulses
}

/* ── Resize Observer ──────────────────────────── */
let resizeObserver: ResizeObserver | null = null

function setupObserver() {
  if (!flowRef.value) return
  resizeObserver = new ResizeObserver(() => {
    // Debounce via requestAnimationFrame
    if (!debounceRaf) debounceRaf = requestAnimationFrame(() => {
      debounceRaf = null
      renderEdges()
    })
  })
  resizeObserver.observe(flowRef.value)
}

let debounceRaf: number | null = null

function teardownObserver() {
  if (resizeObserver) {
    resizeObserver.disconnect()
    resizeObserver = null
  }
  if (debounceRaf) {
    cancelAnimationFrame(debounceRaf)
    debounceRaf = null
  }
}

onMounted(() => {
  setupObserver()
  // Initial render after DOM settles
  requestAnimationFrame(() => renderEdges())
})

onUnmounted(() => {
  teardownObserver()
})

// Re-render edges when agents or positions change
watch(
  () => [props.agents.length, props.positions],
  () => {
    // Wait for DOM update (AgentNode transitions)
    setTimeout(() => renderEdges(), 200)
  },
  { deep: true }
)

/* ── Drag & Drop ──────────────────────────────── */
const DRAG_THRESHOLD = 5

interface DragState {
  id: string
  startX: number
  startY: number
  ox: number
  oy: number
  moved: boolean
  raf: number | null
}

let drag: DragState | null = null

function onPointerDown(e: PointerEvent) {
  const node = (e.target as HTMLElement).closest('.node') as HTMLElement | null
  if (!node) return

  e.preventDefault()
  const nr = node.getBoundingClientRect()

  drag = {
    id: node.dataset.id || '',
    startX: e.clientX,
    startY: e.clientY,
    ox: e.clientX - (nr.left + nr.width / 2),
    oy: e.clientY - (nr.top + nr.height / 2),
    moved: false,
    raf: null,
  }

  node.setPointerCapture(e.pointerId)
}

function onPointerMove(e: PointerEvent) {
  if (!drag) return

  const dist = Math.hypot(e.clientX - drag.startX, e.clientY - drag.startY)
  if (!drag.moved && dist < DRAG_THRESHOLD) return

  if (!drag.moved) {
    drag.moved = true
    const node = flowRef.value?.querySelector(`.node[data-id="${drag.id}"]`) as HTMLElement | null
    if (node) node.classList.add('dragging')
  }

  const flow = flowRef.value
  if (!flow) return

  const fr = flow.getBoundingClientRect()
  const x = Math.max(8, Math.min(92, ((e.clientX - drag.ox - fr.left) / fr.width) * 100))
  const y = Math.max(10, Math.min(92, ((e.clientY - drag.oy - fr.top) / fr.height) * 100))

  // Direct DOM manipulation for responsiveness
  const node = flow.querySelector(`.node[data-id="${drag.id}"]`) as HTMLElement | null
  if (node) {
    node.style.left = x + '%'
    node.style.top = y + '%'
  }

  // Update positions state
  const newPos = { ...props.positions }
  newPos[drag.id] = { x, y }
  emit('updatePositions', newPos)

  // Debounced edge re-render
  if (!drag.raf) {
    drag.raf = requestAnimationFrame(() => {
      renderEdges()
      if (drag) drag.raf = null
    })
  }
}

function onPointerUp() {
  if (!drag) return

  const node = flowRef.value?.querySelector(`.node[data-id="${drag.id}"]`) as HTMLElement | null
  if (node) node.classList.remove('dragging')

  if (!drag.moved) {
    // Was a click — emit select
    emit('select', drag.id)
  }

  drag = null
}

/* ── Keyboard handler for Enter key on buttons ── */
function handleReset() {
  emit('resetLayout')
  nextTick(() => renderEdges())
}
</script>

<template>
  <div
    ref="flowRef"
    class="flow"
    @pointerdown="onPointerDown"
    @pointermove="onPointerMove"
    @pointerup="onPointerUp"
    @pointercancel="onPointerUp"
  >
    <!-- Header -->
    <div class="flow-h">
      <span class="header-icon" v-html="icons.flow || ''"></span>
      <h3>Live-Orchestrierung</h3>
      <span class="flow-count">{{ agentCount }} Agents</span>

      <button
        class="reset-btn"
        title="Auto-Layout wiederherstellen"
        @click="handleReset"
      >
        <span class="btn-icon" v-html="icons.flow || ''"></span>
        Reset
      </button>

      <button class="add-btn" @click="$emit('add')">
        <span class="btn-icon" v-html="icons.plus || ''"></span>
        Agent hinzufügen
      </button>
    </div>

    <!-- SVG Layer -->
    <svg
      ref="svgRef"
      class="edges"
      v-html="edgesDefs + edgesPaths + edgesPulses"
    ></svg>

    <!-- Agent Nodes -->
    <div class="nodes-layer" v-html="''"></div>
    <AgentNode
      v-for="agent in agents"
      :key="agent.id"
      :agent="agent"
      :left="(positions[agent.id] || autoPositions[agent.id] || { x: 50, y: 50 }).x"
      :top="(positions[agent.id] || autoPositions[agent.id] || { x: 50, y: 50 }).y"
      :entering="enteringIds.includes(agent.id)"
      :data-id="agent.id"
      @select="(id: string) => $emit('select', id)"
    />

    <!-- Layout Label -->
    <div class="layout-label">{{ layoutLabel }}</div>
  </div>
</template>

<style scoped>
.flow {
  flex: 1;
  position: relative;
  border-radius: var(--r);
  overflow: hidden;
  min-height: 0;
  border: 1px solid var(--line);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(124, 108, 255, 0.10), transparent 60%);
}

.flow-h {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 16px;
}

.flow-h h3 {
  margin: 0;
  font-family: 'Space Grotesk', sans-serif;
  font-weight: 600;
  font-size: 14.5px;
  color: var(--tx);
}

.header-icon :deep(svg) {
  width: 18px;
  height: 18px;
  color: var(--a-mid);
}

.flow-count {
  font-family: 'JetBrains Mono', monospace;
  font-size: 11.5px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 20px;
  background: rgba(124, 108, 255, 0.14);
  color: var(--tx-2);
  font-variant-numeric: tabular-nums;
}

.reset-btn {
  height: 30px;
  padding: 0 11px;
  border-radius: 9px;
  background: rgba(124, 108, 255, 0.1);
  border: 1px solid var(--line-2);
  color: var(--tx-2);
  font-family: 'Manrope', sans-serif;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: background 0.15s;
}

.reset-btn:hover {
  background: rgba(124, 108, 255, 0.18);
  color: var(--tx);
}

.reset-btn .btn-icon :deep(svg) {
  width: 13px;
  height: 13px;
}

.add-btn {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 34px;
  padding: 0 14px;
  border-radius: 10px;
  background: var(--grad);
  border: none;
  color: #fff;
  font-family: 'Manrope', sans-serif;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--glow-purple);
  transition: filter 0.15s;
}

.add-btn:hover {
  filter: brightness(1.1);
}

.add-btn .btn-icon :deep(svg) {
  width: 15px;
  height: 15px;
}

/* ── SVG Layer ────────────────────────────────── */
.edges {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ── Layout Label ─────────────────────────────── */
.layout-label {
  position: absolute;
  bottom: 14px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px;
  color: var(--tx-3);
  background: rgba(10, 8, 24, 0.7);
  padding: 5px 14px;
  border-radius: 20px;
  border: 1px solid var(--line);
  backdrop-filter: blur(8px);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Drag state ───────────────────────────────── */
:deep(.node.dragging) {
  cursor: grabbing;
  transition: none !important;
  z-index: 10;
}

:deep(.node.dragging .ncard) {
  box-shadow: 0 0 0 2px var(--a-mid), 0 0 36px -2px rgba(124, 108, 255, 0.9) !important;
  transform: scale(1.04);
}

/* Dash animation */
@keyframes dashmove {
  to {
    stroke-dashoffset: -28;
  }
}

/* Node cursor */
:deep(.node) {
  cursor: grab;
}

:deep(.node.dragging) {
  cursor: grabbing;
}
</style>
