// graph.jsx — Force-directed note graph / mindmap view

const { useState, useEffect, useRef, useCallback } = React;

const REPULSION   = 4500;
const SPRING_LEN  = 160;
const SPRING_K    = 0.06;
const CENTER_K    = 0.0015;
const FRICTION    = 0.82;
const NODE_R      = 18;

function runTick(nodes, edges, pos, vel, w, h, pinned) {
  // Repulsion between all pairs
  for (let i = 0; i < nodes.length; i++) {
    for (let j = i + 1; j < nodes.length; j++) {
      const a = nodes[i].id, b = nodes[j].id;
      if (!pos[a] || !pos[b] || !vel[a] || !vel[b]) continue;
      const dx = pos[b].x - pos[a].x;
      const dy = pos[b].y - pos[a].y;
      const d2 = dx * dx + dy * dy || 1;
      const d  = Math.sqrt(d2);
      const f  = REPULSION / d2;
      const fx = (dx / d) * f, fy = (dy / d) * f;
      if (!pinned[a]) { vel[a].vx -= fx; vel[a].vy -= fy; }
      if (!pinned[b]) { vel[b].vx += fx; vel[b].vy += fy; }
    }
  }
  // Spring forces on edges
  edges.forEach(({ source, target }) => {
    if (!pos[source] || !pos[target] || !vel[source] || !vel[target]) return;
    const dx = pos[target].x - pos[source].x;
    const dy = pos[target].y - pos[source].y;
    const d  = Math.sqrt(dx * dx + dy * dy) || 1;
    const f  = (d - SPRING_LEN) * SPRING_K;
    const fx = (dx / d) * f, fy = (dy / d) * f;
    if (!pinned[source]) { vel[source].vx += fx; vel[source].vy += fy; }
    if (!pinned[target]) { vel[target].vx -= fx; vel[target].vy -= fy; }
  });
  // Centering + integration
  nodes.forEach(({ id }) => {
    if (!pos[id] || !vel[id]) return;
    if (pinned[id]) return;
    vel[id].vx += (w / 2 - pos[id].x) * CENTER_K;
    vel[id].vy += (h / 2 - pos[id].y) * CENTER_K;
    // Clamp velocity to prevent NaN/explosion
    if (!isFinite(vel[id].vx)) vel[id].vx = 0;
    if (!isFinite(vel[id].vy)) vel[id].vy = 0;
    vel[id].vx = Math.max(-50, Math.min(50, vel[id].vx * FRICTION));
    vel[id].vy = Math.max(-50, Math.min(50, vel[id].vy * FRICTION));
    pos[id].x += vel[id].vx;
    pos[id].y += vel[id].vy;
    if (!isFinite(pos[id].x)) pos[id].x = w / 2;
    if (!isFinite(pos[id].y)) pos[id].y = h / 2;
  });
}

