// components.jsx — herbruikbare UI-bouwstenen. Globals uit valuations.jsx
// (fmtMoney, pct, …) zijn via window beschikbaar.
const { useState, useRef } = React;

// ── InfoTip ─────────────────────────────────────────────────────────────────
// Hover-tooltip met bron + betekenis voor de waardering. Portal naar body
// zodat de bubbel nooit door overflow:hidden van een paneel wordt afgekapt.
// Met optionele `link` wordt de tooltip klikbaar (hover blijft staan) zodat je
// de bronlink kunt openen.
function InfoTip({ title, bron, betekenis, link, className = '', children }) {
  const [tip, setTip] = useState(null);
  const ref = useRef(null);
  const hideT = useRef(null);
  const interactive = !!link;
  const place = () => {
    const r = ref.current.getBoundingClientRect();
    const below = r.top < 200;
    // klem horizontaal binnen het viewport zodat de bubbel op mobiel niet wegvalt
    const halfW = Math.min(252, window.innerWidth * 0.92) / 2;
    const cx = Math.min(window.innerWidth - halfW - 6, Math.max(halfW + 6, r.left + r.width / 2));
    setTip({ x: cx, y: below ? r.bottom + 9 : r.top - 9, place: below ? 'bottom' : 'top' });
  };
  const show = () => { clearTimeout(hideT.current); place(); };
  const scheduleHide = () => {
    clearTimeout(hideT.current);
    hideT.current = setTimeout(() => setTip(null), interactive ? 160 : 0);
  };
  return (
    <div ref={ref} className={`infotrig ${className}`} onMouseEnter={show} onMouseLeave={scheduleHide}>
      {children}
      {tip && ReactDOM.createPortal(
        <div className={`infotip infotip--${tip.place} ${interactive ? 'infotip--int' : ''}`}
             style={{ left: tip.x, top: tip.y }} role="tooltip"
             onMouseEnter={interactive ? () => clearTimeout(hideT.current) : undefined}
             onMouseLeave={interactive ? scheduleHide : undefined}>
          <div className="infotip__title">{title}</div>
          <div className="infotip__row"><span>Bron</span><p>{bron}</p></div>
          <div className="infotip__row"><span>Voor de waardering</span><p>{betekenis}</p></div>
          {link && (
            <a className="infotip__link" href={link.href} target="_blank" rel="noopener noreferrer">
              <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M4.5 2.5h-2v7h7v-2M7 2.5h2.5V5M9.5 2.5 5 7" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
              {link.label}
            </a>
          )}
          <i className="infotip__arrow" />
        </div>, document.body)}
    </div>
  );
}

// ── StatusPill ──────────────────────────────────────────────────────────────
function StatusPill({ status, children, size = 'md' }) {
  return (
    <span className={`pill pill--${status} pill--${size}`}>
      <i className="pill__dot" />
      {children || STATUS_NL[status]}
    </span>
  );
}

// ── 52-weeks bereik balk ──────────────────────────────────────────────────────
function RangeBar({ low, high, price, currency, compact = false }) {
  const pos = rangePosition(price, low, high) * 100;
  return (
    <div className={`range ${compact ? 'range--compact' : ''}`}>
      <div className="range__track">
        <div className="range__fill" style={{ width: `${pos}%` }} />
        <div className="range__marker" style={{ left: `${pos}%` }} title={fmtMoney(price, currency)} />
      </div>
      {!compact && (
        <div className="range__ends">
          <span>{fmtMoney(low, currency, 2)}</span>
          <span className="range__pos">{Math.round(pos)}% van bereik</span>
          <span>{fmtMoney(high, currency, 2)}</span>
        </div>
      )}
    </div>
  );
}

// ── Stat (label + waarde) ─────────────────────────────────────────────────────
function Stat({ label, value, sub, tone }) {
  return (
    <div className="stat">
      <div className="stat__label">{label}</div>
      <div className={`stat__value ${tone ? `tone--${tone}` : ''}`}>{value}</div>
      {sub && <div className="stat__sub">{sub}</div>}
    </div>
  );
}

// ── FairValueChart (FastGraphs-stijl) ────────────────────────────────────────
// Koers (maandelijks) + Normale K/W (blauw, historische mediaan-P/E) + Fair value
// (oranje, analisten-P/E + sector), met zoombereiken en aan/uit-toggles per lijn.
const FV_RANGES = ['MAX', '5Y', '4Y', '3Y', '2Y', '1Y', 'YTD'];

