// Dashboard — chart primitives (confidence gauge, allocation donut,
// investment projections line chart). All hand-drawn SVG, matching the
// existing prototype pages, which carry no chart library.

/* ---------- shared helpers ---------- */

// Measures a container so charts can draw at 1:1 device pixels. The prototype
// canvas is a fixed 1440px, but the sidebar collapses/expands, so the content
// column width genuinely changes at runtime.
function useMeasure() {
  const ref = React.useRef(null);
  const [w, setW] = React.useState(0);
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const ro = new ResizeObserver((entries) => {
      const cr = entries[0] && entries[0].contentRect;
      if (cr) setW(Math.round(cr.width));
    });
    ro.observe(el);
    setW(Math.round(el.getBoundingClientRect().width));
    return () => ro.disconnect();
  }, []);
  return [ref, w];
}

// Catmull-Rom through the anchor points, emitted as cubic beziers — without the
// leading moveto, so a stacked band can splice one curve into another as its
// floor. Catmull-Rom is symmetric, so feeding reversed points traces exactly the
// same curve backwards: adjacent bands share an edge with no seam.
function splineSegs(pts) {
  if (pts.length < 2) return '';
  let d = '';
  for (let i = 0; i < pts.length - 1; i++) {
    const p0 = pts[i - 1] || pts[i];
    const p1 = pts[i];
    const p2 = pts[i + 1];
    const p3 = pts[i + 2] || p2;
    const c1x = p1[0] + (p2[0] - p0[0]) / 6;
    const c1y = p1[1] + (p2[1] - p0[1]) / 6;
    const c2x = p2[0] - (p3[0] - p1[0]) / 6;
    const c2y = p2[1] - (p3[1] - p1[1]) / 6;
    d += ` C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${p2[0].toFixed(2)} ${p2[1].toFixed(2)}`;
  }
  return d;
}

const dbMoveTo = (p) => `M ${p[0].toFixed(2)} ${p[1].toFixed(2)}`;
const dbLineTo = (p) => ` L ${p[0].toFixed(2)} ${p[1].toFixed(2)}`;

function splinePath(pts) {
  if (pts.length < 2) return '';
  return dbMoveTo(pts[0]) + splineSegs(pts);
}

/* ---------- Plan-confidence gauge ----------
   The arc + needle are the exported Figma vector (node 7376:44839), which is
   authored for the 84% state. The needle is rotated off that baseline so the
   84% render is pixel-identical to the design and other values still track. */
const GAUGE_SWEEP = 234; // degrees of live arc, from the exported geometry
const GAUGE_BASE_PCT = 84;

function ConfidenceGauge({ pct = 84, width = 56, height = 48 }) {
  const rotation = ((pct - GAUGE_BASE_PCT) / 100) * GAUGE_SWEEP;
  const gid = 'wt-gauge-grad';
  return (
    <svg width={width} height={height} viewBox="0 0 56 48" fill="none" aria-hidden="true">
      <path
        d="M10.1826 37.0851C8.62812 34.0366 7.88186 30.6402 8.01519 27.2208C8.14851 23.8014 9.15696 20.4734 10.9441 17.5551C12.7312 14.6369 15.2372 12.2259 18.2224 10.553C21.2076 8.88005 24.5721 8.00102 27.994 8C31.416 7.99898 34.7811 8.876 37.7672 10.5472C40.7534 12.2183 43.2608 14.6278 45.0497 17.545C46.8386 20.4622 47.849 23.7896 47.9843 27.2089C48.1197 30.6282 47.3755 34.025 45.8228 37.0745"
        stroke={`url(#${gid})`}
        strokeWidth="6"
        strokeLinecap="round"
      />
      <g transform={`rotate(${rotation.toFixed(2)} 28 28)`}>
        <path
          d="M43.9976 34.135C44.1992 34.2528 44.0699 34.5617 43.8445 34.5007L26.4006 29.7849C26.3392 29.7683 26.2894 29.7235 26.2664 29.6642L25.0685 26.5792C25.0296 26.479 25.0769 26.366 25.1755 26.3233L28.2167 25.0057C28.2751 24.9804 28.3421 24.9845 28.3971 25.0166L43.9976 34.135Z"
          fill="#303030"
        />
      </g>
      <defs>
        <linearGradient id={gid} x1="52" y1="28" x2="4" y2="28" gradientUnits="userSpaceOnUse">
          <stop stopColor="#008F35" />
          <stop offset="0.519778" stopColor="#CCC500" />
          <stop offset="1" stopColor="#FF4D5E" />
        </linearGradient>
      </defs>
    </svg>
  );
}

