// ─── Resource map ───────────────────────────────────────────────────────────────
// A force-directed "super map" of the user's CONFIRMED resources, in the same
// visual language as the LifeGraph ("Your relationships") on the wizard hub:
// "You" in the centre, coloured circles, name labels, edge lines. Four layers,
// nodes deduped so the tree becomes a web:
//
//   You ── Family ── Org ── Resource
//
// An org appearing in two families links to both — that's where the web forms.
// Layout uses d3-force (loaded as window.d3 from a CDN; the frontend has no
// build step). d3 owns the SVG contents imperatively; React just hosts the node.

// resource_family (DB) → existing CSS family colour token. Cosmetic only;
// anything unmapped falls back to neutral grey.
const _FAM_COLOR = {
  financial:   "var(--fam-financial)",
  investment:  "var(--fam-financial)",
  insurance:   "var(--fam-security)",
  security:    "var(--fam-security)",
  device:      "var(--fam-digital)",
  digital:     "var(--fam-digital)",
  subscription:"var(--fam-subscription)",
  membership:  "var(--fam-membership)",
  asset:       "var(--fam-asset)",
  property:    "var(--fam-asset)",
  document:    "var(--fam-document)",
  government:  "var(--fam-document)",
  legal:       "var(--fam-document)",
  unknown:     "var(--fam-unknown)",
};
const _famColor = (f) => _FAM_COLOR[f] || "var(--fam-unknown)";
const _famLabel = (f) => (f ? f.charAt(0).toUpperCase() + f.slice(1) : "Other");
const _trunc = (s, n) => { s = s || ""; return s.length > n ? s.slice(0, n - 1) + "…" : s; };

// Circle radius: a per-layer minimum, grown to fit the node's label so the text
// sits inside the circle. Capped so a long name can't blow up the layout.
// Greedy word-wrap into lines no longer than maxChars. Long single words are hard-split.
function _wrapLines(text, maxChars) {
  const words = String(text || "").split(/\s+/).filter(Boolean);
  const lines = [];
  let cur = "";
  for (let w of words) {
    while (w.length > maxChars) {                 // a single word longer than the line
      if (cur) { lines.push(cur); cur = ""; }
      lines.push(w.slice(0, maxChars));
      w = w.slice(maxChars);
    }
    if (!cur) cur = w;
    else if ((cur + " " + w).length <= maxChars) cur += " " + w;
    else { lines.push(cur); cur = w; }
  }
  if (cur) lines.push(cur);
  return lines.length ? lines : [""];
}

const _CW = 0.66;   // approx glyph width as a fraction of font size (Geist runs wide)
const _MIN_R = { you: 70, family: 46, org: 42, resource: 38 };
// Wrap each node's label and size its circle to hold the wrapped text block.
// Stores nameLines / font / lineH / blockH / r on the node for the renderer.
function _layout(d) {
  const font = d.kind === "you" ? 16 : 11;
  const maxChars = d.kind === "you" ? 14 : 13;
  d.nameLines = _wrapLines(d.label, maxChars);
  const subFont = 8.5;
  const longest = d.nameLines.reduce((m, l) => Math.max(m, l.length), 0);
  const wName = longest * font * _CW;
  const wSub = d.sublabel ? Math.min(String(d.sublabel).length, 16) * subFont * _CW : 0;
  d.font = font;
  d.lineH = font * 1.2;
  d.blockH = d.nameLines.length * d.lineH + (d.sublabel ? subFont * 1.5 : 0);
  const blockW = Math.max(wName, wSub);
  const fit = Math.sqrt((blockW / 2) ** 2 + (d.blockH / 2) ** 2) + 13;   // circle around the text rect
  d.r = Math.max(_MIN_R[d.kind] || 26, fit);
}
const _radius = (d) => d.r;