function FairValueChart({ priceMonthly, epsAnnual, normalPE, fairPE, buyPrice, currentPrice, currency }) {
  const [range, setRange] = useState('MAX');
  const [show, setShow] = useState({ price: true, normal: true, fair: true, buy: true });

  const monthly = (priceMonthly || [])
    .filter((p) => Number.isFinite(p.price) && Number.isFinite(p.t)).sort((a, b) => a.t - b.t);
  if (monthly.length < 2) {
    return <div className="fvchart fvchart--empty">Koershistorie niet beschikbaar</div>;
  }

  const now = Date.now();
  const YR = 365.25 * 24 * 3600 * 1000;
  const firstT = monthly[0].t;
  const startFor = {
    MAX: firstT, '5Y': now - 5 * YR, '4Y': now - 4 * YR, '3Y': now - 3 * YR,
    '2Y': now - 2 * YR, '1Y': now - 1 * YR, YTD: Date.UTC(new Date().getUTCFullYear(), 0, 1),
  };
  const t0 = Math.max(firstT, startFor[range] ?? firstT);
  const t1 = now;

  let priceSeries = monthly.filter((p) => p.t >= t0 && p.t <= t1);
  if (Number.isFinite(currentPrice)) priceSeries = [...priceSeries, { t: now, price: currentPrice }];
  if (priceSeries.length < 2) priceSeries = monthly.slice(-2);

  // WPA-jaarpunten → tijd (jaareinde; huidig jaar = nu) + lineaire interpolatie
  const cy = new Date().getUTCFullYear();
  const epsPts = (epsAnnual || []).filter((h) => Number.isFinite(h.eps))
    .map((h) => ({ t: h.year >= cy ? now : Date.UTC(h.year, 11, 31), eps: h.eps }))
    .sort((a, b) => a.t - b.t);
  const hasNormal = epsPts.length >= 1 && normalPE > 0;
  const hasFair = epsPts.length >= 1 && fairPE > 0;
  const epsAt = (t) => {
    if (!epsPts.length) return null;
    if (t <= epsPts[0].t) return epsPts[0].eps;
    if (t >= epsPts[epsPts.length - 1].t) return epsPts[epsPts.length - 1].eps;
    for (let i = 1; i < epsPts.length; i++) {
      if (t <= epsPts[i].t) {
        const a = epsPts[i - 1], b = epsPts[i];
        return a.eps + ((t - a.t) / (b.t - a.t)) * (b.eps - a.eps);
      }
    }
    return epsPts[epsPts.length - 1].eps;
  };
  const sampleT = [t0, t1, ...epsPts.map((p) => p.t).filter((t) => t > t0 && t < t1)]
    .filter((v, i, a) => a.indexOf(v) === i).sort((a, b) => a - b);
  // WPA × multiple is alleen zinvol bij positieve winst: verliesperiodes → gat in de band
  // (anders duikt de band onder nul en drukt de koerslijn plat)
  const band = (pe) => sampleT.map((t) => { const e = epsAt(t); return { t, v: e > 0 ? e * pe : null }; });
  const normalPts = hasNormal ? band(normalPE) : [];
  const fairPts = hasFair ? band(fairPE) : [];
  const hasBuy = Number.isFinite(buyPrice);

  const vis = [];
  if (show.price) vis.push(...priceSeries.map((p) => p.price));
  if (show.normal) vis.push(...normalPts.map((p) => p.v).filter(Number.isFinite));
  if (show.fair) vis.push(...fairPts.map((p) => p.v).filter(Number.isFinite));
  if (show.buy && hasBuy) vis.push(buyPrice);
  if (!vis.length) vis.push(...priceSeries.map((p) => p.price));
  const maxV = Math.max(...vis) * 1.08;
  const minV = Math.min(...vis) * 0.9;

  const W = 760, H = 320, padL = 52, padR = 16, padT = 18, padB = 30;
  const x = (t) => padL + ((t - t0) / ((t1 - t0) || 1)) * (W - padL - padR);
  const y = (v) => padT + (1 - (v - minV) / ((maxV - minV) || 1)) * (H - padT - padB);
  const path = (arr, gt, gv) => arr.map((d, i) => `${i ? 'L' : 'M'}${x(gt(d)).toFixed(1)},${y(gv(d)).toFixed(1)}`).join(' ');
  // splits een reeks met gaten (v = null) in aaneengesloten stukken
  const segments = (pts) => {
    const out = []; let cur = [];
    for (const p of pts) { if (Number.isFinite(p.v)) cur.push(p); else { if (cur.length) out.push(cur); cur = []; } }
    if (cur.length) out.push(cur);
    return out.filter((sg) => sg.length >= 2);
  };

  const priceLine = path(priceSeries, (d) => d.t, (d) => d.price);
  const normalLine = segments(normalPts).map((sg) => path(sg, (d) => d.t, (d) => d.v)).join(' ');
  const fairSegs = segments(fairPts);
  const fairLine = fairSegs.map((sg) => path(sg, (d) => d.t, (d) => d.v)).join(' ');
  const fairArea = fairSegs.map((sg) => `${path(sg, (d) => d.t, (d) => d.v)} L${x(sg[sg.length - 1].t).toFixed(1)},${y(minV).toFixed(1)} L${x(sg[0].t).toFixed(1)},${y(minV).toFixed(1)} Z`).join(' ');

  const ticks = 4;
  const tickVals = Array.from({ length: ticks + 1 }, (_, i) => minV + (i / ticks) * (maxV - minV));
  const y0 = new Date(t0).getUTCFullYear(), y1 = new Date(t1).getUTCFullYear();
  const years = []; for (let yy = y0; yy <= y1; yy++) years.push(yy);
  const step = Math.max(1, Math.ceil(years.length / 8));
  const lastP = priceSeries[priceSeries.length - 1];
  // huidige euro-waarde van elke WPA-lijn (= recentste WPA × multiple)
  const normalEnd = normalPts.length ? normalPts[normalPts.length - 1].v : null;
  const fairEnd = fairPts.length ? fairPts[fairPts.length - 1].v : null;
  const eur = (v) => (Number.isFinite(v) ? ' · ' + fmtMoney(v, currency, 0) : ' · n.v.t. (verlies)');

  const toggle = (k) => setShow((s) => ({ ...s, [k]: !s[k] }));
  // Naamgeving: dit zijn waarderingsbánden (WPA × multiple), bewust niet "reële
  // waarde" genoemd — die term is gereserveerd voor de composiet van de methodes.
  const TOGGLES = [
    { k: 'price', label: 'Koers', cls: 'lg__price', on: true, suffix: '' },
    { k: 'normal', label: 'Historische waardering', cls: 'lg__normal', on: hasNormal, suffix: (Number.isFinite(normalPE) ? ` ×${fmtNum(normalPE, 1)}` : '') + eur(normalEnd) },
    { k: 'fair', label: 'Analistenwaardering', cls: 'lg__fair', on: hasFair, suffix: (Number.isFinite(fairPE) ? ` ×${fmtNum(fairPE, 1)}` : '') + eur(fairEnd) },
    { k: 'buy', label: 'Koopprijs', cls: 'lg__buy', on: hasBuy, suffix: Number.isFinite(buyPrice) ? ' ' + fmtMoney(buyPrice, currency, 0) : '' },
  ];

  return (
    <div className="fvchart">
      <div className="fvchart__ranges">
        {FV_RANGES.map((r) => (
          <button key={r} className={range === r ? 'is-on' : ''} onClick={() => setRange(r)}>{r}</button>
        ))}
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" role="img"
           aria-label="Koers, historische waardering en analistenwaardering over de tijd">
        {tickVals.map((tv, i) => (
          <g key={i}>
            <line className="fvchart__grid" x1={padL} x2={W - padR} y1={y(tv)} y2={y(tv)} />
            <text className="fvchart__ylabel" x={padL - 8} y={y(tv) + 3} textAnchor="end">{fmtMoney(tv, currency, 0)}</text>
          </g>
        ))}
        {years.filter((_, i) => i % step === 0).map((yy) => (
          <text key={yy} className="fvchart__xlabel" x={x(Date.UTC(yy, 0, 1))} y={H - 10} textAnchor="middle">{yy}</text>
        ))}
        {show.fair && hasFair && <path className="fvchart__fairarea" d={fairArea} />}
        {show.fair && hasFair && <path className="fvchart__fairline" d={fairLine} />}
        {show.normal && hasNormal && <path className="fvchart__normalline" d={normalLine} />}
        {show.buy && hasBuy && <line className="fvchart__buy" x1={padL} x2={W - padR} y1={y(buyPrice)} y2={y(buyPrice)} />}
        {show.price && <path className="fvchart__priceline" d={priceLine} />}
        {show.price && <circle className="fvchart__dot" cx={x(lastP.t)} cy={y(lastP.price)} r="4.5" />}
      </svg>
      <div className="fvchart__legend">
        {TOGGLES.filter((tg) => tg.on).map((tg) => (
          <button key={tg.k} className={`fvtoggle ${show[tg.k] ? 'is-on' : ''}`} onClick={() => toggle(tg.k)}>
            <i className={tg.cls} />{tg.label}{tg.suffix}
          </button>
        ))}
      </div>
    </div>
  );
}

