// RevenueSankey — interactive, animated lead-to-cash flow chart for the Journeys page.
(function () {

const { useState, useEffect, useRef } = React;

function RevenueSankey() {
  const { font, fg, muted, border, cardBg } = window.MAX_DATA;
  const [hoveredNode, setHoveredNode] = useState(null); // {ci, ni}
  const [hoveredFlow, setHoveredFlow] = useState(null);
  const [drawProgress, setDrawProgress] = useState(0); // 0..1 for entry animation
  const rafRef = useRef(null);

  // Entry animation: cubic ease-out over ~900ms
  useEffect(() => {
    const start = performance.now();
    const dur = 900;
    const tick = (t) => {
      const elapsed = t - start;
      const p = Math.min(elapsed / dur, 1);
      const eased = 1 - Math.pow(1 - p, 3);
      setDrawProgress(eased);
      if (p < 1) rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, []);

  // Layout constants (viewBox is 1140 x 360)
  const nw = 168, ng = 4, colGap = 70, topY = 50;
  const availH = 280; // vertical room for stacked nodes within a column

  // Raw values drive both block heights and flow widths.
  // Each column's total height = availH; node heights = (val / colTotal) * (availH - gaps),
  // so columns with more "loss" have shorter overall stacks visually.
  // We achieve "Demand Gen tallest, Payment shortest" by scaling each column's stack
  // to its total / max-total.
  const rawCols = [
    { label: "Demand Gen", subLabel: "Lead sources", color: "#2563eb", nodes: [
      { l: "Ad Campaigns", val: 112, sub: "Google, Meta, LSAs" },
      { l: "MP Campaigns", val: 86, sub: "Email + SMS to customers" },
      { l: "Outbound AI", val: 52, sub: "AI-driven outreach" },
      { l: "Social & Other", val: 34.6, sub: "Organic + referrals" },
    ]},
    { label: "Job Booking", subLabel: "How leads convert", color: "#7c3aed", nodes: [
      { l: "AI Agent Booked", val: 148, sub: "Calls + SMS · 52% of booked" },
      { l: "Human CSR Booked", val: 98, sub: "Live agent handled" },
      { l: "Web Scheduler", val: 26, sub: "Customer self-service" },
      { l: "Didn't Book", val: 12.6, sub: "Lost or abandoned", neg: true },
    ]},
    { label: "Dispatch", subLabel: "Reaching the job", color: "#0891b2", nodes: [
      { l: "Jobs Assigned", val: 236, sub: "Tech dispatched on time" },
      { l: "Jobs Rescheduled", val: 18.4, sub: "Pushed to later slot" },
      { l: "Jobs Cancelled", val: 6.4, sub: "Pulled before completion", neg: true },
    ]},
    { label: "Quoting", subLabel: "Turning visits to sales", color: "#059669", nodes: [
      { l: "AI Estimate", val: 142, sub: "Generated in the field" },
      { l: "Manual Estimate", val: 72, sub: "Tech-built proposal" },
      { l: "Financing Sold", val: 62, sub: "Approved + funded" },
      { l: "No Estimate / Lost", val: 24, sub: "Visit didn't quote", neg: true },
    ]},
    { label: "Payment", subLabel: "Cash collected", color: "#d97706", nodes: [
      { l: "Collected", val: 226.1, sub: "79% of pipeline", hl: true },
      { l: "Outstanding AR", val: 34.2, sub: "Past due — needs attention", warn: true },
      { l: "Uncollected", val: 15.7, sub: "Written off", neg: true },
    ]},
  ];

  // Compute column totals + per-node heights.
  const colTotals = rawCols.map(c => c.nodes.reduce((s, n) => s + n.val, 0));
  const maxTotal = Math.max(...colTotals);

  const cols = rawCols.map((c, ci) => {
    const total = colTotals[ci];
    // Each column's stack scales to total/maxTotal of availH (so Demand Gen = full, Payment = ~76%).
    const stackH = availH * (total / maxTotal);
    const gapsTotal = ng * (c.nodes.length - 1);
    const usableH = stackH - gapsTotal;
    const nodes = c.nodes.map(n => {
      const h = Math.max(20, (n.val / total) * usableH);
      const v = n.neg ? `-$${n.val < 100 ? n.val.toFixed(1) : Math.round(n.val)}K`
                     : `$${n.val < 100 ? n.val.toFixed(1) : Math.round(n.val)}K`;
      return { ...n, h, v };
    });
    return { ...c, x: ci * (nw + colGap), nodes, stackH, total };
  });

  const getY = (ci, ni) => {
    // Center each column's stack vertically within the available area
    const c = cols[ci];
    const colTop = topY + (availH - c.stackH) / 2;
    let y = colTop;
    for (let i = 0; i < ni; i++) y += c.nodes[i].h + ng;
    return y + c.nodes[ni].h / 2;
  };
  const getNodeTop = (ci, ni) => {
    const c = cols[ci];
    const colTop = topY + (availH - c.stackH) / 2;
    let y = colTop;
    for (let i = 0; i < ni; i++) y += c.nodes[i].h + ng;
    return y;
  };

  // Flows defined by source/target value (in $K). Stroke width is derived from value.
  // Sums per source ≈ source node value; sums per target ≈ target node value.
  const rawFlows = [
    // DemandGen → Booking (total ~284.6 → 284.6 redistributed: 148 + 98 + 26 + 12.6 = 284.6)
    { from: [0,0], to: [1,0], val: 70 },   // Ads → AI booked
    { from: [0,0], to: [1,1], val: 32 },   // Ads → Human
    { from: [0,0], to: [1,3], val: 10 },   // Ads → Didn't book
    { from: [0,1], to: [1,0], val: 48 },   // MP → AI
    { from: [0,1], to: [1,1], val: 38 },   // MP → Human
    { from: [0,2], to: [1,0], val: 30 },   // Outbound AI → AI
    { from: [0,2], to: [1,1], val: 22 },   // Outbound AI → Human
    { from: [0,3], to: [1,2], val: 26 },   // Social → Web Scheduler
    { from: [0,3], to: [1,3], val: 8.6 },  // Social → Didn't book
    // Booking → Dispatch (sum source 148+98+26 = 272 minus 12.6 lost; 236+18.4+6.4 = 260.8)
    { from: [1,0], to: [2,0], val: 138 },
    { from: [1,0], to: [2,1], val: 8 },
    { from: [1,0], to: [2,2], val: 2 },
    { from: [1,1], to: [2,0], val: 88 },
    { from: [1,1], to: [2,1], val: 8 },
    { from: [1,1], to: [2,2], val: 2 },
    { from: [1,2], to: [2,0], val: 22 },
    { from: [1,2], to: [2,1], val: 2.4 },
    { from: [1,2], to: [2,2], val: 1.6 },
    // Dispatch → Quoting (236 + 18.4 → 142+72+62+24 = 300; rescheduled mostly continues)
    { from: [2,0], to: [3,0], val: 130 },
    { from: [2,0], to: [3,1], val: 64 },
    { from: [2,0], to: [3,2], val: 22 },
    { from: [2,0], to: [3,3], val: 20 },
    { from: [2,1], to: [3,1], val: 8 },
    { from: [2,1], to: [3,2], val: 8 },
    { from: [2,1], to: [3,3], val: 2.4 },
    // Quoting → Payment (142+72+62 = 276 paths to 226.1+34.2+15.7 = 276)
    { from: [3,0], to: [4,0], val: 118 },
    { from: [3,0], to: [4,1], val: 18 },
    { from: [3,0], to: [4,2], val: 6 },
    { from: [3,1], to: [4,0], val: 56 },
    { from: [3,1], to: [4,1], val: 12 },
    { from: [3,1], to: [4,2], val: 4 },
    { from: [3,2], to: [4,0], val: 52 },
    { from: [3,2], to: [4,1], val: 4.2 },
    { from: [3,2], to: [4,2], val: 5.7 },
  ];
  // Mark high-volume flows
  const flows = rawFlows.map(f => ({ ...f, hl: f.val >= 100 }));

  // Hover precedence: explicit hovered flow > flow connected to hovered node > none
  const isFlowDimmed = (flow) => {
    if (!hoveredNode && hoveredFlow == null) return false;
    if (hoveredFlow != null) return hoveredFlow !== flow;
    const [fci, fni] = flow.from;
    const [tci, tni] = flow.to;
    return !(
      (fci === hoveredNode.ci && fni === hoveredNode.ni) ||
      (tci === hoveredNode.ci && tni === hoveredNode.ni)
    );
  };

  const isNodeDimmed = (ci, ni) => {
    if (!hoveredNode && hoveredFlow == null) return false;
    if (hoveredNode) return !(ci === hoveredNode.ci && ni === hoveredNode.ni);
    if (hoveredFlow != null) {
      const f = flows[hoveredFlow];
      return !((f.from[0] === ci && f.from[1] === ni) || (f.to[0] === ci && f.to[1] === ni));
    }
    return false;
  };

  // Tooltip data
  const hoveredNodeData = hoveredNode ? cols[hoveredNode.ci].nodes[hoveredNode.ni] : null;
  const hoveredNodeColor = hoveredNode ? cols[hoveredNode.ci].color : null;

  return (
    <div style={{ background: "linear-gradient(135deg, #0f1f3d 0%, #141414 100%)", borderRadius: 12, marginBottom: 16, position: "relative", overflow: "hidden" }}>
      <div style={{ position: "absolute", top: -60, right: -60, width: 240, height: 240, borderRadius: "50%", background: "rgba(34,197,94,0.05)", filter: "blur(30px)" }} />
      <div style={{ position: "relative" }}>
        <div style={{ padding: "18px 24px", borderBottom: "1px solid rgba(255,255,255,0.06)", display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: 16 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
            <div style={{ background: "rgba(34,197,94,0.15)", borderRadius: 10, padding: 10 }}>
              <window.Icon name="DollarSign" size={20} color="#4ade80" />
            </div>
            <div>
              <div style={{ fontSize: 10, color: "rgba(255,255,255,0.4)", fontFamily: font, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.8, marginBottom: 2 }}>Lead → Cash · Last 30 days</div>
              <h2 style={{ fontSize: 22, fontWeight: 800, color: "white", fontFamily: font, margin: 0, letterSpacing: -0.4 }}>Revenue Pipeline</h2>
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "baseline", gap: 16 }}>
            <div style={{ textAlign: "right" }}>
              <div style={{ fontSize: 10, color: "rgba(255,255,255,0.4)", fontFamily: font, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>Pipeline conversion</div>
              <div style={{ fontSize: 14, fontWeight: 800, color: "#4ade80", fontFamily: font, marginTop: 2 }}>79.4%</div>
            </div>
            <div style={{ width: 1, alignSelf: "stretch", background: "rgba(255,255,255,0.08)" }} />
            <div style={{ textAlign: "right" }}>
              <div style={{ fontSize: 10, color: "rgba(255,255,255,0.4)", fontFamily: font, fontWeight: 600, textTransform: "uppercase", letterSpacing: 0.5 }}>Collected</div>
              <div style={{ fontSize: 28, fontWeight: 800, color: "white", fontFamily: font, letterSpacing: -0.5, lineHeight: 1, marginTop: 2 }}>
                $226.1K <span style={{ fontSize: 12, fontWeight: 700, color: "#4ade80", marginLeft: 4 }}>↑ 14%</span>
              </div>
            </div>
          </div>
        </div>

        <div style={{ padding: "8px 16px 6px", position: "relative" }}>
          {/* Tooltip overlay */}
          {hoveredNodeData && (
            <div style={{ position: "absolute", top: 14, left: "50%", transform: "translateX(-50%)", background: "rgba(0,0,0,0.85)", border: `1px solid ${hoveredNodeColor}66`, borderRadius: 8, padding: "8px 14px", display: "flex", alignItems: "center", gap: 10, zIndex: 5, boxShadow: "0 4px 16px rgba(0,0,0,0.4)", pointerEvents: "none" }}>
              <span style={{ width: 8, height: 8, borderRadius: 99, background: hoveredNodeColor }} />
              <span style={{ fontSize: 12, fontWeight: 700, color: "white", fontFamily: font }}>{hoveredNodeData.l}</span>
              <span style={{ fontSize: 12, fontWeight: 800, color: hoveredNodeData.neg ? "#f87171" : hoveredNodeData.hl ? "#4ade80" : "white", fontFamily: font }}>{hoveredNodeData.v}</span>
              <span style={{ fontSize: 11, color: "rgba(255,255,255,0.6)", fontFamily: font }}>· {hoveredNodeData.sub}</span>
            </div>
          )}

          <svg viewBox="0 0 1140 360" style={{ width: "100%", height: "auto", display: "block" }}>
            <defs>
              {cols.map((c, ci) => (
                <linearGradient key={`g${ci}`} id={`flow-grad-${ci}`} x1="0%" y1="0%" x2="100%" y2="0%">
                  <stop offset="0%" stopColor={c.color} stopOpacity="0.55" />
                  <stop offset="100%" stopColor={cols[ci + 1] ? cols[ci + 1].color : c.color} stopOpacity="0.35" />
                </linearGradient>
              ))}
            </defs>

            {/* Column headers */}
            {cols.map((c, ci) => (
              <g key={`h${ci}`}>
                <text x={c.x + nw / 2} y={20} textAnchor="middle" style={{ fontSize: 11, fill: c.color, fontFamily: font, fontWeight: 800, letterSpacing: 1.2 }}>
                  {c.label.toUpperCase()}
                </text>
                <text x={c.x + nw / 2} y={34} textAnchor="middle" style={{ fontSize: 9, fill: "rgba(255,255,255,0.4)", fontFamily: font, fontWeight: 600 }}>
                  {c.subLabel}
                </text>
              </g>
            ))}

            {/* Flow paths (drawn first, behind nodes). Strokes hit each node at offsets
                proportional to flow value, so a node's incoming/outgoing flows visually
                align with the slice of the rect they correspond to. */}
            {(() => {
              // Pre-compute, per node, the cumulative offset top of each flow into/out of it.
              const nodeIns = {};   // key `${ci}-${ni}` → [{flowIdx, offset, w}]
              const nodeOuts = {};
              cols.forEach((c, ci) => c.nodes.forEach((n, ni) => {
                nodeIns[`${ci}-${ni}`] = [];
                nodeOuts[`${ci}-${ni}`] = [];
              }));
              flows.forEach((f, fi) => {
                const sk = `${f.from[0]}-${f.from[1]}`;
                const tk = `${f.to[0]}-${f.to[1]}`;
                nodeOuts[sk].push(fi);
                nodeIns[tk].push(fi);
              });
              // Build a map from flowIdx → {y1, y2, w}
              const flowGeom = flows.map(() => ({}));
              cols.forEach((c, ci) => {
                c.nodes.forEach((n, ni) => {
                const top = getNodeTop(ci, ni);
                const totalOut = nodeOuts[`${ci}-${ni}`].reduce((s, fi) => s + flows[fi].val, 0);
                const totalIn = nodeIns[`${ci}-${ni}`].reduce((s, fi) => s + flows[fi].val, 0);
                let outOff = 0;
                nodeOuts[`${ci}-${ni}`].forEach(fi => {
                  const ratio = totalOut > 0 ? flows[fi].val / totalOut : 0;
                  const w = ratio * n.h;
                  flowGeom[fi].y1 = top + outOff + w / 2;
                  flowGeom[fi].w = w;
                  outOff += w;
                });
                let inOff = 0;
                nodeIns[`${ci}-${ni}`].forEach(fi => {
                  const ratio = totalIn > 0 ? flows[fi].val / totalIn : 0;
                  const w = ratio * n.h;
                  flowGeom[fi].y2 = top + inOff + w / 2;
                  // Use min of in-side and out-side widths so the path doesn't bulge
                  flowGeom[fi].w = Math.min(flowGeom[fi].w || w, w);
                  inOff += w;
                });
                });
              });

              return flows.map((flow, fi) => {
                const [c1, n1] = flow.from;
                const [c2, n2] = flow.to;
                const x1 = cols[c1].x + nw;
                const x2 = cols[c2].x;
                const { y1, y2, w } = flowGeom[fi];
                const cx = (x1 + x2) / 2;
                const path = `M${x1},${y1} C${cx},${y1} ${cx},${y2} ${x2},${y2}`;
                const isNeg = cols[c1].nodes[n1].neg || cols[c2].nodes[n2].neg;
                const dimmed = isFlowDimmed(flow);
                const isHl = hoveredFlow === fi || (hoveredNode && (
                  (flow.from[0] === hoveredNode.ci && flow.from[1] === hoveredNode.ni) ||
                  (flow.to[0] === hoveredNode.ci && flow.to[1] === hoveredNode.ni)
                ));
                const opacity = dimmed ? 0.04 : isHl ? 0.55 : isNeg ? 0.18 : 0.22;
                const pathLen = 600;
                const dashOff = pathLen * (1 - drawProgress);
                return (
                  <path key={`f${fi}`}
                    d={path}
                    fill="none"
                    stroke={isNeg ? "#dc2626" : (isHl ? cols[c1].color : `url(#flow-grad-${c1})`)}
                    strokeWidth={Math.max(1.5, w - 1)}
                    opacity={opacity}
                    strokeDasharray={pathLen}
                    strokeDashoffset={dashOff}
                    style={{ transition: "opacity 0.2s, stroke 0.2s", cursor: "pointer" }}
                    onMouseEnter={() => setHoveredFlow(fi)}
                    onMouseLeave={() => setHoveredFlow(null)}
                  />
                );
              });
            })()}

            {/* Nodes (drawn on top) */}
            {cols.map((c, ci) => {
              return c.nodes.map((n, ni) => {
                const fill = n.neg ? "#dc2626" : n.hl ? "#4ade80" : n.warn ? "#fbbf24" : c.color;
                const dimmed = isNodeDimmed(ci, ni);
                const isHovered = hoveredNode && hoveredNode.ci === ci && hoveredNode.ni === ni;
                const baseOp = n.hl ? 0.92 : n.neg ? 0.55 : n.warn ? 0.75 : 0.82;
                const op = dimmed ? 0.2 : isHovered ? 1 : baseOp;
                const yPos = getNodeTop(ci, ni);
                // Animation: nodes scale in
                const scale = drawProgress;
                return (
                  <g key={`n${ci}-${ni}`}
                    style={{ cursor: "pointer", opacity: scale }}
                    onMouseEnter={() => setHoveredNode({ ci, ni })}
                    onMouseLeave={() => setHoveredNode(null)}>
                    {/* Glow halo on hover/highlight */}
                    {(isHovered || n.hl) && (
                      <rect x={c.x - 2} y={yPos - 2} width={nw + 4} height={n.h + 4} rx="6"
                        fill={fill} opacity={isHovered ? 0.18 : 0.08}
                        style={{ filter: "blur(6px)" }} />
                    )}
                    <rect x={c.x} y={yPos} width={nw} height={n.h} rx="5"
                      fill={fill} opacity={op}
                      stroke={n.hl ? "#4ade80" : isHovered ? "white" : "none"}
                      strokeWidth={n.hl ? 1.5 : isHovered ? 1 : 0}
                      style={{ transition: "opacity 0.15s, stroke 0.15s" }} />
                    <text x={c.x + 10} y={yPos + (n.h <= 24 ? n.h / 2 + 4 : 18)}
                      style={{ fontSize: 11, fill: "white", fontFamily: font, fontWeight: 700, pointerEvents: "none" }}>
                      {n.l}
                    </text>
                    {n.h > 30 && (
                      <text x={c.x + 10} y={yPos + 32}
                        style={{ fontSize: 9, fill: "rgba(255,255,255,0.6)", fontFamily: font, pointerEvents: "none" }}>
                        {n.sub}
                      </text>
                    )}
                    <text x={c.x + nw - 10} y={yPos + (n.h <= 24 ? n.h / 2 + 4 : 18)} textAnchor="end"
                      style={{ fontSize: n.hl ? 14 : 12, fill: n.hl ? "#86efac" : n.neg ? "#fca5a5" : "white", fontFamily: font, fontWeight: 800, pointerEvents: "none" }}>
                      {n.v}
                    </text>
                  </g>
                );
              });
            })}
          </svg>
        </div>

        {/* Conversion strip */}
        <div style={{ padding: "12px 24px 16px", borderTop: "1px solid rgba(255,255,255,0.06)" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8 }}>
            <span style={{ fontSize: 10, color: "rgba(255,255,255,0.5)", fontFamily: font, fontWeight: 700, textTransform: "uppercase", letterSpacing: 0.6 }}>Pipeline conversion</span>
            <div style={{ flex: 1, height: 6, borderRadius: 99, background: "rgba(255,255,255,0.08)", overflow: "hidden" }}>
              <div style={{ width: `${79.4 * drawProgress}%`, height: "100%", background: "linear-gradient(90deg, #2563eb, #4ade80)", borderRadius: 99, transition: "width 0.05s linear" }} />
            </div>
            <span style={{ fontSize: 12, fontWeight: 800, color: "#4ade80", fontFamily: font }}>79.4%</span>
          </div>
          <p style={{ fontSize: 11, color: "rgba(255,255,255,0.5)", fontFamily: font, margin: 0, lineHeight: 1.5 }}>
            Of every dollar of demand generated, <strong style={{ color: "white" }}>$0.79</strong> made it to the bank. Hover any node or flow to see its share of the pipeline.
          </p>
        </div>
      </div>
    </div>
  );
}

window.RevenueSankey = RevenueSankey;
})();