// Turn the flat resource list into deduped nodes + links.
function _buildGraph(resources, youLabel) {
  const youId = "you";
  const nodes = [{ id: youId, kind: "you", label: youLabel || "You" }];
  const links = [];
  const famNodes = new Map();   // family   -> node id
  const orgNodes = new Map();   // org key  -> node id
  const orgFamLinks = new Set();// "orgId|famId" dedupe (org can span families)

  for (const it of resources) {
    const fam = it.resource_family || "unknown";
    const org = (it.organisation || "Unknown").trim() || "Unknown";
    const orgKey = org.toLowerCase();

    if (!famNodes.has(fam)) {
      const id = "fam:" + fam;
      famNodes.set(fam, id);
      nodes.push({ id, kind: "family", family: fam, label: _famLabel(fam) });
      links.push({ source: youId, target: id });
    }
    const famId = famNodes.get(fam);

    if (!orgNodes.has(orgKey)) {
      const id = "org:" + orgKey;
      orgNodes.set(orgKey, id);
      nodes.push({ id, kind: "org", family: fam, label: org });
    }
    const orgId = orgNodes.get(orgKey);

    const fl = orgId + "|" + famId;
    if (!orgFamLinks.has(fl)) { orgFamLinks.add(fl); links.push({ source: famId, target: orgId }); }

    nodes.push({
      id: "res:" + it.id,
      kind: "resource",
      family: fam,
      label: it.resource_name || it.resource_type || "Resource",
      sublabel: it.resource_type || "",
      confirmed: it.source !== "AI_INGESTION",   // AI guesses render faded
    });
    links.push({ source: orgId, target: "res:" + it.id });
  }
  return { nodes, links };
}