// ── MethodCard ────────────────────────────────────────────────────────────────
// Eén waarderingsmethode: waarde, mini-balk t.o.v. koers, uitklapbare context.
function MethodCard({ method, price, currency }) {
  const [open, setOpen] = useState(false);
  const v = method.value;
  const ok = Number.isFinite(v);
  const status = ok ? valuationStatus(price, v) : 'unknown';
  const upside = ok ? (v - price) / price : NaN;
  // balk: koers (0) ↔ reële waarde, gecentreerd
  const ratio = ok ? clampN(price / v, 0.4, 1.6) : 1;
  const pos = ((ratio - 0.4) / (1.6 - 0.4)) * 100;

  return (
    <div className={`method method--${status}`}>
      <button className="method__head" onClick={() => setOpen((o) => !o)} aria-expanded={open}>
        <div className="method__title">
          <span className="method__name">{method.label}</span>
          <code className="method__formula">{method.formula}</code>
        </div>
        <div className="method__right">
          <div className="method__value">{fmtMoney(v, currency)}</div>
          <div className={`method__upside tone--${status}`}>{ok ? signedPct(upside) : '—'}</div>
        </div>
        <svg className={`method__chev ${open ? 'is-open' : ''}`} width="14" height="14" viewBox="0 0 14 14">
          <path d="M3 5.5 7 9.5l4-4" fill="none" stroke="currentColor" strokeWidth="1.6"
                strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </button>
      <div className="method__bar">
        <div className="method__bartrack">
          <div className="method__barfv" />
          <div className="method__barnow" style={{ left: `${pos}%` }} />
        </div>
      </div>
      {open && (
        <div className="method__detail">
          <div className="method__row"><span>Formule</span><code>{method.formula}</code></div>
          <div className="method__row"><span>Ingevuld</span><code>{method.plug}</code></div>
          <div className="method__row"><span>Uitkomst</span><code>{fmtMoney(v, currency)}</code></div>
          <p className="method__note">{method.note}</p>
        </div>
      )}
    </div>
  );
}
function clampN(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }

