/* Connections view - ego network re-centred on ANY node (org, person,
   project, event). Pan + zoom (expand/contract). People render as ID cards.
   Exports: PersonalChart.                                              */

function PersonalChart({ focusId, onSelect, onClose }) {
  const D = window.DATA;
  const [hops, setHops] = React.useState(1);
  const [hoverId, setHoverId] = React.useState(null);
  const focus = D.byId[focusId];
  const focusKind = D.kindOf(focusId);

  // BFS layers up to `hops`
  const { layers, edges, seen } = React.useMemo(() => {
    const seen = new Set([focusId]);
    const layers = [[focusId]];
    const edges = [];
    let frontier = [focusId];
    for (let h = 0; h < hops; h++) {
      const next = [];
      frontier.forEach((id) => {
        (D.adj[id] || []).forEach((nb) => {
          edges.push([id, nb]);
          if (!seen.has(nb)) { seen.add(nb); next.push(nb); }
        });
      });
      if (next.length) layers.push(next);
      frontier = next;
    }
    const uniq = new Map();
    edges.forEach(([a, b]) => { if (seen.has(a) && seen.has(b)) uniq.set([a, b].sort().join("|"), [a, b]); });
    return { layers, edges: [...uniq.values()], seen };
  }, [focusId, hops]);

  // radial positions
  const W = 1320, H = 860, cx = W / 2, cy = H / 2;
  const pos = React.useMemo(() => {
    const pos = {};
    pos[focusId] = { x: cx, y: cy };
    const ring = [300, 250];
    layers.slice(1).forEach((layer, li) => {
      const r = (ring[li] || 300) + li * 60;
      layer.forEach((id, j) => {
        const a = (j / layer.length) * Math.PI * 2 - Math.PI / 2 + (li * 0.35);
        pos[id] = { x: cx + Math.cos(a) * r * 1.34, y: cy + Math.sin(a) * r };
      });
    });
    return pos;
  }, [layers]);

  // pan/zoom
  const wrapRef = React.useRef(null);
  const [view, setView] = React.useState({ x: 0, y: 0, k: 1 });
  const drag = React.useRef(null);

  const bounds = React.useMemo(() => {
    const all = Object.values(pos);
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    all.forEach((p) => { minX = Math.min(minX, p.x); minY = Math.min(minY, p.y); maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y); });
    const m = 130;
    return { x: minX - m, y: minY - m, w: (maxX - minX) + m * 2, h: (maxY - minY) + m * 2 };
  }, [pos]);

  const fit = React.useCallback(() => {
    const el = wrapRef.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const padR = 80, padB = 80, padT = 24, padL = 24;
    const availW = Math.max(200, r.width - padL - padR);
    const availH = Math.max(200, r.height - padT - padB);
    const k = Math.min(availW / bounds.w, availH / bounds.h, 1.4);
    setView({ k, x: padL + (availW - bounds.w * k) / 2 - bounds.x * k, y: padT + (availH - bounds.h * k) / 2 - bounds.y * k });
  }, [bounds]);
  React.useEffect(() => { fit(); }, [fit, focusId, hops]);

  const onWheel = (e) => {
    e.preventDefault();
    const el = wrapRef.current.getBoundingClientRect();
    const mx = e.clientX - el.left, my = e.clientY - el.top;
    const factor = Math.exp(-e.deltaY * 0.0014);
    setView((v) => {
      const k = Math.min(2.6, Math.max(0.35, v.k * factor));
      const ratio = k / v.k;
      return { k, x: mx - (mx - v.x) * ratio, y: my - (my - v.y) * ratio };
    });
  };
  const onDown = (e) => { if (e.target.closest(".node")) return; drag.current = { sx: e.clientX, sy: e.clientY, vx: view.x, vy: view.y }; };
  const onMove = (e) => { if (!drag.current) return; setView((v) => ({ ...v, x: drag.current.vx + (e.clientX - drag.current.sx), y: drag.current.vy + (e.clientY - drag.current.sy) })); };
  const onUp = () => (drag.current = null);
  const zoom = (dir) => setView((v) => {
    const k = Math.min(2.6, Math.max(0.35, v.k * (dir > 0 ? 1.2 : 1 / 1.2)));
    const el = wrapRef.current.getBoundingClientRect();
    const mx = el.width / 2, my = el.height / 2, ratio = k / v.k;
    return { k, x: mx - (mx - v.x) * ratio, y: my - (my - v.y) * ratio };
  });

  const active = hoverId;
  const neighbors = React.useMemo(() => {
    if (!active) return null;
    const s = new Set([active]);
    edges.forEach(([a, b]) => { if (a === active) s.add(b); if (b === active) s.add(a); });
    return s;
  }, [active, edges]);

  function nodeEl(id) {
    const isFocus = id === focusId;
    const kind = D.kindOf(id);
    const e = D.byId[id];
    const dim = neighbors && !neighbors.has(id);
    const sel = false;
    const p = pos[id]; if (!p) return null;
    const hov = hoverId === id;
    const common = {
      key: id, className: "node",
      onClick: () => onSelect(id),
      onMouseEnter: () => setHoverId(id), onMouseLeave: () => setHoverId(null),
      style: {
        position: "absolute", left: p.x, top: p.y, cursor: "pointer",
        transform: `translate(-50%,-50%) scale(${isFocus ? 1 : (hov ? 1.05 : 1)})`,
        opacity: dim ? 0.3 : 1, zIndex: isFocus ? 4 : 2,
      },
    };

    // person → ID card
    if (kind === "person") {
      return <div {...common} style={{ ...common.style, zIndex: isFocus ? 4 : 2 }}>
        <IDCard id={id} width={isFocus ? 200 : 178} selected={isFocus} photoSize={isFocus ? 46 : 40} />
      </div>;
    }
    // funder
    if (kind === "funder") {
      return <div {...common}>
        <div style={{ width: 140, padding: "13px 16px", borderRadius: 999, background: "var(--accent)", color: "var(--accent-contrast)", boxShadow: isFocus ? "0 0 0 4px var(--accent-ring), var(--shadow-lg)" : "var(--shadow-lg)", textAlign: "center" }}>
          <div style={{ fontSize: 10.5, opacity: 0.8, fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase" }}>Funder</div>
          <div style={{ fontWeight: 650, fontSize: 13.5, lineHeight: 1.2 }}>{e.name}</div>
        </div>
      </div>;
    }
    // org
    if (kind === "org") {
      const dc = domainColor(e.domain);
      let bg = "var(--surface)", border = "var(--border-strong)";
      if (isFocus) { bg = "var(--accent)"; }
      else if (e.priority === "key") { bg = "var(--accent-soft)"; border = "var(--accent)"; }
      else if (e.priority === "need") { bg = "var(--rose-bg)"; border = "var(--rose)"; }
      return <div {...common}>
        <div style={{
          width: isFocus ? 168 : 144, padding: isFocus ? "12px 15px" : "10px 12px", borderRadius: 12,
          background: bg, color: isFocus ? "var(--accent-contrast)" : "var(--text)",
          border: isFocus ? "none" : `${e.funded ? "1.5px solid" : "1.5px dashed"} ${border}`,
          boxShadow: isFocus ? "0 0 0 4px var(--accent-ring), var(--shadow-lg)" : "var(--shadow)",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 3 }}>
            <span style={{ width: 8, height: 8, borderRadius: 999, background: isFocus ? "#fff" : dc }} />
            <span style={{ fontSize: 10, fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase", opacity: isFocus ? 0.85 : 0.55 }}>Organisation</span>
          </div>
          <div style={{ fontSize: isFocus ? 14.5 : 12.5, fontWeight: isFocus ? 680 : 600, lineHeight: 1.2 }}>{e.name}</div>
        </div>
      </div>;
    }
    // project / event
    const icon = kind === "project" ? "folder" : "calendar";
    return <div {...common}>
      <div style={{
        width: isFocus ? 164 : 138, padding: "9px 11px", borderRadius: 11,
        background: isFocus ? "var(--accent)" : "var(--surface-2)", color: isFocus ? "var(--accent-contrast)" : "var(--text)",
        border: isFocus ? "none" : "1px dashed var(--border-strong)",
        boxShadow: isFocus ? "0 0 0 4px var(--accent-ring), var(--shadow-lg)" : "var(--shadow-sm)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
          <Icon name={icon} size={12} style={{ opacity: 0.7 }} />
          <span style={{ fontSize: 10, fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase", opacity: 0.6 }}>{KIND_META[kind].label}</span>
        </div>
        <div style={{ fontSize: 11.5, fontWeight: 600, lineHeight: 1.2 }}>{e.name.replace(/^\d{4}\s\w+:\s/, "")}</div>
      </div>
    </div>;
  }

  const connCount = (D.adj[focusId] || []).length;

  return (
    <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "14px 24px", borderBottom: "1px solid var(--border)", background: "var(--surface)", zIndex: 3 }}>
        <button className="btn btn-sm" onClick={onClose}><Icon name="chevronLeft" size={15} /> Back to map</button>
        <div style={{ marginLeft: 4 }}>
          <div style={{ fontSize: 16, fontWeight: 650, letterSpacing: "-0.01em" }}>Connections · {focus.name}</div>
          <div style={{ fontSize: 12.5, color: "var(--text-muted)" }}>
            {KIND_META[focusKind] ? KIND_META[focusKind].label : "Record"} · <span className="mono">{connCount}</span> direct {connCount === 1 ? "link" : "links"} - scroll to zoom
          </div>
        </div>
        <div style={{ flex: 1 }} />
        <span style={{ fontSize: 12.5, color: "var(--text-muted)" }}>Depth</span>
        <Segmented value={String(hops)} onChange={(v) => setHops(Number(v))} options={[{ value: "1", label: "1 hop" }, { value: "2", label: "2 hops" }]} />
      </div>

      <div style={{ position: "relative", flex: 1, minHeight: 0 }}>
        {/* zoom controls */}
        <div style={{ position: "absolute", bottom: 16, right: 16, zIndex: 4, display: "flex", flexDirection: "column", gap: 6 }}>
          <button className="btn btn-icon" onClick={() => zoom(1)} title="Zoom in"><Icon name="zoomIn" size={16} /></button>
          <button className="btn btn-icon" onClick={() => zoom(-1)} title="Zoom out"><Icon name="zoomOut" size={16} /></button>
          <button className="btn btn-icon" onClick={fit} title="Recenter"><Icon name="recenter" size={16} /></button>
        </div>

        <div ref={wrapRef} className="grid-texture"
          onWheel={onWheel} onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onMouseLeave={onUp}
          style={{ position: "absolute", inset: 0, overflow: "hidden", cursor: drag.current ? "grabbing" : "grab", background: "var(--bg)" }}>
          <div style={{ position: "absolute", top: 0, left: 0, width: W, height: H, transformOrigin: "0 0", transform: `translate(${view.x}px,${view.y}px) scale(${view.k})` }}>
            <svg width={W} height={H} style={{ position: "absolute", inset: 0, overflow: "visible", pointerEvents: "none" }}>
              {edges.map(([a, b], i) => {
                const pa = pos[a], pb = pos[b]; if (!pa || !pb) return null;
                const oa = D.byId[a], ob = D.byId[b];
                const funded = (D.kindOf(a) === "org" && oa.funded) || (D.kindOf(b) === "org" && ob.funded);
                const touchesFocus = a === focusId || b === focusId;
                const dim = neighbors && !(neighbors.has(a) && neighbors.has(b));
                return <line key={i} x1={pa.x} y1={pa.y} x2={pb.x} y2={pb.y}
                  stroke={touchesFocus ? "var(--accent)" : "var(--border-strong)"}
                  strokeWidth={touchesFocus ? 1.8 : 1.3}
                  strokeDasharray={funded ? "none" : "5 4"}
                  opacity={dim ? 0.12 : (touchesFocus ? 0.55 : 0.4)} />;
              })}
            </svg>
            {layers.flat().map((id) => nodeEl(id))}
          </div>
        </div>
      </div>
    </div>
  );
}

window.PersonalChart = PersonalChart;
