/* Krytz · Force-directed dependency graph
 *
 * Tasks as nodes, dependencies as edges. Simulation runs on rAF
 * with O(n²) charge + spring + center. Nodes have a "z" depth
 * that gives a subtle 3D look (scale + opacity). Pointer drag
 * grabs nodes; wheel zooms. No external libs.
 */

(function () {
  const { useEffect, useRef, useState, useMemo, useCallback } = React;

  /* ── Seed data ─────────────────────────────────────────────────── */
  const SEED_NODES = [
    { id: 'launch',   label: 'Launch v2.3',         group: 'milestone', priority: 0.94 },
    { id: 'deploy',   label: 'Prod rollout',        group: 'technical', priority: 0.86 },
    { id: 'qa',       label: 'QA sign-off',         group: 'technical', priority: 0.72 },
    { id: 'bug',      label: 'Fix staging bug',     group: 'technical', priority: 0.92, status: 'blocker' },
    { id: 'docs',     label: 'Update API docs',     group: 'technical', priority: 0.48 },
    { id: 'pr',       label: 'Press release',       group: 'go-to-market', priority: 0.62 },
    { id: 'deck',     label: 'Investor update',     group: 'go-to-market', priority: 0.74 },
    { id: 'asha',     label: 'Asha proposal',       group: 'go-to-market', priority: 0.81 },
    { id: 'pricing',  label: 'Pricing review',      group: 'strategy', priority: 0.55 },
    { id: 'hire',     label: 'Hire senior eng',     group: 'team', priority: 0.43 },
    { id: 'onboard',  label: 'Onboarding flow',     group: 'design', priority: 0.51 },
    { id: 'metrics',  label: 'Q3 metrics review',   group: 'strategy', priority: 0.66 },
  ];

  const SEED_EDGES = [
    ['bug', 'deploy'],
    ['qa', 'deploy'],
    ['deploy', 'launch'],
    ['docs', 'launch'],
    ['pr', 'launch'],
    ['deck', 'asha'],
    ['asha', 'pricing'],
    ['pricing', 'metrics'],
    ['onboard', 'launch'],
    ['hire', 'onboard'],
    ['metrics', 'deck'],
  ];

  const GROUP_COLORS = {
    'milestone':    '#1B2A4A',
    'technical':  '#3498db',
    'go-to-market': '#E58B2A',
    'strategy':     '#9b59b6',
    'team':         '#2ed573',
    'design':       '#D8493A',
  };

  /* ── Simulation ────────────────────────────────────────────────── */
  function useForceSim({ nodes, edges, width, height, running }) {
    const stateRef = useRef(null);
    const [, setTick] = useState(0);

    // Initialize on first run
    if (!stateRef.current || stateRef.current.nodeCount !== nodes.length) {
      const cx = width / 2;
      const cy = height / 2;
      const r = Math.min(width, height) * 0.32;
      stateRef.current = {
        nodeCount: nodes.length,
        nodes: nodes.map((n, i) => {
          const a = (i / nodes.length) * Math.PI * 2;
          return {
            ...n,
            x: cx + Math.cos(a) * r,
            y: cy + Math.sin(a) * r,
            z: (Math.random() - 0.5) * 80,
            vx: 0, vy: 0, vz: 0,
            fixed: false,
          };
        }),
      };
    }

    useEffect(() => {
      if (!running) return;
      let raf;
      const step = () => {
        const st = stateRef.current;
        if (!st) { raf = requestAnimationFrame(step); return; }
        const ns = st.nodes;
        const cx = width / 2, cy = height / 2;

        // Charge (repulsion) — all pairs
        for (let i = 0; i < ns.length; i++) {
          const a = ns[i];
          for (let j = i + 1; j < ns.length; j++) {
            const b = ns[j];
            const dx = b.x - a.x, dy = b.y - a.y;
            const d2 = Math.max(dx * dx + dy * dy, 80);
            const d = Math.sqrt(d2);
            const f = 2200 / d2;
            const fx = (dx / d) * f, fy = (dy / d) * f;
            a.vx -= fx; a.vy -= fy;
            b.vx += fx; b.vy += fy;
          }
        }

        // Springs (edges)
        const byId = Object.fromEntries(ns.map((n) => [n.id, n]));
        edges.forEach(([fromId, toId]) => {
          const a = byId[fromId], b = byId[toId];
          if (!a || !b) return;
          const dx = b.x - a.x, dy = b.y - a.y;
          const d = Math.sqrt(dx * dx + dy * dy) || 0.1;
          const target = 140;
          const f = (d - target) * 0.04;
          const fx = (dx / d) * f, fy = (dy / d) * f;
          a.vx += fx; a.vy += fy;
          b.vx -= fx; b.vy -= fy;
          // gentle z pull
          const dz = b.z - a.z;
          a.vz += dz * 0.005;
          b.vz -= dz * 0.005;
        });

        // Center pull
        ns.forEach((n) => {
          n.vx += (cx - n.x) * 0.012;
          n.vy += (cy - n.y) * 0.012;
          n.vz += (0 - n.z) * 0.012;
        });

        // Integrate + damping
        ns.forEach((n) => {
          if (n.fixed) { n.vx = 0; n.vy = 0; n.vz = 0; return; }
          n.vx *= 0.78; n.vy *= 0.78; n.vz *= 0.78;
          n.x += n.vx; n.y += n.vy; n.z += n.vz;
          // gentle bounds
          n.x = Math.max(40, Math.min(width - 40, n.x));
          n.y = Math.max(40, Math.min(height - 40, n.y));
          n.z = Math.max(-120, Math.min(120, n.z));
        });

        setTick((t) => (t + 1) & 0xfff);
        raf = requestAnimationFrame(step);
      };
      raf = requestAnimationFrame(step);
      return () => cancelAnimationFrame(raf);
    }, [running, width, height, edges]);

    return stateRef;
  }

  /* ── Component ─────────────────────────────────────────────────── */
  function ForceDependencyGraph() {
    const W = 1200, H = 540;
    const wrapRef = useRef(null);
    const [hover, setHover] = useState(null);
    const [zoom, setZoom] = useState(1);
    const [pan, setPan] = useState({ x: 0, y: 0 });
    const [running, setRunning] = useState(true);
    const [showLabels, setShowLabels] = useState(true);

    const state = useForceSim({ nodes: SEED_NODES, edges: SEED_EDGES, width: W, height: H, running });

    /* Drag */
    const dragRef = useRef(null);
    const onPointerDown = useCallback((e) => {
      const target = e.target.closest('[data-node-id]');
      if (target) {
        const id = target.getAttribute('data-node-id');
        const n = state.current.nodes.find((x) => x.id === id);
        if (n) {
          n.fixed = true;
          const rect = wrapRef.current.getBoundingClientRect();
          dragRef.current = {
            mode: 'node', node: n,
            offsetX: e.clientX, offsetY: e.clientY,
            startX: n.x, startY: n.y,
            rect, scaleX: rect.width / W, scaleY: rect.height / H,
          };
          e.currentTarget.setPointerCapture(e.pointerId);
        }
      } else {
        // pan
        const rect = wrapRef.current.getBoundingClientRect();
        dragRef.current = {
          mode: 'pan',
          offsetX: e.clientX, offsetY: e.clientY,
          startPan: { ...pan }, rect,
        };
        e.currentTarget.setPointerCapture(e.pointerId);
      }
    }, [pan, state]);

    const onPointerMove = useCallback((e) => {
      const d = dragRef.current;
      if (!d) {
        const target = e.target.closest('[data-node-id]');
        setHover(target ? target.getAttribute('data-node-id') : null);
        return;
      }
      if (d.mode === 'node') {
        const dx = (e.clientX - d.offsetX) / d.scaleX / zoom;
        const dy = (e.clientY - d.offsetY) / d.scaleY / zoom;
        d.node.x = d.startX + dx;
        d.node.y = d.startY + dy;
        d.node.vx = 0; d.node.vy = 0;
      } else if (d.mode === 'pan') {
        const dx = e.clientX - d.offsetX;
        const dy = e.clientY - d.offsetY;
        setPan({ x: d.startPan.x + dx, y: d.startPan.y + dy });
      }
    }, [zoom]);

    const onPointerUp = useCallback((e) => {
      const d = dragRef.current;
      if (d && d.mode === 'node') {
        d.node.fixed = false;
      }
      dragRef.current = null;
    }, []);

    const onWheel = useCallback((e) => {
      e.preventDefault();
      setZoom((z) => Math.max(0.4, Math.min(2.4, z * (1 + e.deltaY * -0.0015))));
    }, []);

    useEffect(() => {
      const el = wrapRef.current;
      if (!el) return;
      el.addEventListener('wheel', onWheel, { passive: false });
      return () => el.removeEventListener('wheel', onWheel);
    }, [onWheel]);

    const reset = () => {
      setZoom(1); setPan({ x: 0, y: 0 });
      state.current = null;
      setRunning(true);
    };

    // pseudo-3D depth helpers
    const depthScale = (z) => 1 + z * 0.002;        // ~ 0.76 .. 1.24
    const depthOpacity = (z) => Math.max(0.35, Math.min(1, 0.7 + z * 0.0035));

    const ns = state.current ? state.current.nodes : [];
    // Sort by z so deeper nodes render first (back-to-front)
    const sortedNodes = [...ns].sort((a, b) => a.z - b.z);

    const hoveredNode = hover ? ns.find((n) => n.id === hover) : null;
    const isConnected = (id) => {
      if (!hover) return false;
      return SEED_EDGES.some(([a, b]) => (a === hover && b === id) || (b === hover && a === id));
    };

    return (
      <div className="fg-wrap">
        <div className="fg-toolbar">
          <div className="fg-toolbar-left">
            <span><span className="v">{ns.length}</span> nodes</span>
            <span><span className="v">{SEED_EDGES.length}</span> dependencies</span>
            <span>zoom <span className="v">{zoom.toFixed(2)}×</span></span>
          </div>
          <div className="fg-toolbar-right">
            <button className={`fg-btn ${running ? 'is-on' : ''}`} onClick={() => setRunning((r) => !r)}>
              {running ? '◼ Pause' : '▶ Simulate'}
            </button>
            <button className={`fg-btn ${showLabels ? 'is-on' : ''}`} onClick={() => setShowLabels((v) => !v)}>
              Labels
            </button>
            <button className="fg-btn" onClick={reset}>Reset</button>
          </div>
        </div>

        <div
          ref={wrapRef}
          className={`fg-canvas ${dragRef.current ? 'is-dragging' : ''}`}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onPointerCancel={onPointerUp}
          onPointerLeave={() => setHover(null)}
        >
          <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
            <defs>
              <marker id="fg-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
                <path d="M0 0 L7 4 L0 8 Z" fill="#C5CCD8" />
              </marker>
              <filter id="fg-glow" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="6" result="blur" />
                <feMerge>
                  <feMergeNode in="blur" />
                  <feMergeNode in="SourceGraphic" />
                </feMerge>
              </filter>
            </defs>

            <g transform={`translate(${pan.x},${pan.y}) scale(${zoom})`} style={{ transformOrigin: `${W/2}px ${H/2}px` }}>
              {/* Edges */}
              {SEED_EDGES.map(([fromId, toId], i) => {
                const a = ns.find((n) => n.id === fromId);
                const b = ns.find((n) => n.id === toId);
                if (!a || !b) return null;
                const highlight = hover && (hover === fromId || hover === toId);
                const dimmed = hover && !highlight;
                return (
                  <line
                    key={i}
                    x1={a.x} y1={a.y} x2={b.x} y2={b.y}
                    stroke={highlight ? '#1B2A4A' : '#C5CCD8'}
                    strokeWidth={highlight ? 1.8 : 1.1}
                    opacity={dimmed ? 0.18 : (highlight ? 0.85 : 0.45)}
                    markerEnd="url(#fg-arrow)"
                  />
                );
              })}

              {/* Nodes */}
              {sortedNodes.map((n) => {
                const s = depthScale(n.z);
                const opacity = depthOpacity(n.z);
                const color = GROUP_COLORS[n.group] || '#1B2A4A';
                const r = (12 + n.priority * 18) * s;
                const isHover = hover === n.id;
                const isLinked = isConnected(n.id);
                const dimmed = hover && !isHover && !isLinked;
                return (
                  <g
                    key={n.id}
                    data-node-id={n.id}
                    transform={`translate(${n.x},${n.y})`}
                    opacity={dimmed ? 0.25 : opacity}
                    style={{ cursor: 'grab', transition: 'opacity 200ms' }}
                  >
                    {n.status === 'blocker' && (
                      <circle r={r + 6} fill="none" stroke="#D8493A" strokeWidth="1.4" strokeDasharray="3 3" opacity="0.6" />
                    )}
                    {isHover && (
                      <circle r={r + 10} fill={color} opacity="0.18" filter="url(#fg-glow)" />
                    )}
                    <circle
                      r={r}
                      fill={color}
                      stroke="#fff"
                      strokeWidth="2"
                    />
                    <circle r={r * 0.5} fill="rgba(255,255,255,0.18)" cx={-r * 0.25} cy={-r * 0.25} />
                    {showLabels && (
                      <g transform={`translate(0, ${r + 16})`}>
                        <rect
                          x={-n.label.length * 3.4}
                          y={-9}
                          width={n.label.length * 6.8}
                          height={18}
                          rx="4"
                          fill="rgba(255,255,255,0.92)"
                          stroke="rgba(27, 42, 74,0.08)"
                        />
                        <text
                          textAnchor="middle"
                          y="4"
                          fontFamily="DM Sans, sans-serif"
                          fontSize="11"
                          fontWeight="500"
                          fill="#0E1320"
                          style={{ pointerEvents: 'none' }}
                        >
                          {n.label}
                        </text>
                      </g>
                    )}
                  </g>
                );
              })}
            </g>
          </svg>

          <div className={`fg-detail ${hoveredNode ? 'is-visible' : ''}`}>
            {hoveredNode && (
              <>
                <div className="d-title">{hoveredNode.label}</div>
                <div className="d-row"><span>group</span><span className="v">{hoveredNode.group}</span></div>
                <div className="d-row"><span>priority</span><span className="v">{hoveredNode.priority.toFixed(2)}</span></div>
                <div className="d-row"><span>depth z</span><span className="v">{hoveredNode.z.toFixed(0)}</span></div>
                <div className="d-row"><span>edges</span><span className="v">{SEED_EDGES.filter(([a,b]) => a === hoveredNode.id || b === hoveredNode.id).length}</span></div>
                {hoveredNode.status === 'blocker' && (
                  <div className="d-row"><span>status</span><span className="v" style={{ color: '#ff8a7e' }}>BLOCKER</span></div>
                )}
              </>
            )}
          </div>
        </div>
      </div>
    );
  }

  window.ForceDependencyGraph = ForceDependencyGraph;
})();