/* ---------- Allocation donut ----------
   Geometry matches the exported Figma ring: outer r=100, inner r=87.5 on a
   200 box, first slice starting at 9 o'clock. The labelled percentages total
   86% in the design, so slice angles are normalised to fill the circle. */
function AllocationDonut({ slices, size = 200, thickness = 12.5, gap = 4, active = -1, onHover }) {
  const r = size / 2 - thickness / 2;
  const c = 2 * Math.PI * r;
  const total = slices.reduce((s, x) => s + x.pct, 0) || 1;

  // The design leads with the first slice clockwise from 9 o'clock and mirrors
  // the remainder, so the second-largest slice closes the ring against the
  // first instead of following it. Legend order is left untouched.
  const ordered = slices.length > 1
    ? [{ s: slices[0], i: 0 }].concat(slices.slice(1).map((s, k) => ({ s, i: k + 1 })).reverse())
    : slices.map((s, i) => ({ s, i }));

  let offset = 0;
  const arcs = ordered.map(({ s, i }) => {
    const len = c * (s.pct / total);
    // Round caps add thickness/2 at each end, so the dash has to be shortened
    // by a full thickness on top of the gap or the slices overlap.
    const seg = Math.max(len - thickness - gap, 0.6);
    const node = (
      <circle
        key={i}
        cx={size / 2}
        cy={size / 2}
        r={r}
        fill="none"
        stroke={s.color}
        strokeWidth={thickness}
        strokeLinecap="round"
        strokeDasharray={`${seg} ${c - seg}`}
        strokeDashoffset={-(offset + (thickness + gap) / 2)}
        className="db-donut__seg"
        opacity={active === -1 || active === i ? 1 : 0.35}
        onMouseEnter={onHover ? () => onHover(i) : undefined}
        onMouseLeave={onHover ? () => onHover(-1) : undefined}
      />
    );
    offset += len;
    return node;
  });

  return (
    <svg
      width={size}
      height={size}
      viewBox={`0 0 ${size} ${size}`}
      // Dash offset 0 sits at 3 o'clock; -180deg moves the start to 9 o'clock.
      style={{ transform: 'rotate(-180deg)' }}
      aria-hidden="true">
      {arcs}
    </svg>
  );
}

/* ---------- Investment projections chart (line + bar) ---------- */
const CHART_PAD = { top: 16, right: 24, bottom: 44, left: 62 };
const CHART_H = 250;
const TIP_W = 320;      // Figma tooltip width
const BAND_FILL = 0.15; // tints the band; the stroked upper edge does the work

// Bar mode, from Figma 7367:17092. Every year in the period gets its own
// column, so the bar width comes from the pitch rather than being fixed: at the
// full 2026–2085 range that lands on the design's 8px, and shorter periods let
// the columns grow (to a limit) instead of leaving thin bars far apart.
const BAR_W_RATIO = 0.45;
const BAR_W_MAX = 14;

// Series values are carried in $M, so the tooltip prints them the same way the
// y-axis captions do.
const fmtM = (v) => `$${Math.round(v * 10) / 10}M`;