// ── DividendChart ─────────────────────────────────────────────────────────
// Uitgekeerde dividenden (massief) + verwacht dividend (gestreept) per aandeel.
function DividendChart({ history, projection, currency }) {
  const all = [...(history || []).map((h) => ({ ...h, fc: false })), ...(projection || []).map((p) => ({ ...p, fc: true }))]
    .filter((d) => Number.isFinite(d.dividend));
  if (!all.length) {
    return <div className="divchart divchart--empty">Dividendhistorie niet beschikbaar</div>;
  }
  const W = 760, H = 230, padL = 48, padR = 14, padT = 14, padB = 26;
  const max = Math.max(...all.map((d) => d.dividend), 0.01) * 1.18;
  const n = all.length;
  const bw = (W - padL - padR) / n;
  const base = padT + (H - padT - padB);
  const y = (v) => padT + (1 - v / max) * (H - padT - padB);
  const ticks = 3;
  const tickVals = Array.from({ length: ticks + 1 }, (_, i) => (i / ticks) * max);
  return (
    <div className="divchart">
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" role="img"
           aria-label="Dividend per aandeel per jaar, historisch en verwacht">
        {tickVals.map((tv, i) => (
          <g key={i}>
            <line className="fvchart__grid" x1={padL} x2={W - padR} y1={y(tv)} y2={y(tv)} />
            <text className="fvchart__ylabel" x={padL - 8} y={y(tv) + 3} textAnchor="end">{fmtMoney(tv, currency, tv >= 10 ? 0 : 1)}</text>
          </g>
        ))}
        {all.map((d, i) => {
          const x = padL + i * bw + bw * 0.16;
          const w = bw * 0.68;
          const h = Math.max(0, base - y(d.dividend));
          return (
            <g key={d.year}>
              {d.dividend > 0 && (
                <rect className={d.fc ? 'divchart__fc' : 'divchart__bar'} x={x} y={y(d.dividend)} width={w} height={h} rx="2.5">
                  <title>{`${d.year}: ${fmtMoney(d.dividend, currency)}${d.fc ? ' (verwacht)' : ''}`}</title>
                </rect>
              )}
              {(i % 2 === 0 || d.fc) && <text className="fvchart__xlabel" x={x + w / 2} y={H - 9} textAnchor="middle">{`'${String(d.year).slice(2)}`}</text>}
            </g>
          );
        })}
      </svg>
      <div className="fvchart__legend">
        <span className="lg"><i className="lg__paid" />Uitgekeerd</span>
        <span className="lg"><i className="lg__fc" />Verwacht</span>
      </div>
    </div>
  );
}