function ResourceMap({ items, name }) {
  const svgRef = React.useRef(null);
  // Show every resource; AI-ingested ones are drawn faded, confirmed ones solid.
  const resources = (items || []).filter(Boolean);

  React.useEffect(() => {
    const d3 = window.d3;
    if (!svgRef.current || !d3) return;   // render even with 0 resources: shows the lone "you" node

    const W = 1400, H = 1000, cx = W / 2, cy = H / 2;
    const { nodes, links } = _buildGraph(resources, name);
    nodes.forEach(_layout);   // wrap labels + size circles before laying out

    // Pin "You" dead-centre; everything else orbits it in concentric rings.
    const you = nodes.find((n) => n.kind === "you");
    you.fx = cx; you.fy = cy;

    const svg = d3.select(svgRef.current);
    svg.selectAll("*").remove();
    const g = svg.append("g");

    // Concentric tiers keep the layers clearly separated and You centred even
    // when one family dominates: family ring → org ring → resource ring.
    const _ring = { family: 340, org: 700, resource: 1060 };

    // Seed families as evenly-spaced spokes around You so the branches start
    // out symmetrically, but leave them UNPINNED (initial x/y, not fx/fy) so the
    // radial + charge forces spread them out flexibly instead of locking them.
    const fams = nodes.filter((n) => n.kind === "family");
    fams.forEach((f, i) => {
      const ang = (2 * Math.PI * i) / fams.length - Math.PI / 2;
      f.x = cx + _ring.family * Math.cos(ang);
      f.y = cy + _ring.family * Math.sin(ang);
    });

    const sim = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).id((d) => d.id).distance(90).strength(0.1))
      .force("charge", d3.forceManyBody().strength(-900).distanceMax(1200))
      .force("radial", d3.forceRadial((d) => _ring[d.kind] || 0, cx, cy)
        .strength((d) => d.kind === "you" ? 0 : 0.3))
      .force("collide", d3.forceCollide().radius((d) => d.r + 28).strength(1));

    const link = g.append("g").attr("class", "resourcemap__edges")
      .selectAll("line").data(links).join("line")
      .attr("class", "resourcemap__edge");

    const node = g.append("g")
      .selectAll("g").data(nodes).join("g")
      .attr("class", (d) => `resourcemap__node resourcemap__node--${d.kind}`
        + (d.kind === "resource" && !d.confirmed ? " resourcemap__node--ai" : ""))
      .call(drag(sim));

    node.append("title").text((d) => d.sublabel ? `${d.label} · ${d.sublabel}` : d.label);

    node.append("circle")
      .attr("class", "resourcemap__circle")
      .attr("r", _radius)
      // You = dark ink. Org/family hubs = base family colour. Resource leaves get
      // a darker tone of the same family colour so they read distinctly from the
      // structural nodes while still grouping by family. White labels stay legible.
      .attr("fill", (d) => {
        if (d.kind === "you") return "var(--ink, #2a2622)";
        const c = _famColor(d.family);
        return d.kind === "resource" ? `color-mix(in oklch, ${c}, #000 24%)` : c;
      });

    // Wrapped label (white, inside the circle — mirrors LifeGraph) + optional
    // type sub-label, both centred vertically on the node.
    node.each(function (d) {
      const top = -d.blockH / 2;
      const t = d3.select(this).append("text")
        .attr("class", "resourcemap__name").attr("text-anchor", "middle");
      d.nameLines.forEach((ln, i) => {
        t.append("tspan").attr("x", 0).attr("y", top + d.lineH * (i + 0.78)).text(ln);
      });
      if (d.sublabel) {
        d3.select(this).append("text")
          .attr("class", "resourcemap__role").attr("text-anchor", "middle")
          .attr("x", 0).attr("y", top + d.lineH * d.nameLines.length + 7)
          .text(_trunc(d.sublabel, 16));
      }
    });

    sim.on("tick", () => {
      link
        .attr("x1", (d) => d.source.x).attr("y1", (d) => d.source.y)
        .attr("x2", (d) => d.target.x).attr("y2", (d) => d.target.y);
      node.attr("transform", (d) => `translate(${d.x},${d.y})`);
    });

    // Pan + zoom; allow zooming further out since the radial layout is large.
    const zoom = d3.zoom().scaleExtent([0.1, 2.5]).on("zoom", (e) => g.attr("transform", e.transform));
    svg.call(zoom);

    // Once the layout settles, frame the whole graph in the viewport (once).
    let fitted = false;
    function fitToView() {
      // Keep You (pinned at cx,cy) in the viewport centre; scale so the farthest node fits.
      const maxDist = Math.max(...nodes.map((n) => Math.hypot(n.x - cx, n.y - cy) + n.r)) + 30;
      const scale = Math.min((Math.min(W, H) / 2) / maxDist, 1.4);
      const tx = W / 2 - scale * cx, ty = H / 2 - scale * cy;
      svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity.translate(tx, ty).scale(scale));
    }
    sim.on("end", () => { if (!fitted) { fitted = true; fitToView(); } });

    function drag(sim) {
      function started(event, d) { if (!event.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; }
      function dragged(event, d) { d.fx = event.x; d.fy = event.y; }
      function ended(event, d) {
        if (!event.active) sim.alphaTarget(0);
        if (d.kind !== "you") { d.fx = null; d.fy = null; }   // keep You pinned
      }
      return d3.drag().on("start", started).on("drag", dragged).on("end", ended);
    }

    return () => sim.stop();
  }, [items]);

  return (
    <section className="resourcemap-tab">
      <div className="resourcemap__bar">
        <h1 className="resourcemap__title">Your life map</h1>
        <p className="resourcemap__sub">Everything you use, and who provides it. Solid = confirmed. Faded = AI-found. Drag and zoom to explore.</p>
        {resources.length === 0 && (
          <p className="resourcemap__empty">Empty for now. Add resources or run AI to begin.</p>
        )}
      </div>
      <svg ref={svgRef} className="resourcemap__svg" viewBox="0 0 900 600" role="img" aria-label="Resource map" />
    </section>
  );
}

if (typeof window !== "undefined") window.ResourceMap = ResourceMap;