function ProjectionChart({ data }) {
  const [ref, width] = useMeasure();
  const [hover, setHover] = React.useState(null);
  const [hoverYear, setHoverYear] = React.useState(null);

  const plotW = Math.max(width - CHART_PAD.left - CHART_PAD.right, 10);
  const plotH = CHART_H - CHART_PAD.top - CHART_PAD.bottom;
  const { firstYear, lastYear, yMin, yMax, yTicks, xTicks, series, milestones } = data;

  const xOf = (year) =>
    CHART_PAD.left + ((year - firstYear) / (lastYear - firstYear)) * plotW;
  const yOf = (v) =>
    CHART_PAD.top + (1 - (v - yMin) / (yMax - yMin)) * plotH;

  const baseY = CHART_PAD.top + plotH;
  const isBar = data.chartType === 'Bar';

  // Both modes stack the buckets bottom-to-top in legend order, so the bands
  // read top-to-bottom against the legend. The Total view ships a single
  // series that isn't in legendOrder, so fall back to paint order there.
  const stackSeries = React.useMemo(() => {
    if (!data.legendOrder) return series;
    const ordered = data.legendOrder.map((k) => series.find((s) => s.key === k)).filter(Boolean);
    return ordered.length === series.length ? ordered : series;
  }, [series, data.legendOrder]);

  // Line mode: resample each bucket onto a yearly grid, accumulating as it goes,
  // so entry i is the top edge of band i — the running sum of buckets 0..i, with
  // the last entry landing on the total. Each band is filled between its own
  // running top and the top of the band below (the axis floor, for the bottom
  // one), tracing that neighbour's ceiling backwards so the bands tile seamlessly.
  const bands = React.useMemo(() => {
    if (!width || isBar) return [];
    const years = [];
    for (let y = firstYear; y <= lastYear; y++) years.push(y);
    const acc = new Array(years.length).fill(yMin);
    const tops = stackSeries.map((s) => years.map((y, j) => {
      acc[j] += valueAt(s.anchors, y);
      return [xOf(y), yOf(acc[j])];
    }));
    const floor = years.map((y) => [xOf(y), baseY]);
    return stackSeries.map((s, i) => {
      const top = tops[i];
      const rev = (i === 0 ? floor : tops[i - 1]).slice().reverse();
      return {
        ...s,
        edge: splinePath(top),
        area: `${dbMoveTo(top[0])}${splineSegs(top)}${dbLineTo(rev[0])}${splineSegs(rev)} Z`,
      };
    });
  }, [width, isBar, stackSeries, firstYear, lastYear, yMin, yMax]);

  // Running totals at one year — the hovered-year dots sit on the band edges,
  // not on the old independent curves.
  const stackAt = (year) => {
    let acc = yMin;
    return stackSeries.map((s) => { acc += valueAt(s.anchors, year); return acc; });
  };

  // Every year in the period is its own column — no gaps in the series.
  const barYears = React.useMemo(() => {
    if (!isBar || !width) return [];
    return Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
  }, [isBar, width, firstYear, lastYear]);

  const barW = barYears.length > 1
    ? Math.max(2, Math.min((plotW / (barYears.length - 1)) * BAR_W_RATIO, BAR_W_MAX))
    : BAR_W_MAX;

  // Inset the column centres by half a bar so the first and last don't hang
  // outside the plot the way xOf() (which pins the axis ends) would put them.
  const xBar = (year) => {
    const inner = Math.max(plotW - barW, 1);
    return CHART_PAD.left + barW / 2 + ((year - firstYear) / (lastYear - firstYear)) * inner;
  };

  // Anything anchored to a year (milestones, the tooltip) follows the columns
  // in bar mode and the curves in line mode.
  const xAt = (year) => (isBar ? xBar(year) : xOf(year));

  // One bar per year, so the hovered year is the highlighted column.
  const hotBar = isBar ? hoverYear : null;

  const stackTotal = (year) => stackSeries.reduce((s, x) => s + valueAt(x.anchors, year), 0);

  // Year under the cursor, snapped to the yearly grid the series are drawn on.
  const readYear = (e) => {
    const rect = e.currentTarget.ownerSVGElement.getBoundingClientRect();
    const t = (e.clientX - rect.left - CHART_PAD.left) / plotW;
    const y = Math.round(firstYear + t * (lastYear - firstYear));
    setHoverYear(Math.max(firstYear, Math.min(lastYear, y)));
  };

  // The year the tooltip describes: the hovered year in line mode, the
  // highlighted column's year in bar mode.
  const tipYear = isBar ? hotBar : hoverYear;

  // Rows for the hover tooltip: one per plotted series, largest first, plus the
  // combined total — the same shape the Financial Profile chart uses.
  const tipRows = tipYear == null ? [] : series
    .map((s) => ({ key: s.key, label: s.label, color: s.color, value: valueAt(s.anchors, tipYear) }))
    .sort((a, b) => b.value - a.value);
  const tipTotal = tipRows.reduce((s, r) => s + r.value, 0);
  const tipMilestone = tipYear == null ? null : milestones.find((m) => m.year === tipYear);

  // The Total view plots one series, so there is no neighbouring band to tell it
  // apart from — a flat slab would just be heavy. A lone band keeps the soft
  // gradient the curves used to carry; solid fills are for actual stacks.
  const solo = bands.length === 1;

  return (
    <div className="db-chart" ref={ref}>
      {width > 0 && (
        <svg width={width} height={CHART_H} className="db-chart__svg" role="img"
          onMouseLeave={() => setHoverYear(null)}
          aria-label="Investment projections by account tax treatment, 2026 to 2085">
          {solo && (
            <defs>
              <linearGradient id="db-band-solo" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor={bands[0].color} stopOpacity="0.24" />
                <stop offset="100%" stopColor={bands[0].color} stopOpacity="0.02" />
              </linearGradient>
            </defs>
          )}

          {/* gridlines + y captions */}
          {yTicks.map((t, i) => {
            const y = CHART_PAD.top + (i / (yTicks.length - 1)) * plotH;
            return (
              <g key={t}>
                <line
                  x1={CHART_PAD.left} y1={y} x2={width - CHART_PAD.right} y2={y}
                  stroke="#E7E7E7" strokeWidth="1" strokeDasharray="3 4" />
                <text x={CHART_PAD.left - 6} y={y} textAnchor="end" dominantBaseline="middle"
                  className="db-chart__ytick">{t}</text>
              </g>
            );
          })}

          {/* Bar mode — one stacked column per sampled year, muted until the
              column is hovered (Figma 7367:17092). */}
          {isBar && barYears.map((yr) => {
            let acc = yMin;
            const on = yr === hotBar;
            return (
              <g key={yr} opacity={on || hotBar == null ? 1 : 0.45}>
                {stackSeries.map((s) => {
                  const v = valueAt(s.anchors, yr);
                  const yTop = yOf(acc + v);
                  const h = Math.max(yOf(acc) - yTop, 0);
                  acc += v;
                  if (h < 0.5) return null;
                  return (
                    <rect key={s.key} x={xBar(yr) - barW / 2} y={yTop} width={barW} height={h}
                      rx={Math.min(2, barW / 2)} fill={s.color} fillOpacity={on ? 1 : 0.35} />
                  );
                })}
              </g>
            );
          })}

          {/* Line mode — every band filled first, then every upper edge, so the
              strokes stay crisp instead of being half-covered by the band
              stacked on top of them */}
          {!isBar && bands.map((b) => (
            <path key={`f-${b.key}`} d={b.area}
              fill={solo ? 'url(#db-band-solo)' : b.color}
              fillOpacity={solo ? 1 : BAND_FILL} stroke="none" />
          ))}
          {!isBar && bands.map((b) => (
            <path key={b.key} d={b.edge} fill="none" stroke={b.color} strokeWidth="1.75"
              strokeLinecap="round" strokeLinejoin="round" />
          ))}

          {/* x captions — evenly distributed, matching the design's flex row */}
          {xTicks.map((t, i) => {
            const x = CHART_PAD.left + (i / (xTicks.length - 1)) * plotW;
            return (
              <text key={t} x={x} y={CHART_H - 14} textAnchor="middle"
                className="db-chart__xtick">{t}</text>
            );
          })}

          {/* year-hover capture layer — sits under the milestone buttons, so
              hovering a milestone hands over to its own tooltip */}
          <rect
            x={CHART_PAD.left} y={CHART_PAD.top} width={plotW} height={plotH}
            fill="transparent" style={{ cursor: 'crosshair' }}
            onMouseMove={readYear} onMouseLeave={() => setHoverYear(null)} />

          {/* guide line + one dot per series at the hovered year (line mode —
              bar mode marks the year by saturating its column instead) */}
          {!isBar && hoverYear != null && hover == null && (
            <g style={{ pointerEvents: 'none' }}>
              <line
                x1={xOf(hoverYear)} x2={xOf(hoverYear)} y1={CHART_PAD.top} y2={baseY}
                stroke="#B0B0B0" strokeWidth="1" strokeDasharray="3 4" />
              {stackAt(hoverYear).map((v, i) => (
                <circle key={stackSeries[i].key} cx={xOf(hoverYear)} cy={yOf(v)}
                  r="3.5" fill="#fff" stroke={stackSeries[i].color} strokeWidth="1.75" />
              ))}
            </g>
          )}
        </svg>
      )}

      {/* HTML tooltip for the hovered year (milestone hover takes precedence).
          It sits beside the guide line, never on top of it, and flips to the
          other side once it would run past the right edge of the plot. */}
      {width > 0 && tipYear != null && hover == null && (
        <div
          className="db-tip"
          style={{
            width: TIP_W,
            left: xAt(tipYear) + 16 + TIP_W <= width - CHART_PAD.right
              ? xAt(tipYear) + 16
              : Math.max(0, xAt(tipYear) - 16 - TIP_W),
            top: CHART_PAD.top,
          }}>
          <div className="db-tip__hd">
            {/* the milestone names the year when one lands on it, as designed */}
            <span className="db-tip__title">
              {tipMilestone && <Icon name={tipMilestone.icon} style={{ width: 15, height: 15 }} />}
              {tipMilestone ? tipMilestone.tip : <span className="tabular">{tipYear}</span>}
            </span>
            <span className="db-tip__sub">
              <span>
                {tipYear === firstYear ? 'Today' : `${tipYear - firstYear} years from today`}
              </span>
              {tipMilestone && <b className="tabular">{tipYear}</b>}
            </span>
          </div>
          <div className="db-tip__rule" />
          <div className="db-tip__rows">
            {tipRows.map((r) => (
              <div className="db-tip__row" key={r.key}>
                <span className="db-tip__nm">
                  <span className="db-tip__dot" style={{ background: r.color }} />
                  {r.label}
                </span>
                <span className="db-tip__amt tabular">{fmtM(r.value)}</span>
              </div>
            ))}
            {/* the Total view already plots one combined series — no sum to add */}
            {tipRows.length > 1 && (
              <div className="db-tip__row db-tip__row--total">
                <span className="db-tip__nm">
                  <span className="db-tip__dot" style={{ background: '#2E5FFF' }} />
                  Total Investments
                </span>
                <span className="db-tip__amt tabular">{fmtM(tipTotal)}</span>
              </div>
            )}
          </div>
        </div>
      )}

      {/* Milestones sit above the SVG so they can carry real tooltips. Both
          modes stack, so both anchor the icon just above the total at that year —
          on the top of the stack in line mode, above the column in bar mode. */}
      {width > 0 && milestones.map((m, i) => (
        <button
          key={i}
          type="button"
          className="db-milestone"
          style={{
            left: xAt(m.year),
            top: Math.max(CHART_PAD.top, yOf(yMin + stackTotal(m.year)) - 18),
          }}
          onMouseEnter={() => setHover(i)}
          onMouseLeave={() => setHover(null)}
          aria-label={m.tip}>
          <Icon name={m.icon} style={{ width: 15, height: 15 }} />
          {hover === i && (
            <span className="db-tip db-tip--compact db-milestone__tip">
              <span className="db-tip__title">{m.tip}</span>
            </span>
          )}
        </button>
      ))}
    </div>
  );
}

// Linear read of an anchor list at a given year (the spline does the smoothing).
function valueAt(anchors, year) {
  if (year <= anchors[0][0]) return anchors[0][1];
  const last = anchors[anchors.length - 1];
  if (year >= last[0]) return last[1];
  for (let i = 0; i < anchors.length - 1; i++) {
    const [x0, v0] = anchors[i];
    const [x1, v1] = anchors[i + 1];
    if (year >= x0 && year <= x1) {
      const t = (year - x0) / (x1 - x0);
      return v0 + (v1 - v0) * t;
    }
  }
  return last[1];
}

Object.assign(window, { ConfidenceGauge, AllocationDonut, ProjectionChart });