// ── FinancialsChart ─────────────────────────────────────────────────────────
// Omzet & winst / Marges / Kasstroom per boekjaar (+TTM), wisselbaar, met een
// tabelweergave. Bedragen in de rapportagevaluta. Kleuren: --s1..--s3 (vaste
// volgorde, gevalideerd); identiteit nooit alleen via kleur (legenda, tooltip,
// directe labels bij lijnen, tabel).
const FIN_TABS = [
  { k: 'pl', label: 'Omzet & winst', kind: 'bar', series: [
    { f: 'revenue', label: 'Omzet' }, { f: 'operatingIncome', label: 'Operationele winst' },
    { f: 'netIncome', label: 'Nettowinst' }] },
  { k: 'margin', label: 'Marges', kind: 'line', series: [
    { f: 'grossMargin', label: 'Brutomarge', short: 'Bruto' },
    { f: 'operatingMargin', label: 'Operationele marge', short: 'Operationeel' },
    { f: 'netMargin', label: 'Nettomarge', short: 'Netto' }] },
  { k: 'cf', label: 'Kasstroom', kind: 'bar', series: [
    { f: 'operatingCashFlow', label: 'Operationele kasstroom' }, { f: 'capex', label: 'Investeringen (capex)' },
    { f: 'freeCashFlow', label: 'Vrije kasstroom' }] },
];

// Perioden + marges (alleen bij omzet > 0: marges op nul/negatieve omzet zijn betekenisloos)
function finPeriods(fin) {
  const rows = (fin.annual || []).map((r) => ({ ...r, label: String(r.year) }));
  if (fin.ttm) rows.push({ ...fin.ttm, label: 'TTM' });
  return rows.map((r) => {
    const m = (x) => (r.revenue > 0 && Number.isFinite(x) ? x / r.revenue : null);
    return { ...r, grossMargin: m(r.grossProfit), operatingMargin: m(r.operatingIncome), netMargin: m(r.netIncome) };
  });
}
// Mooie as-stappen (1-2-2,5-5 × 10ⁿ)
function niceScale(lo, hi, ticks = 4) {
  if (!(hi > lo)) { hi = lo + 1; }
  const raw = (hi - lo) / ticks;
  const mag = Math.pow(10, Math.floor(Math.log10(raw)));
  const step = [1, 2, 2.5, 5, 10].find((m) => m * mag >= raw) * mag;
  return { lo: Math.floor(lo / step) * step, hi: Math.ceil(hi / step) * step, step };
}
// Staaf met 4px afgeronde data-kant, vlak op de nullijn
function finBarPath(x, w, y0, y1, r = 4) {
  const h = Math.abs(y1 - y0);
  if (h < 0.5) return '';
  const rr = Math.min(r, h, w / 2);
  const up = y1 < y0, e = up ? y1 + rr : y1 - rr;
  return `M${x},${y0} V${e} Q${x},${y1} ${x + rr},${y1} H${x + w - rr} Q${x + w},${y1} ${x + w},${e} V${y0} Z`;
}