const GraphView = ({ notes, sections, setRoute, onUpdateConnections }) => {
  const containerRef = useRef(null);
  const [size, setSize]       = useState({ w: 900, h: 600 });
  const [positions, setPositions] = useState({});
  const physRef  = useRef({ pos: {}, vel: {}, pinned: {} });
  const frameRef = useRef(null);
  const [hovered, setHovered]   = useState(null);
  const [selected, setSelected] = useState(null);
  const [filterLf, setFilterLf] = useState(null);
  const [transform, setTransform] = useState({ x: 0, y: 0, scale: 1 });
  const dragNode = useRef(null);
  const dragPan  = useRef(null);
  const dragMoved = useRef(false);
  const transformRef = useRef({ x: 0, y: 0, scale: 1 });
  const [showLabels, setShowLabels] = useState(true);
  const [connectMode, setConnectMode] = useState(false);
  const [connectSource, setConnectSource] = useState(null);
  const [tick, setTick] = useState(0);

  const visibleNotes = filterLf ? notes.filter(n => n.sectionId === filterLf) : notes;
  const edges = [];
  visibleNotes.forEach(n => {
    (n.connections || []).forEach(cid => {
      if (visibleNotes.find(x => x.id === cid) && !edges.find(e => (e.source === n.id && e.target === cid) || (e.source === cid && e.target === n.id))) {
        edges.push({ source: n.id, target: cid });
      }
    });
  });

  // Keep latest data in refs so the rAF loop never reads a stale closure
  const dataRef = useRef({ nodes: visibleNotes, edges, size });
  dataRef.current = { nodes: visibleNotes, edges, size };
  transformRef.current = transform;

  // Init physics on note set change
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const w = el.clientWidth, h = el.clientHeight;
    setSize({ w, h });
    const pos = {}, vel = {};
    visibleNotes.forEach((n, i) => {
      const angle = (i / Math.max(visibleNotes.length, 1)) * Math.PI * 2;
      const r = Math.min(w, h) * 0.28;
      pos[n.id] = { x: w / 2 + Math.cos(angle) * r + (Math.random() - 0.5) * 40, y: h / 2 + Math.sin(angle) * r + (Math.random() - 0.5) * 40 };
      vel[n.id] = { vx: 0, vy: 0 };
    });
    // Pre-warm
    for (let i = 0; i < 120; i++) runTick(visibleNotes, edges, pos, vel, w, h, {});
    physRef.current = { pos, vel, pinned: {} };
    setPositions({ ...pos });
  }, [notes.length, filterLf]);

  // Simulation loop — reads from refs so it always sees current data
  useEffect(() => {
    let running = true;
    const loop = () => {
      if (!running) return;
      try {
        const { pos, vel, pinned } = physRef.current;
        const { nodes, edges: curEdges, size: curSize } = dataRef.current;
        // Ensure every current node has a pos/vel entry — fill in any gaps from filter changes
        nodes.forEach(n => {
          if (!pos[n.id]) {
            pos[n.id] = { x: curSize.w / 2 + (Math.random() - 0.5) * 60, y: curSize.h / 2 + (Math.random() - 0.5) * 60 };
            vel[n.id] = { vx: 0, vy: 0 };
          }
        });
        runTick(nodes, curEdges, pos, vel, curSize.w, curSize.h, pinned);
        setPositions({ ...pos });
      } catch (err) {
        console.warn('graph tick error', err);
      }
      frameRef.current = requestAnimationFrame(loop);
    };
    frameRef.current = requestAnimationFrame(loop);
    return () => { running = false; cancelAnimationFrame(frameRef.current); };
  }, []); // mount once — refs handle data updates

  // All pointer events live on the SVG element — no per-node capture needed.
  // This prevents stuck drag state when the pointer moves off a node quickly.

  const cancelDragState = () => {
    if (dragNode.current) {
      physRef.current.pinned[dragNode.current.id] = false;
      dragNode.current = null;
    }
    dragPan.current = null;
  };

  // Node drag starts here — stopPropagation prevents the SVG background handler from firing
  const onNodePointerDown = (e, nodeId) => {
    e.stopPropagation();
    if (dragNode.current) physRef.current.pinned[dragNode.current.id] = false;
    dragMoved.current = false;
    dragNode.current = { id: nodeId, startMouse: { x: e.clientX, y: e.clientY } };
    physRef.current.pinned[nodeId] = true;
    physRef.current.vel[nodeId] = { vx: 0, vy: 0 };
  };

  // Background pan starts here — only fires when a node didn't stop propagation
  const onSvgPointerDown = (e) => {
    if (dragNode.current) { physRef.current.pinned[dragNode.current.id] = false; dragNode.current = null; }
    dragMoved.current = false;
    dragPan.current = { startMouse: { x: e.clientX, y: e.clientY }, startTransform: { ...transformRef.current } };
  };

  const onSvgPointerMove = (e) => {
    if (dragNode.current) {
      const nodeId = dragNode.current.id;
      const { pos, vel } = physRef.current;
      if (pos[nodeId]) {
        const scale = transformRef.current.scale;
        const rawDx = (e.clientX - dragNode.current.startMouse.x) / scale;
        const rawDy = (e.clientY - dragNode.current.startMouse.y) / scale;
        const dx = Math.max(-150, Math.min(150, rawDx));
        const dy = Math.max(-150, Math.min(150, rawDy));
        pos[nodeId].x += dx;
        pos[nodeId].y += dy;
        if (!isFinite(pos[nodeId].x)) pos[nodeId].x = dataRef.current.size.w / 2;
        if (!isFinite(pos[nodeId].y)) pos[nodeId].y = dataRef.current.size.h / 2;
        vel[nodeId] = { vx: 0, vy: 0 };
      }
      dragNode.current.startMouse = { x: e.clientX, y: e.clientY };
      dragMoved.current = true;
    } else if (dragPan.current) {
      const dx = e.clientX - dragPan.current.startMouse.x;
      const dy = e.clientY - dragPan.current.startMouse.y;
      const { x: sx, y: sy } = dragPan.current.startTransform;
      setTransform(t => ({ ...t, x: sx + dx, y: sy + dy }));
    }
  };

  const onSvgPointerUp = () => { cancelDragState(); };

  // Global safety net for pointer leaving window / blur
  useEffect(() => {
    window.addEventListener('pointerup', cancelDragState);
    window.addEventListener('pointercancel', cancelDragState);
    window.addEventListener('blur', cancelDragState);
    document.addEventListener('visibilitychange', cancelDragState);
    return () => {
      window.removeEventListener('pointerup', cancelDragState);
      window.removeEventListener('pointercancel', cancelDragState);
      window.removeEventListener('blur', cancelDragState);
      document.removeEventListener('visibilitychange', cancelDragState);
    };
  }, []);

  const onWheel = (e) => {
    e.preventDefault();
    const rect = containerRef.current.getBoundingClientRect();
    const cx = e.clientX - rect.left, cy = e.clientY - rect.top;
    const delta = e.deltaY > 0 ? 0.9 : 1.1;
    setTransform(t => {
      const scale = Math.min(3, Math.max(0.3, t.scale * delta));
      return { scale, x: cx - (cx - t.x) * (scale / t.scale), y: cy - (cy - t.y) * (scale / t.scale) };
    });
  };

  const lfsInView = [...new Set(visibleNotes.map(n => n.sectionId))];
  const selectedNote = selected ? notes.find(n => n.id === selected) : null;
  const hoveredNote  = hovered  ? notes.find(n => n.id === hovered)  : null;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg)', overflow: 'hidden' }}>
      {/* Toolbar */}
      <div style={{ flexShrink: 0, borderBottom: '1px solid var(--border)', padding: '10px 20px', display: 'flex', alignItems: 'center', gap: 10, background: 'var(--sidebar-bg)', flexWrap: 'wrap' }}>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--accent)', marginRight: 4 }}>~ / graph</div>
        <div style={{ height: 16, width: 1, background: 'var(--border)' }} />
        <div style={{ display: 'flex', gap: 6, flex: 1, flexWrap: 'wrap' }}>
          <button onClick={() => setFilterLf(null)}
            style={{ padding: '3px 10px', borderRadius: 20, border: '1px solid var(--border)', background: !filterLf ? 'var(--accent-dim)' : 'transparent', color: !filterLf ? 'var(--accent)' : 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
            Alle
          </button>
          {lfsInView.map(sectionId => {
            const lf = sections.find(l => l.id === sectionId);
            return lf ? (
              <button key={sectionId} onClick={() => setFilterLf(filterLf === sectionId ? null : sectionId)}
                style={{ padding: '3px 10px', borderRadius: 20, border: `1px solid ${lf.color}40`, background: filterLf === sectionId ? `${lf.color}20` : 'transparent', color: filterLf === sectionId ? lf.color : 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)', transition: 'all 0.15s' }}>
                {lf.short}
              </button>
            ) : null;
          })}
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <label style={{ fontSize: 12, color: 'var(--text-muted)', cursor: 'pointer', display: 'flex', gap: 5, alignItems: 'center' }}>
            <input type="checkbox" checked={showLabels} onChange={e => setShowLabels(e.target.checked)} style={{ accentColor: 'var(--accent)' }} />
            Labels
          </label>
          <button onClick={() => setTransform({ x: 0, y: 0, scale: 1 })}
            style={{ padding: '4px 10px', border: '1px solid var(--border)', borderRadius: 6, background: 'transparent', color: 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
            Reset
          </button>
          <button
            onClick={() => { setConnectMode(!connectMode); setConnectSource(null); setSelected(null); }}
            style={{
              padding: '4px 12px', borderRadius: 6, cursor: 'pointer', fontSize: 11,
              fontFamily: 'var(--font-mono)', transition: 'all 0.15s',
              border: connectMode ? '1px solid var(--accent)' : '1px solid var(--border)',
              background: connectMode ? 'var(--accent-dim)' : 'transparent',
              color: connectMode ? 'var(--accent)' : 'var(--text-muted)',
              boxShadow: connectMode ? '0 0 8px var(--accent-glow)' : 'none',
            }}
          >
            {connectMode
              ? connectSource
                ? `⚡ Ziel wählen — verbinden / lösen`
                : `⚡ Verbinden — Quelle wählen`
              : `⚡ Verbinden`}
          </button>
          <span style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
            {visibleNotes.length} Nodes · {edges.length} Edges
          </span>
        </div>
      </div>

      {/* Graph Canvas */}
      <div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}
        ref={containerRef}
        onWheel={onWheel}
      >
        {/* Dot grid bg */}
        <div style={{ position: 'absolute', inset: 0, backgroundImage: 'radial-gradient(circle, var(--border) 1px, transparent 1px)', backgroundSize: '28px 28px', opacity: 0.5, pointerEvents: 'none' }} />

        <svg
          width={size.w} height={size.h}
          style={{ position: 'absolute', inset: 0, cursor: dragPan.current ? 'grabbing' : dragNode.current ? 'grabbing' : 'grab' }}
          onPointerDown={onSvgPointerDown}
          onPointerMove={onSvgPointerMove}
          onPointerUp={onSvgPointerUp}
          onPointerLeave={onSvgPointerUp}
          onPointerCancel={onSvgPointerUp}
          onClick={() => { if (!dragMoved.current) setSelected(null); }}
        >
          <defs>
            {/* Glow filters per LF color */}
            {sections.map(lf => (
              <filter key={lf.id} id={`glow-${lf.id}`} x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur" />
                <feFlood floodColor={lf.color} floodOpacity="0.6" result="color" />
                <feComposite in="color" in2="blur" operator="in" result="glow" />
                <feMerge><feMergeNode in="glow" /><feMergeNode in="SourceGraphic" /></feMerge>
              </filter>
            ))}
            <filter id="glow-default">
              <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur" />
              <feFlood floodColor="#9b5cf6" floodOpacity="0.6" result="color" />
              <feComposite in="color" in2="blur" operator="in" result="glow" />
              <feMerge><feMergeNode in="glow" /><feMergeNode in="SourceGraphic" /></feMerge>
            </filter>
            <marker id="arrowhead" markerWidth="6" markerHeight="4" refX="6" refY="2" orient="auto">
              <polygon points="0 0, 6 2, 0 4" fill="var(--accent)" opacity="0.5" />
            </marker>
          </defs>

          <g transform={`translate(${transform.x},${transform.y}) scale(${transform.scale})`}>
            {/* Edges */}
            {edges.map(({ source, target }) => {
              const sp = positions[source], tp = positions[target];
              if (!sp || !tp) return null;
              const isHighlighted = hovered === source || hovered === target || selected === source || selected === target;
              const mx = (sp.x + tp.x) / 2 + (tp.y - sp.y) * 0.15;
              const my = (sp.y + tp.y) / 2 - (tp.x - sp.x) * 0.15;
              const srcLf = sections.find(l => l.id === notes.find(n => n.id === source)?.sectionId);
              return (
                <g key={`${source}-${target}`}>
                  {isHighlighted && (
                    <path d={`M${sp.x},${sp.y} Q${mx},${my} ${tp.x},${tp.y}`}
                      fill="none" stroke={srcLf?.color || 'var(--accent)'}
                      strokeWidth={2.5} strokeOpacity={0.6}
                      style={{ filter: `drop-shadow(0 0 4px ${srcLf?.color || '#9b5cf6'})` }}
                    />
                  )}
                  <path d={`M${sp.x},${sp.y} Q${mx},${my} ${tp.x},${tp.y}`}
                    fill="none"
                    stroke={isHighlighted ? (srcLf?.color || 'var(--accent)') : 'var(--border)'}
                    strokeWidth={isHighlighted ? 1.5 : 1}
                    strokeOpacity={isHighlighted ? 0.8 : 0.4}
                    markerEnd={isHighlighted ? 'url(#arrowhead)' : undefined}
                  />
                </g>
              );
            })}

            {/* Nodes */}
            {visibleNotes.map(note => {
              const p = positions[note.id];
              if (!p) return null;
              const lf = sections.find(l => l.id === note.sectionId);
              const color = lf?.color || '#9b5cf6';
              const isHov = hovered === note.id;
              const isSel = selected === note.id;
              const isConnSrc = connectSource === note.id;
              const isConnectable = connectMode && connectSource && connectSource !== note.id;
              const alreadyConnected = connectSource ? notes.find(n => n.id === connectSource)?.connections?.includes(note.id) : false;
              const connCount = (note.connections || []).filter(c => visibleNotes.find(n => n.id === c)).length;
              const r = NODE_R + Math.min(connCount * 3, 12);

              return (
                <g key={note.id}
                  onMouseEnter={() => setHovered(note.id)}
                  onMouseLeave={() => setHovered(null)}
                  onPointerDown={e => onNodePointerDown(e, note.id)}
                  onClick={(e) => {
                    e.stopPropagation();
                    if (dragMoved.current) return;
                    if (connectMode) {
                      if (!connectSource) {
                        setConnectSource(note.id);
                      } else if (connectSource === note.id) {
                        setConnectSource(null);
                      } else {
                        const srcNote = notes.find(n => n.id === connectSource);
                        if (srcNote) {
                          const conns = srcNote.connections || [];
                          const next = conns.includes(note.id)
                            ? conns.filter(c => c !== note.id)
                            : [...conns, note.id];
                          onUpdateConnections(connectSource, next);
                        }
                        setConnectSource(null);
                      }
                    } else {
                      setSelected(note.id === selected ? null : note.id);
                    }
                  }}
                  style={{ cursor: 'pointer' }}
                >
                  {/* Connect source pulse ring */}
                  {isConnSrc && (
                    <circle cx={p.x} cy={p.y} r={r + 10}
                      fill="none" stroke="#f59e0b" strokeWidth={2} strokeOpacity={0.7}
                      strokeDasharray="4 3"
                      style={{ filter: 'drop-shadow(0 0 8px #f59e0b)', animation: 'blink 1s step-end infinite' }}
                    />
                  )}
                  {/* Connectable target ring */}
                  {isConnectable && !alreadyConnected && (
                    <circle cx={p.x} cy={p.y} r={r + 6}
                      fill="none" stroke="#34d399" strokeWidth={1.5} strokeOpacity={0.5}
                      strokeDasharray="3 3"
                    />
                  )}
                  {/* Already connected indicator */}
                  {isConnectable && alreadyConnected && (
                    <circle cx={p.x} cy={p.y} r={r + 6}
                      fill="none" stroke="#f87171" strokeWidth={1.5} strokeOpacity={0.4}
                      strokeDasharray="3 3"
                    />
                  )}
                  {/* Glow ring when selected */}
                  {isSel && (
                    <circle cx={p.x} cy={p.y} r={r + 8}
                      fill="none" stroke={color} strokeWidth={2} strokeOpacity={0.4}
                      style={{ filter: `drop-shadow(0 0 8px ${color})` }}
                    />
                  )}
                  {/* Outer glow circle (hover) */}
                  {(isHov || isSel) && (
                    <circle cx={p.x} cy={p.y} r={r + 4}
                      fill={color} fillOpacity={0.12}
                    />
                  )}
                  {/* Main node circle */}
                  <circle cx={p.x} cy={p.y} r={r}
                    fill={isConnSrc ? '#f59e0b22' : isConnectable && !alreadyConnected ? '#34d39922' : `${color}22`}
                    stroke={isConnSrc ? '#f59e0b' : isConnectable && !alreadyConnected ? '#34d399' : alreadyConnected && isConnectable ? '#f87171' : color}
                    strokeWidth={isHov || isSel || isConnSrc || isConnectable ? 2 : 1.5}
                    style={{
                      filter: isHov || isSel ? `url(#glow-${note.sectionId}) drop-shadow(0 0 6px ${color})` : isConnSrc ? 'drop-shadow(0 0 8px #f59e0b)' : undefined,
                      transition: 'r 0.2s',
                      cursor: connectMode ? (isConnSrc ? 'not-allowed' : 'crosshair') : 'pointer',
                    }}
                  />
                  {/* Inner dot */}
                  <circle cx={p.x} cy={p.y} r={4} fill={color} fillOpacity={0.9} />

                  {/* Label */}
                  {(showLabels || isHov || isSel) && (
                    <text
                      x={p.x} y={p.y + r + 14}
                      textAnchor="middle"
                      fontSize={isHov || isSel ? 12 : 10}
                      fill={isHov || isSel ? 'var(--text)' : 'var(--text-muted)'}
                      fontFamily="var(--font-sans)"
                      fontWeight={isHov || isSel ? 600 : 400}
                      style={{ pointerEvents: 'none', userSelect: 'none' }}
                    >
                      {note.title.length > 22 ? note.title.slice(0, 20) + '…' : note.title}
                    </text>
                  )}

                  {/* LF badge (hovered) */}
                  {(isHov || isSel) && lf && (
                    <text x={p.x} y={p.y + 4} textAnchor="middle"
                      fontSize={8} fill={color} fontFamily="var(--font-mono)" fontWeight={700}
                      style={{ pointerEvents: 'none', userSelect: 'none' }}>
                      {lf.short}
                    </text>
                  )}
                </g>
              );
            })}
          </g>
        </svg>

        {/* Info panel (selected note) */}
        {selectedNote && (
          <div style={{
            position: 'absolute', right: 16, top: 16, width: 260,
            background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 12,
            padding: 16, boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
            animation: 'fadeIn 0.15s ease',
          }}>
            {(() => {
              const lf = sections.find(l => l.id === selectedNote.sectionId);
              const connectedNotes = notes.filter(n => selectedNote.connections?.includes(n.id));
              return (
                <>
                  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 8, marginBottom: 10 }}>
                    <div style={{ flex: 1 }}>
                      {lf && <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: lf.color, marginBottom: 4 }}>{lf.short}</div>}
                      <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text)', lineHeight: 1.3 }}>{selectedNote.title}</div>
                    </div>
                    <button onClick={() => setSelected(null)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex', padding: 2 }}>
                      <Icon name="x" size={14} />
                    </button>
                  </div>
                  <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 10 }}>
                    {selectedNote.tags.slice(0, 4).map(t => <TagBadge key={t} label={t} />)}
                  </div>
                  {connectedNotes.length > 0 && (
                    <div style={{ marginBottom: 12 }}>
                      <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Verknüpft mit</div>
                      {connectedNotes.map(cn => {
                        const cnLf = sections.find(l => l.id === cn.sectionId);
                        return (
                          <div key={cn.id} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '3px 0', cursor: 'pointer' }}
                            onClick={() => setSelected(cn.id)}>
                            <span style={{ width: 6, height: 6, borderRadius: '50%', background: cnLf?.color || '#9b5cf6', flexShrink: 0 }} />
                            <span style={{ fontSize: 12, color: 'var(--accent)', textDecoration: 'underline' }}>{cn.title}</span>
                          </div>
                        );
                      })}
                    </div>
                  )}
                  <div style={{ display: 'flex', gap: 6 }}>
                    <Btn variant="primary" size="sm" icon="book" onClick={() => setRoute({ page: 'note', noteId: selectedNote.id })}>Öffnen</Btn>
                    <Btn variant="secondary" size="sm" icon="edit" onClick={() => setRoute({ page: 'note-edit', noteId: selectedNote.id })}>Bearbeiten</Btn>
                  </div>
                </>
              );
            })()}
          </div>
        )}

        {/* Zoom controls */}
        <div style={{ position: 'absolute', left: 16, bottom: 16, display: 'flex', flexDirection: 'column', gap: 4 }}>
          {[{ label: '+', delta: 1.2 }, { label: '−', delta: 0.8 }].map(({ label, delta }) => (
            <button key={label} onClick={() => setTransform(t => ({ ...t, scale: Math.min(3, Math.max(0.3, t.scale * delta)) }))}
              style={{ width: 28, height: 28, borderRadius: 6, border: '1px solid var(--border)', background: 'var(--surface)', color: 'var(--text)', cursor: 'pointer', fontSize: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-mono)' }}>
              {label}
            </button>
          ))}
          <div style={{ width: 28, textAlign: 'center', fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginTop: 2 }}>
            {Math.round(transform.scale * 100)}%
          </div>
        </div>

        {/* Hint */}
        <div style={{ position: 'absolute', left: '50%', bottom: 14, transform: 'translateX(-50%)', fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', pointerEvents: 'none' }}>
          Scroll = Zoom · Drag Hintergrund = Pan · Drag Node = Verschieben · Klick = Details
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { GraphView });
