// Relationship graph — force-directed, API-wired.

// React hooks (Babel-standalone — make hooks visible per-script)
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

const graphStyles = {
  root: { display: "flex", height: "100%", minHeight: 0 },
  rail: {
    width: 180, flex: "0 0 180px",
    borderRight: "1px solid var(--vk-border-subtle)",
    background: "var(--vk-surface-1)", padding: 14,
    display: "flex", flexDirection: "column", gap: 14, overflowY: "auto",
  },
  canvas: { flex: 1, position: "relative", overflow: "hidden", background: "var(--vk-bg-base)" },
  legend: {
    position: "absolute", left: 12, bottom: 12,
    background: "var(--vk-surface-1)", boxShadow: "var(--vk-ring-ambient)",
    borderRadius: 5, padding: 10, fontSize: 11,
    display: "flex", flexDirection: "column", gap: 4, color: "var(--vk-text-secondary)",
  },
};

const TYPE_COLORS = {
  person: "#E91E63", company: "#5DCAA5", investor: "#FF6B35", fund: "#F0993D",
  government: "#A8A0B3", nonprofit: "#7BA8E0", university: "#C394E0",
  accelerator: "#9DD9C5", incubator: "#9DD9C5", corporation: "#E0C394",
  event: "#6B6478", high_risk_actor: "#E24B6A", other: "#6B6478",
};

const REL_TYPES = [
  "FOUNDED","INVESTED_IN","FUNDED","LEADS","EMPLOYED_BY","ADVISES","BOARD_MEMBER_OF",
  "PARTNERED_WITH","SUPPORTED_BY","REGULATED_BY","COMPETES_WITH","BLOCKS","CONNECTED_TO","MENTIONED_WITH",
];

function useForceLayout(nodes, edges, w, h, iters = 220) {
  return useMemo(() => {
    const pos = {};
    nodes.forEach((n, i) => {
      const angle = (i / nodes.length) * 2 * Math.PI;
      const r = Math.min(w, h) * 0.32 + (Math.random() - 0.5) * 60;
      pos[n.id] = { x: w / 2 + r * Math.cos(angle), y: h / 2 + r * Math.sin(angle), vx: 0, vy: 0 };
    });
    const k = 38, kSq = k * k, center = { x: w / 2, y: h / 2 };
    for (let iter = 0; iter < iters; iter++) {
      for (let i = 0; i < nodes.length; i++) {
        const a = pos[nodes[i].id];
        for (let j = i + 1; j < nodes.length; j++) {
          const b = pos[nodes[j].id];
          let dx = a.x - b.x, dy = a.y - b.y, d2 = dx * dx + dy * dy + 0.01;
          const f = kSq / d2, d = Math.sqrt(d2);
          const dxf = (dx / d) * f, dyf = (dy / d) * f;
          a.vx += dxf; a.vy += dyf; b.vx -= dxf; b.vy -= dyf;
        }
      }
      edges.forEach((e) => {
        const a = pos[e.source], b = pos[e.target];
        if (!a || !b) return;
        const dx = a.x - b.x, dy = a.y - b.y;
        const d = Math.sqrt(dx * dx + dy * dy) + 0.01;
        const f = (d * d) / k;
        const dxf = (dx / d) * f * 0.012, dyf = (dy / d) * f * 0.012;
        a.vx -= dxf; a.vy -= dyf; b.vx += dxf; b.vy += dyf;
      });
      nodes.forEach((n) => {
        const p = pos[n.id];
        p.vx += (center.x - p.x) * 0.002;
        p.vy += (center.y - p.y) * 0.002;
      });
      const damp = 0.78;
      nodes.forEach((n) => {
        const p = pos[n.id];
        p.x += Math.max(-12, Math.min(12, p.vx)) * damp;
        p.y += Math.max(-12, Math.min(12, p.vy)) * damp;
        p.vx *= damp; p.vy *= damp;
        p.x = Math.max(24, Math.min(w - 24, p.x));
        p.y = Math.max(24, Math.min(h - 24, p.y));
      });
    }
    return pos;
  }, [nodes, edges, w, h, iters]);
}