function FinancialsChart({ financials }) {
  const [tab, setTab] = useState('pl');
  const [view, setView] = useState('chart');
  const [hot, setHot] = useState(null);
  const fin = financials;
  if (!fin || !(fin.annual || []).length) {
    return <div className="fvchart--empty">Financiële historie niet beschikbaar</div>;
  }
  const cur = fin.currency;
  const T = FIN_TABS.find((t) => t.k === tab);
  const isPct = T.kind === 'line';
  const fmtV = (v) => (!Number.isFinite(v) ? '—' : isPct ? pct(v, 1) : fmtBig(v, cur));
  const P = finPeriods(fin).filter((p) => T.series.some((s) => Number.isFinite(p[s.f])));

  const bar = (
    <div className="fin__bar">
      <div className="fvchart__ranges" role="tablist" aria-label="Financiële grafiek">
        {FIN_TABS.map((t) => (
          <button key={t.k} role="tab" aria-selected={tab === t.k} className={tab === t.k ? 'is-on' : ''}
                  onClick={() => { setTab(t.k); setHot(null); }}>{t.label}</button>
        ))}
      </div>
      <div className="seg2 seg2--sm">
        <button className={view === 'chart' ? 'is-on' : ''} onClick={() => setView('chart')}>Grafiek</button>
        <button className={view === 'table' ? 'is-on' : ''} onClick={() => setView('table')}>Tabel</button>
      </div>
    </div>
  );
  const legend = (
    <div className="fvchart__legend">
      {T.series.map((s, j) => <span key={s.f} className="lg"><i className={`lg__s${j + 1}`} />{s.label}</span>)}
    </div>
  );

  if (P.length < 2) {
    return (
      <div className="fvchart">
        {bar}
        <div className="fvchart--empty">{isPct
          ? 'Marges niet zinvol voor dit type bedrijf (bv. verzekeraar of bank) of omzet niet beschikbaar'
          : 'Te weinig cijfers voor deze grafiek'}</div>
      </div>
    );
  }

  if (view === 'table') {
    return (
      <div className="fvchart">
        {bar}
        <div className="fin__tablewrap">
          <table className="fin__table">
            <thead><tr><th>{isPct ? '' : cur}</th>{P.map((p) => <th key={p.label}>{p.label}</th>)}</tr></thead>
            <tbody>
              {T.series.map((s) => (
                <tr key={s.f}><td>{s.label}</td>{P.map((p) => <td key={p.label}>{fmtV(p[s.f])}</td>)}</tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    );
  }

  const W = 760, H = 280, padL = 66, padR = isPct ? 120 : 14, padT = 14, padB = 28;
  const vals = P.flatMap((p) => T.series.map((s) => p[s.f])).filter(Number.isFinite);
  // marges: as begrensd op −100% (extreme verliesmarges drukken anders alles plat; tooltip/tabel tonen de echte waarde)
  const rawLo = Math.min(0, ...vals), rawHi = Math.max(0, ...vals);
  const sc = niceScale(isPct ? Math.max(rawLo, -1) : rawLo, rawHi);
  const clampV = (v) => Math.max(sc.lo, Math.min(sc.hi, v));
  const y = (v) => padT + ((sc.hi - clampV(v)) / (sc.hi - sc.lo)) * (H - padT - padB);
  const n = P.length, colW = (W - padL - padR) / n;
  const cx = (i) => padL + (i + 0.5) * colW;
  const ticks = [];
  for (let v = sc.lo; v <= sc.hi + sc.step / 2; v += sc.step) ticks.push(+v.toFixed(10));
  const y0 = y(0);

  let marks = null;
  if (!isPct) {
    const groupW = Math.min(colW * 0.72, 120), gap = 2;
    const bw = (groupW - gap * (T.series.length - 1)) / T.series.length;
    marks = P.map((p, i) => T.series.map((s, j) => {
      const v = p[s.f];
      if (!Number.isFinite(v)) return null;
      const x = padL + i * colW + (colW - groupW) / 2 + j * (bw + gap);
      return <path key={`${i}-${s.f}`} className={`fin__s${j + 1}`} d={finBarPath(x, bw, y0, y(v))} style={{ pointerEvents: 'none' }} />;
    }));
  } else {
    const lines = T.series.map((s, j) => {
      let d = '', pen = false;
      P.forEach((p, i) => {
        const v = p[s.f];
        if (!Number.isFinite(v)) { pen = false; return; }
        d += `${pen ? 'L' : 'M'}${cx(i).toFixed(1)},${y(v).toFixed(1)} `; pen = true;
      });
      return <path key={s.f} className={`fin__line fin__s${j + 1}`} d={d} style={{ pointerEvents: 'none' }} />;
    });
    const dots = T.series.map((s, j) => P.map((p, i) => (Number.isFinite(p[s.f])
      ? <circle key={`${s.f}-${i}`} className={`fin__dot fin__s${j + 1}`} cx={cx(i)} cy={y(p[s.f])} r="4" style={{ pointerEvents: 'none' }} />
      : null)));
    // directe labels aan het einde van elke lijn, verticaal uit elkaar geduwd
    const ends = T.series.map((s, j) => {
      for (let i = P.length - 1; i >= 0; i--) if (Number.isFinite(P[i][s.f])) return { j, s, v: P[i][s.f], i, yy: y(P[i][s.f]) };
      return null;
    }).filter(Boolean).sort((a, b) => a.yy - b.yy);
    for (let k = 1; k < ends.length; k++) if (ends[k].yy - ends[k - 1].yy < 13) ends[k].yy = ends[k - 1].yy + 13;
    const labels = ends.map((e) => (
      <text key={e.s.f} className="fin__dlabel" x={cx(P.length - 1) + 10} y={e.yy + 4} style={{ pointerEvents: 'none' }}>
        {`${e.s.short} ${pct(e.v, 0)}`}
      </text>
    ));
    marks = [lines, dots, labels];
  }

  const tipI = hot != null && hot < n ? hot : null;
  // tooltip naast de kolom (niet erover): rechterhelft → links ervan, anders rechts
  const tipLeft = tipI != null ? ((tipI < n / 2 ? cx(tipI) + colW / 2 : cx(tipI) - colW / 2) / W) * 100 : 0;
  const tipSide = tipI != null && tipI >= n / 2 ? 'translateX(calc(-100% - 6px))' : 'translateX(6px)';
  return (
    <div className="fvchart">
      {bar}
      <div className="fin__plot" onMouseLeave={() => setHot(null)}>
        <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" role="img"
             aria-label={`${T.label} per boekjaar${cur && !isPct ? ` in ${cur}` : ''}`}>
          {ticks.map((tv) => (
            <g key={tv}>
              <line className={tv === 0 ? 'fin__zero' : 'fvchart__grid'} x1={padL} x2={W - padR} y1={y(tv)} y2={y(tv)} />
              <text className="fvchart__ylabel" x={padL - 8} y={y(tv) + 3} textAnchor="end">
                {isPct ? pct(tv, 0) : fmtBig(tv, cur)}
              </text>
            </g>
          ))}
          {P.map((p, i) => (
            <g key={p.label} className={`fin__col ${tipI === i ? 'is-hot' : ''}`}
               onMouseEnter={() => setHot(i)} onClick={() => setHot((h) => (h === i ? null : i))}>
              <rect className="fin__hover" x={padL + i * colW} y={padT} width={colW} height={H - padT - padB} rx="6" />
              <text className="fvchart__xlabel" x={cx(i)} y={H - 9} textAnchor="middle">{p.label}</text>
            </g>
          ))}
          {marks}
        </svg>
        {tipI != null && (
          <div className="fin__tip" style={{ left: `${tipLeft}%`, transform: tipSide }} role="tooltip">
            <b>{P[tipI].label === 'TTM' ? `Laatste 12 mnd (t/m ${P[tipI].end})` : `Boekjaar ${P[tipI].label} (t/m ${P[tipI].end})`}</b>
            {T.series.map((s, j) => (
              <div key={s.f}><span><i className={`fin__sw fin__s${j + 1}`} />{s.label}</span><em>{fmtV(P[tipI][s.f])}</em></div>
            ))}
          </div>
        )}
      </div>
      {legend}
    </div>
  );
}

Object.assign(window, { StatusPill, RangeBar, Stat, FairValueChart, MethodCard, InfoTip, DividendChart, FinancialsChart });