function RelationshipGraph({ openEntity }) {
  const app = useAppState();
  const entsQ = useApi("/entities", { city: app.city, run_id: app.runId, limit: 500 }, [app.city, app.runId]);
  const relsQ = useApi("/relationships", { city: app.city, run_id: app.runId }, [app.city, app.runId]);
  const ents = Array.isArray(entsQ.data) ? entsQ.data : [];
  const rels = Array.isArray(relsQ.data) ? relsQ.data : [];

  const [showEdgeTypes, setShowEdgeTypes] = useState(new Set(REL_TYPES));
  const [minConf, setMinConf] = useState(0);
  const [sensitiveOnly, setSensitiveOnly] = useState(false);
  const [highInfluenceOnly, setHighInfluenceOnly] = useState(false);
  const [hoverId, setHoverId] = useState(null);
  const wrapRef = useRef(null);
  const [size, setSize] = useState({ w: 900, h: 600 });

  useEffect(() => {
    if (!wrapRef.current) return;
    const ro = new ResizeObserver(([entry]) => {
      const r = entry.contentRect;
      setSize({ w: r.width, h: r.height });
    });
    ro.observe(wrapRef.current);
    return () => ro.disconnect();
  }, []);

  const confRank = { high: 2, medium: 1, med: 1, low: 0 };
  const filtered = useMemo(() => {
    let es = rels.filter((r) => showEdgeTypes.has(r.relationship_type));
    es = es.filter((r) => (confRank[r.confidence] ?? 0) >= minConf);
    if (sensitiveOnly) es = es.filter((r) => r.sensitive_claim);
    const nodeIds = new Set();
    es.forEach((e) => { nodeIds.add(e.source_entity_id); nodeIds.add(e.target_entity_id); });
    let ns = ents.filter((e) => nodeIds.has(e.entity_id));
    if (highInfluenceOnly) {
      ns = ns.filter((e) => e.influence_score >= 70);
      const keep = new Set(ns.map((e) => e.entity_id));
      es = es.filter((r) => keep.has(r.source_entity_id) && keep.has(r.target_entity_id));
    }
    return {
      nodes: ns.map((e) => ({ id: e.entity_id, e })),
      edges: es.map((r) => ({ source: r.source_entity_id, target: r.target_entity_id, r })),
    };
  }, [rels, ents, showEdgeTypes, minConf, sensitiveOnly, highInfluenceOnly]);

  const pos = useForceLayout(filtered.nodes, filtered.edges, size.w, size.h, 220);

  function toggleEdgeType(t) {
    setShowEdgeTypes((prev) => {
      const next = new Set(prev);
      next.has(t) ? next.delete(t) : next.add(t);
      return next;
    });
  }

  const loading = entsQ.loading || relsQ.loading;

  return (
    <div style={graphStyles.root}>
      <aside style={graphStyles.rail}>
        <MicroCap>Edge types</MicroCap>
        <div style={{ display: "flex", flexDirection: "column", gap: 4, maxHeight: 240, overflowY: "auto" }}>
          {REL_TYPES.map((t) => (
            <Checkbox key={t} checked={showEdgeTypes.has(t)} onChange={() => toggleEdgeType(t)} label={t.replaceAll("_", " ").toLowerCase()} />
          ))}
        </div>
        <FilterGroup label="Confidence ≥" open>
          <input type="range" min="0" max="2" step="1" value={minConf} onChange={(e) => setMinConf(+e.target.value)} style={{ accentColor: "#E91E63" }} />
          <div style={{ fontSize: 11, color: "var(--vk-text-tertiary)" }}>{["low", "med", "high"][minConf]}+</div>
        </FilterGroup>
        <FilterGroup label="Toggles" open>
          <Checkbox checked={sensitiveOnly} onChange={setSensitiveOnly} label="Sensitive only" />
          <Checkbox checked={highInfluenceOnly} onChange={setHighInfluenceOnly} label="High influence only" />
        </FilterGroup>
        <div style={{ fontSize: 11, color: "var(--vk-text-tertiary)" }}>
          {loading ? "loading…" : `${filtered.nodes.length} nodes · ${filtered.edges.length} edges`}
        </div>
      </aside>

      <div ref={wrapRef} style={graphStyles.canvas}>
        {loading && (
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--vk-text-tertiary)" }}>
            loading graph…
          </div>
        )}
        {!loading && filtered.nodes.length === 0 && (
          <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }}>
            <EmptyState icon="Share2" title="No relationships for this run yet." hint="The Relationship Agent hasn't produced edges for this dataset." />
          </div>
        )}
        <svg width="100%" height="100%" style={{ display: "block" }}>
          {filtered.edges.map((e, i) => {
            const a = pos[e.source], b = pos[e.target];
            if (!a || !b) return null;
            const sensitive = e.r.sensitive_claim;
            const w = e.r.confidence === "high" ? 1.5 : e.r.confidence === "low" ? 0.5 : 1.0;
            const color = sensitive ? "rgba(226,75,106,0.55)"
              : e.r.relationship_type === "COMPETES_WITH" ? "rgba(240,153,61,0.45)"
              : (e.r.relationship_type === "INVESTED_IN" || e.r.relationship_type === "FUNDED") ? "rgba(93,202,165,0.45)"
              : (e.r.relationship_type === "BLOCKS" || e.r.relationship_type === "REGULATED_BY") ? "rgba(226,75,106,0.45)"
              : "rgba(168,160,179,0.25)";
            return <line key={i} x1={a.x} y1={a.y} x2={b.x} y2={b.y} stroke={color} strokeWidth={w} />;
          })}
          {filtered.nodes.map((n) => {
            const p = pos[n.id]; if (!p) return null;
            const c = TYPE_COLORS[n.e.entity_type] || "#A8A0B3";
            const r = 4 + Math.round((n.e.influence_score || 50) / 12);
            const isHover = hoverId === n.id;
            return (
              <g key={n.id} transform={`translate(${p.x},${p.y})`} style={{ cursor: "pointer" }}
                onMouseEnter={() => setHoverId(n.id)} onMouseLeave={() => setHoverId(null)}
                onClick={() => openEntity(n.e)}>
                <circle r={r} fill={c} opacity={isHover ? 1 : 0.85} stroke={isHover ? "#F5F2EA" : "transparent"} strokeWidth={1} />
                {isHover && (
                  <g>
                    <rect x={r + 6} y={-12} rx={3} ry={3} width={Math.max(80, n.e.canonical_name.length * 6.5)} height={24} fill="#16111F" stroke="rgba(255,255,255,0.12)" />
                    <text x={r + 12} y={4} fill="#F5F2EA" fontSize="11" fontFamily="Inter">{n.e.canonical_name}</text>
                  </g>
                )}
              </g>
            );
          })}
        </svg>

        <div style={graphStyles.legend}>
          <MicroCap>Node color · entity type</MicroCap>
          {["person","company","investor","accelerator","university","government","corporation","nonprofit"].map((t) => (
            <div key={t} style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: TYPE_COLORS[t], display: "inline-block" }}></span>
              <span>{t}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { RelationshipGraph });
