/* Clear Mint — Investment Portfolio "Command Center"
   Wired to live state: every figure is derived from CM.S.investments
   (the same localStorage S the rest of the app reads/writes). The
   "Refresh prices" control hits the serverless /api/quote service and
   writes currentPrice / marketValue back through CM.mutate so the whole
   app stays consistent. window.PageInvestments overrides the legacy
   fallback in cm-pages-money.jsx (loaded AFTER it).
   Depends on cm-charts + cm-components + cm-app-kit + cm-an-charts + cm-data. */
(function () {
const { ChartCard, DonutChart, Legend, DataTable } = window;
const MiniSpark = window.AnMiniSpark, ANI = window.ANI;
const { G, PU, AM, BL, SL, RD, TEAL, PINK, INDIGO } = window.CMC;
const GOLD = '#D4A95D';

/* ───────────────── classification / palette ───────────────── */
const TYPE_META = {
  etf:         { label:'ETFs',         color:PU },
  stock:       { label:'Stocks',       color:BL },
  mutual_fund: { label:'Mutual Funds', color:G  },
  crypto:      { label:'Crypto',       color:GOLD },
  bond:        { label:'Bonds',        color:SL },
  real_estate: { label:'Real Estate',  color:RD },
  cash:        { label:'Cash',         color:TEAL },
  other:       { label:'Other',        color:TEAL },
};
const typeMeta = t => TYPE_META[t] || TYPE_META.other;

const ACCT_COLORS = { TFSA:G, RRSP:BL, RESP:AM, FHSA:PINK, LIRA:TEAL, RRIF:INDIGO, 'Non-Registered':PU };
const acctColor = a => ACCT_COLORS[a] || SL;
const REGISTERED = /TFSA|RRSP|RESP|LIRA|RRIF|FHSA/i;

const CCY_COLORS = { CAD:G, USD:BL, EUR:PU, GBP:AM };
const ccyColor = c => CCY_COLORS[c] || SL;

/* tiny eased ramp for sparkline context (endpoints are the real values) */
function ramp(a, b, n = 9) {
  const out = [];
  for (let i = 0; i < n; i++) { const t = i / (n - 1); const e = t * t * (3 - 2 * t); out.push(a + (b - a) * e); }
  return out;
}

/* USD↔CAD conversion for the "Show in CAD / USD" toggle (other currencies pass through). */
function fxConv(value, fromCcy, displayCcy, usdToCad) {
  fromCcy = (fromCcy || 'CAD').toUpperCase();
  if (fromCcy === displayCcy || !usdToCad) return value;
  if (fromCcy === 'USD' && displayCcy === 'CAD') return value * usdToCad;
  if (fromCcy === 'CAD' && displayCcy === 'USD') return value / usdToCad;
  return value;
}

/* ───────────────── unified normalize + writers (the 3 source paths converge here) ─────────────────
   Statement import, integration, and manual entry all build records through these, so every holding /
   account carries the same shape — including source + inputMethod ("statement"|"integration"|"manual")
   + excluded. Writers mutate S.investments / S.investmentAccounts (persisted by cm-data save/load and,
   because cm-sync reuses that same blob, the cloud snapshot too). */
function normalizeHolding(raw, opts) {
  opts = opts || {};
  const ccy = (raw.currency || 'CAD').toUpperCase();
  const ticker = String(raw.symbol || raw.ticker || '').trim().toUpperCase();
  const shares = +raw.shares || +raw.units || 0;
  const price  = raw.currentPrice != null ? +raw.currentPrice : (raw.price != null ? +raw.price : 0);
  const mv     = raw.marketValue != null ? +raw.marketValue
               : (raw.value != null ? +raw.value : Math.round(shares * price * 100) / 100);
  const isFund = raw.isFund != null ? !!raw.isFund : !ticker;
  const inputMethod = opts.inputMethod || raw.inputMethod || 'manual';
  return {
    symbol: ticker || '—', name: raw.name || raw.fundName || ticker || 'Holding',
    type: raw.type || (isFund ? 'mutual_fund' : 'stock'),
    shares, avgCost: raw.avgCost != null ? +raw.avgCost : price, currentPrice: price, marketValue: mv,
    account: raw.account || raw.planType || 'Non-Registered', broker: raw.broker || opts.source || 'Brokerage',
    currency: ccy, isFund,
    source: opts.source || raw.source || null, inputMethod,
    excluded: !!raw.excluded, fromStatement: inputMethod === 'statement' || !!raw.fromStatement,
  };
}
function normalizeAccount(raw, opts) {
  opts = opts || {};
  const inputMethod = opts.inputMethod || raw.inputMethod || 'statement';
  return {
    provider: opts.source || raw.provider || raw.source || 'Statement',
    source: opts.source || raw.source || 'Statement',
    planType: raw.planType, planName: raw.planName || raw.planType, register: raw.planType,
    registered: raw.registered !== false, currency: (raw.currency || 'CAD').toUpperCase(),
    balance: raw.closingValue != null ? +raw.closingValue : (+raw.balance || 0),
    holdings: Array.isArray(raw.holdings) ? raw.holdings : [],
    inputMethod, fromStatement: inputMethod === 'statement', importedAt: opts.importedAt || null,
  };
}
/* Writers — the single place S.investments / S.investmentAccounts get appended. */
function writeHoldings(CM, list) {
  if (!list || !list.length) return;
  CM.mutate(function () { const arr = (CM.S.investments = CM.S.investments || []); list.forEach(h => arr.push(h)); });
}
function writeAccounts(CM, list, replaceSource) {
  if (!list || !list.length) return;
  CM.mutate(function () {
    let arr = (CM.S.investmentAccounts || []);
    if (replaceSource) arr = arr.filter(a => a.source !== replaceSource); // re-import replaces same provider
    list.forEach(a => arr.push(a));
    CM.S.investmentAccounts = arr;
  });
}

/* ───────────────── derive everything from holdings ───────────────── */
function derive(invs, displayCcy, usdToCad) {
  displayCcy = displayCcy || 'CAD';
  const rows = invs.map((i, _i) => {
    const ccy  = (i.currency || 'CAD').toUpperCase();
    const cv   = (v) => fxConv(v, ccy, displayCcy, usdToCad);     // native → display currency
    const mvN  = i.marketValue != null ? +i.marketValue : (+i.shares || 0) * (+i.currentPrice || 0);
    const costN = i.costBasis  != null ? +i.costBasis  : (+i.shares || 0) * (+i.avgCost || 0);
    const mv = cv(mvN), cost = cv(costN), gain = mv - cost;
    return {
      symbol:i.symbol || '—', name:i.name || i.symbol || 'Holding', type:i.type || 'other',
      broker:i.broker || 'Brokerage', account:i.account || 'Non-Registered',
      currency: ccy, shares:+i.shares || 0,
      avgCost: cv(+i.avgCost || 0), price: cv(+i.currentPrice || 0),
      mv, cost, gain, gainPct: cost ? gain / cost * 100 : 0, mvNative: mvN,
      _i, excluded: !!i.excluded, fromStatement: !!i.fromStatement,
    };
  });
  const active    = rows.filter(r => !r.excluded);   // excluded holdings drop out of every total/chart/allocation
  const totalMV   = active.reduce((s, r) => s + r.mv, 0);
  const totalCost = active.reduce((s, r) => s + r.cost, 0);
  rows.forEach(r => r.weight = (!r.excluded && totalMV) ? r.mv / totalMV : 0);
  rows.sort((a, b) => (a.excluded - b.excluded) || (b.mv - a.mv));  // excluded sink to the bottom of the list
  active.sort((a, b) => b.mv - a.mv);

  const group = key => {
    const m = {};
    active.forEach(r => { const k = r[key]; m[k] = (m[k] || 0) + r.mv; });
    return Object.keys(m).map(k => ({ key:k, value:m[k] })).sort((a, b) => b.value - a.value);
  };
  const byType    = group('type').map(g => ({ ...g, ...typeMeta(g.key) }));
  const byBroker  = group('broker').map(g => ({ ...g, label:g.key }));
  const byAccount = group('account').map(g => ({ ...g, label:g.key, color:acctColor(g.key) }));
  const byCcy     = group('currency').map(g => ({ ...g, label:g.key, color:ccyColor(g.key) }));

  const crypto     = active.filter(r => r.type === 'crypto').reduce((s, r) => s + r.mv, 0);
  const registered = active.filter(r => REGISTERED.test(r.account)).reduce((s, r) => s + r.mv, 0);
  const top3       = active.slice(0, 3).reduce((s, r) => s + r.mv, 0);
  const topW       = active.length ? active[0].weight : 0;
  const cryptoW    = totalMV ? crypto / totalMV : 0;

  // concentration / volatility proxy → 0..100 (higher = riskier)
  const risk = Math.max(8, Math.min(96, Math.round(22 + cryptoW * 100 * 0.55 + topW * 100 * 0.40)));
  const riskBand = risk < 30 ? ['Low', G] : risk < 50 ? ['Moderate', '#B5740F']
                 : risk < 70 ? ['Elevated', '#C77A24'] : ['High', RD];

  const movers = [...active].sort((a, b) => b.gainPct - a.gainPct);
  return {
    rows, totalMV, totalCost, totalGain: totalMV - totalCost,
    gainPct: totalCost ? (totalMV - totalCost) / totalCost * 100 : 0,
    byType, byBroker, byAccount, byCcy,
    crypto, cryptoW, registered, registeredW: totalMV ? registered / totalMV : 0,
    top3, top3W: totalMV ? top3 / totalMV : 0, topW,
    best: movers[0], worst: movers[movers.length - 1], movers,
    risk, riskBand, brokers:byBroker.length,
  };
}

/* ───────────────── small building blocks ───────────────── */
function Badge({ ini, color }) {
  return <span className="inv-badge" style={{ background:'#F1F5F4', color, boxShadow:`inset 0 0 0 1px ${color}22` }}>{ini}</span>;
}
function ViewLink({ children = 'View Details', onClick }) {
  return <a className="t-label text-green" onClick={onClick}
    style={{ cursor:'pointer', display:'inline-flex', alignItems:'center', gap:4, fontWeight:800, fontSize:12.5 }}>
    {children} <ANI.chevR size={13}/></a>;
}

function KpiTile({ feat, icon, tint, col, label, value, delta, foot, spark }) {
  return (
    <div className={`an-exectile ${feat ? 'feat' : ''}`}>
      <div className="et-head">
        <span className="et-ic" style={feat ? null : { background:tint, color:col }}>{icon}</span>
        <span className="et-label">{label}</span>
      </div>
      <div className="et-val num" style={{ fontSize:23 }}>{value}</div>
      <div className="et-delta" style={{ color: feat ? '#7FD8AE' : (delta && delta.neg ? RD : G) }}>
        {delta
          ? <>{delta.neg ? <ANI.arrDn size={13}/> : <ANI.arrUp size={13}/>}{delta.v}
              {delta.sub && <span className="num" style={{ fontWeight:600, color: feat ? 'rgba(234,240,234,.7)' : (delta.neg ? RD : G) }}>{delta.sub}</span>}
              <span className="mut" style={feat ? { color:'rgba(234,240,234,.6)' } : null}>{foot}</span></>
          : <span className="mut" style={feat ? { color:'rgba(234,240,234,.6)' } : null}>{foot}</span>}
      </div>
      <div className="et-spark"><MiniSpark data={spark} color={feat ? GOLD : (col || G)} light={feat} h={34}/></div>
    </div>
  );
}

/* portfolio value vs invested cost — endpoints are real, interior is an eased path */
function PerfChart({ vals, inv, fmt, height = 300 }) {
  const W = 760, H = height, P = { l:54, r:16, t:14, b:28 };
  const n = vals.length, yMax = (Math.max(...vals, ...inv) * 1.12) || 1;
  const X = i => P.l + (i / (n - 1)) * (W - P.l - P.r);
  const Y = v => P.t + (1 - v / yMax) * (H - P.t - P.b);
  const path = a => a.map((v, i) => `${i ? 'L' : 'M'}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join(' ');
  const area = `${path(vals)} L${X(n - 1)},${H - P.b} L${X(0)},${H - P.b} Z`;
  const ticks = [1, .75, .5, .25, 0].map(f => f * yMax);
  const months = ['', 'Jul', '', 'Sep', '', 'Nov', '', 'Jan', '', 'Mar', '', 'May', ''];
  return (
    <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{ display:'block' }}>
      <defs><linearGradient id="invPerf" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor={G} stopOpacity=".16"/><stop offset="100%" stopColor={G} stopOpacity="0"/>
      </linearGradient></defs>
      {ticks.map((v, i) => { const y = Y(v); return (
        <g key={i}><line x1={P.l} y1={y} x2={W - P.r} y2={y} stroke="var(--line)" strokeWidth="1"/>
          <text x={P.l - 9} y={y + 4} textAnchor="end" fontSize="10.5" fontWeight="600" fill="var(--ink-400)">{fmt(v)}</text></g>); })}
      <path d={area} fill="url(#invPerf)"/>
      <path d={path(inv)}  fill="none" stroke={BL} strokeWidth="2.2" strokeDasharray="5 4" strokeLinejoin="round"/>
      <path d={path(vals)} fill="none" stroke={G} strokeWidth="2.8" strokeLinejoin="round"/>
      {vals.map((v, i) => i % 2 === 0 && <circle key={i} cx={X(i)} cy={Y(v)} r="2.6" fill="#fff" stroke={G} strokeWidth="1.6"/>)}
      {months.map((m, i) => m && <text key={i} x={X(i)} y={H - 7} textAnchor="middle" fontSize="10.5" fontWeight="600" fill="var(--ink-400)">{m}</text>)}
    </svg>
  );
}

/* horizontal bar list (broker / account-type / currency) */
function BarList({ rows, total, fmt }) {
  const max = Math.max(...rows.map(r => r.value), 1);
  return (
    <div>
      {rows.map((r, i) => (
        <div className="inv-row" key={i}>
          <Badge ini={r.ini} color={r.color}/>
          <div className="inv-row-main">
            <div className="inv-row-name">{r.label}{r.tag && <span style={{ fontSize:10.5, fontWeight:700, color:'var(--ink-400)', marginLeft:7 }}>{r.tag}</span>}</div>
            <div className="inv-acctbar"><i style={{ width:(r.value / max * 100) + '%', background:r.color }}/></div>
          </div>
          <div className="inv-row-val"><div className="v num">{fmt(r.value)}</div><div className="p num">{(r.value / total * 100).toFixed(1)}%</div></div>
        </div>
      ))}
    </div>
  );
}

/* gain / loss by holding — diverging bars */
function GainBars({ rows }) {
  const maxAbs = Math.max(...rows.map(r => Math.abs(r.gainPct)), 1);
  return (
    <div style={{ marginTop:6 }}>
      {rows.map((r, i) => (
        <div className="inv-sector" key={i}>
          <span className="nm" style={{ width:120, flex:'0 0 120px' }}>{r.symbol} <span style={{ color:'var(--ink-400)', fontWeight:600 }}>·{r.type === 'mutual_fund' ? 'fund' : r.type}</span></span>
          <span className="tk"><i style={{ width:(Math.abs(r.gainPct) / maxAbs * 100) + '%', background:r.gain >= 0 ? G : RD }}/></span>
          <span className="pc num" style={{ color:r.gain >= 0 ? G : RD, width:58, flex:'0 0 58px' }}>{(r.gain >= 0 ? '+' : '') + r.gainPct.toFixed(1)}%</span>
        </div>
      ))}
    </div>
  );
}

/* risk gauge — multicolor semicircle + needle */
function RiskGauge({ score = 62, size = 240 }) {
  const W = size, H = size * 0.62, cx = W / 2, cy = H - 8, r = W * 0.40, stroke = W * 0.075;
  const segs = [['#E04044', 0, .25], ['#FBA834', .25, .5], ['#F2C94C', .5, .7], ['#5AB97E', .7, .85], ['#10915F', .85, 1]];
  const polar = f => { const a = Math.PI - f * Math.PI; return [cx + Math.cos(a) * r, cy - Math.sin(a) * r]; };
  const arcSeg = (f0, f1) => { const [x0, y0] = polar(f0), [x1, y1] = polar(f1); return `M ${x0} ${y0} A ${r} ${r} 0 0 1 ${x1} ${y1}`; };
  const f = Math.max(0, Math.min(1, score / 100));
  const na = Math.PI - f * Math.PI, nlen = r - stroke * 0.3;
  const nx = cx + Math.cos(na) * nlen, ny = cy - Math.sin(na) * nlen;
  return (
    <div style={{ position:'relative', width:'100%', maxWidth:W, margin:'0 auto' }}>
      <svg width="100%" viewBox={`0 0 ${W} ${H + 30}`} style={{ display:'block' }}>
        {segs.map(([c, a, b], i) => <path key={i} d={arcSeg(a, b)} fill="none" stroke={c} strokeWidth={stroke} strokeLinecap="butt"/>)}
        <line x1={cx} y1={cy} x2={nx} y2={ny} stroke="var(--ink-900)" strokeWidth="3" strokeLinecap="round"/>
        <circle cx={cx} cy={cy} r="6" fill="var(--ink-900)"/>
        <text x={polar(0)[0]} y={cy + 20} textAnchor="middle" fontSize="11" fontWeight="700" fill="var(--ink-400)">Low</text>
        <text x={cx} y={polar(.5)[1] - 12} textAnchor="middle" fontSize="11" fontWeight="700" fill="var(--ink-400)">Moderate</text>
        <text x={polar(1)[0]} y={cy + 20} textAnchor="middle" fontSize="11" fontWeight="700" fill="var(--ink-400)">High</text>
      </svg>
    </div>
  );
}

const FOOT = [
  { icon:<ANI.bank size={20}/>,    t:'10,000+ Institutions', s:'Connect with banks, brokerages and wealth platforms.' },
  { icon:<ANI.shield size={20}/>,  t:'Secure & Private',     s:'Your data is encrypted and never shared.' },
  { icon:<ANI.refresh size={20}/>, t:'Live Market Prices',   s:'Refresh quotes from the market on demand.' },
  { icon:<ANI.spark size={20}/>,   t:'AI-Powered Insights',  s:'Smart insights to help you grow your wealth.' },
];

/* ───────────────── PAGE ───────────────── */
function PageInvestments() {
  const CM = window.useStore ? window.useStore() : window.CM;
  const fmt = CM.M0, fmt2 = CM.M;
  const tabs = ['Portfolio', 'Holdings', 'Performance', 'Allocation', 'Market Context'];
  const [tab, setTab] = React.useState('Portfolio');
  const [busy, setBusy] = React.useState(false);
  const [stamp, setStamp] = React.useState(null);
  const [note, setNote] = React.useState(null);
  const [hq, setHq] = React.useState('');
  const [hType, setHType] = React.useState('');
  const [hAcct, setHAcct] = React.useState('');
  const [hCcy, setHCcy] = React.useState('');
  const [hSort, setHSort] = React.useState('value');
  const [holdModal, setHoldModal] = React.useState(null); // null | {idx:-1} add | {idx} edit
  const [delHold, setDelHold] = React.useState(null);     // null | {idx, name}
  const [srcPick, setSrcPick] = React.useState(false);    // source-picker (3 paths) open
  const [importPrev, setImportPrev] = React.useState(null); // null | parsed statement data (preview)
  const [connectOpen, setConnectOpen] = React.useState(false); // integrations modal open
  const [impBusy, setImpBusy] = React.useState(false);
  const [impErr, setImpErr] = React.useState(null);
  const [displayCcy, setDisplayCcy] = React.useState('CAD'); // "Show in CAD / USD" — never assume CAD
  const [fxRate, setFxRate] = React.useState(0);             // USD→CAD (Bank of Canada via /api/fx)
  React.useEffect(() => {
    let alive = true;
    fetch((window.CM_API_BASE || '/api') + '/fx?base=USD&quote=CAD').then(r => r.json())
      .then(d => { if (alive && d && d.rate) setFxRate(+d.rate); }).catch(() => {});
    return () => { alive = false; };
  }, []);
  const usdToCad = fxRate || 1.35; // sensible fallback if FX is briefly unavailable
  const toggleExclude = (i) => CM.mutate(() => { const x = (CM.S.investments || [])[i]; if (x) x.excluded = !x.excluded; });

  const invs = CM.S.investments || [];
  const D = derive(invs, displayCcy, usdToCad);

  function refreshPrices() {
    const syms = invs.map(i => i.symbol).filter(Boolean).join(',');
    if (!syms) return;
    setBusy(true); setNote(null);
    CM.api.quote(syms).then(d => {
      const list = (d && d.quotes) || [];           // /api/quote returns { quotes:[{symbol,price,…}] }
      const q = {}; list.forEach(r => { if (r && r.symbol) q[r.symbol] = r; });
      let hit = 0;
      CM.mutate(() => {
        (CM.S.investments || []).forEach(i => {
          const row = q[i.symbol];
          const px = row && (row.price || row.regularMarketPrice || row.c || (typeof row === 'number' ? row : null));
          if (px) { i.currentPrice = +px; i.marketValue = Math.round(+px * (+i.shares || 0) * 100) / 100; hit++; }
        });
      });
      setStamp(new Date());
      setNote(hit ? { ok:true, msg:`Updated ${hit} holding${hit > 1 ? 's' : ''} from the market.` }
                  : { ok:false, msg:'Quote service returned no matches — prices unchanged.' });
    }).catch(() => {
      setNote({ ok:false, msg:'Live prices aren’t connected in this preview. Wired server-side on deploy.' });
    }).finally(() => setBusy(false));
  }

  // "+ Add Investment" opens the source picker; cm:add-holding still opens the manual modal directly.
  React.useEffect(() => {
    const openPick = () => { setImpErr(null); setSrcPick(true); };
    const openManual = () => setHoldModal({ idx: -1 });
    window.addEventListener('cm:add-investment', openPick);
    window.addEventListener('cm:add-holding', openManual);
    return () => { window.removeEventListener('cm:add-investment', openPick); window.removeEventListener('cm:add-holding', openManual); };
  }, []);

  // Statement import (shared by the source picker): parse → preview. Merge happens on confirm.
  const importFromFile = (file) => {
    setImpBusy(true); setImpErr(null);
    const reader = new FileReader();
    reader.onerror = () => { setImpBusy(false); setImpErr('Could not read that file.'); };
    reader.onload = () => {
      const b64 = String(reader.result).split(',')[1] || '';
      fetch((window.CM_API_BASE || '/api') + '/parse-statement', { method:'POST', headers:{ 'Content-Type':'application/json' }, body:JSON.stringify({ pdf:b64 }) })
        .then(r => r.json()).then(d => {
          const hasAccts = d && Array.isArray(d.subAccounts) && d.subAccounts.length;
          if (!hasAccts && !(d && d.docType === 'insurance')) {
            setImpErr((d && d.issuer && d.issuer !== 'Unknown') ? (d.issuer + ' document read, but no investment accounts were found.') : ((d && d.warnings && d.warnings[0]) || 'No investment accounts found in that document.'));
            return;
          }
          setSrcPick(false); setImportPrev(d);
        })
        .catch(() => setImpErr('Could not read that statement — try a PDF export from your provider.'))
        .finally(() => setImpBusy(false));
    };
    reader.readAsDataURL(file);
  };

  // The unified "Add Investment" flow (source picker → import preview / connect / manual). Rendered in
  // both the empty state and the populated page so "+ Add Investment" works either way.
  const addFlows = (
    <>
      {srcPick && <SourcePickerModal busy={impBusy} error={impErr}
        onManual={() => { setSrcPick(false); setHoldModal({ idx:-1 }); }}
        onConnect={() => { setSrcPick(false); setConnectOpen(true); }}
        onFile={importFromFile} onClose={() => setSrcPick(false)}/>}
      {importPrev && <ImportPreviewModal data={importPrev} CM={CM} onClose={() => setImportPrev(null)}/>}
      {connectOpen && <ConnectAccountModal CM={CM} onClose={() => setConnectOpen(false)}/>}
    </>
  );

  if (!invs.length) {
    return (
      <>
      {addFlows}
      <ChartCard>
        <div style={{ textAlign:'center', padding:'60px 0' }}>
          <div style={{ color:G, marginBottom:14 }}><ANI.pie size={40}/></div>
          <div className="title" style={{ fontSize:18 }}>No holdings yet</div>
          <div className="t-caption" style={{ marginTop:6 }}>Add an investment or import a brokerage statement to see your portfolio.</div>
          <button className="cm-btn cm-btn-primary" style={{ marginTop:18 }} onClick={() => window.addInvestmentPrompt && window.addInvestmentPrompt()}>+ Add Investment</button>
        </div>
      </ChartCard>
      <InvestmentAccountsCard CM={CM} fmt={fmt} displayCcy={displayCcy} usdToCad={usdToCad}/>
      {holdModal && <AddHoldingModal CM={CM} idx={holdModal.idx} holding={holdModal.idx >= 0 ? invs[holdModal.idx] : null} onClose={() => setHoldModal(null)}/>}
      </>
    );
  }

  const gp = D.gainPct, gpStr = (gp >= 0 ? '+' : '') + gp.toFixed(2) + '%';
  const KPIS = [
    { feat:true, icon:<ANI.pie size={15}/>, label:'Total Portfolio Value', value:fmt2(D.totalMV),
      delta:{ v:gpStr, sub:` · ${D.totalGain >= 0 ? '+' : '-'}${fmt(Math.abs(D.totalGain)).replace('$', '$')}`, neg:gp < 0 }, foot:'',
      spark:ramp(D.totalCost * 0.92, D.totalMV, 9) },
    { icon:<ANI.trend size={15}/>, tint:'var(--mint-100)', col:G, label:'Total Gain / Loss',
      value:(D.totalGain >= 0 ? '+' : '-') + fmt2(Math.abs(D.totalGain)).replace('$', '$'),
      delta:{ v:gpStr, neg:gp < 0 }, foot:'unrealized', spark:ramp(0, Math.max(1, D.totalGain), 9) },
    { icon:<ANI.wallet size={15}/>, tint:'#E7EEFE', col:BL, label:'Invested Amount', value:fmt2(D.totalCost),
      foot:'total cost basis', spark:ramp(D.totalCost * 0.78, D.totalCost, 9) },
    { icon:<ANI.percent size={15}/>, tint:'#EDE7F9', col:PU, label:'Portfolio Return', value:gpStr,
      foot:'on cost basis', spark:ramp(0, Math.max(0.5, gp), 9) },
    { icon:<ANI.rocket size={15}/>, tint:'var(--mint-100)', col:G, label:'Best Performer',
      value:D.best.symbol, delta:{ v:(D.best.gainPct >= 0 ? '+' : '') + D.best.gainPct.toFixed(1) + '%', neg:D.best.gainPct < 0 },
      foot:'top mover', spark:ramp(D.best.cost * 0.9, D.best.mv, 9) },
    { icon:<ANI.layers size={15}/>, tint:'#FEF1DD', col:'#B5740F', label:'Holdings', value:String(D.rows.length),
      foot:`across ${D.brokers} account${D.brokers > 1 ? 's' : ''}`, spark:ramp(Math.max(1, D.rows.length - 2), D.rows.length, 9) },
  ];

  const perfVals = ramp(D.totalCost * 0.90, D.totalMV, 13);
  const perfInv  = ramp(D.totalCost * 0.76, D.totalCost, 13);
  const donutType = D.byType.map(t => ({
    color:t.color, label:t.label, value:fmt(t.value), pct:(t.value / D.totalMV * 100).toFixed(1) + '%', v:t.value,
  }));

  const brokerRows  = D.byBroker.map(b => ({ ini:b.label.replace(/[^A-Za-z]/g, '').slice(0, 2).toUpperCase() || 'BR', label:b.label, color:G,     value:b.value }));
  const accountRows = D.byAccount.map(a => ({ ini:(a.label === 'Non-Registered' ? 'NR' : a.label.slice(0, 4)), label:a.label, tag:REGISTERED.test(a.label) ? 'Registered' : null, color:a.color, value:a.value }));
  const ccyRows     = D.byCcy.map(c => ({ ini:c.label, label:c.label === 'CAD' ? 'Canadian Dollar' : c.label === 'USD' ? 'US Dollar' : c.label, color:c.color, value:c.value }));

  /* AI insights — computed from the real portfolio shape */
  const insights = [];
  if (D.topW >= 0.5) insights.push({ col:'#B5740F', bg:'#FEF1DD', icon:<ANI.alert size={16}/>, t:'Concentration is high',
    s:`${D.rows[0].symbol} is ${(D.topW * 100).toFixed(0)}% of your portfolio. Consider trimming to reduce single-name risk.` });
  else insights.push({ col:G, bg:'var(--mint-100)', icon:<ANI.trophy size={16}/>, t:'Reasonably diversified',
    s:`Your largest position (${D.rows[0].symbol}) is ${(D.topW * 100).toFixed(0)}% — risk is spread across ${D.rows.length} holdings.` });
  if (D.cryptoW >= 0.15) insights.push({ col:'#B5740F', bg:'#FEF1DD', icon:<ANI.flame size={16}/>, t:'Elevated crypto exposure',
    s:`Crypto is ${(D.cryptoW * 100).toFixed(0)}% of holdings — expect higher volatility than a typical balanced book.` });
  insights.push({ col:G, bg:'var(--mint-100)', icon:<ANI.shield size={16}/>, t:'Tax-sheltered share',
    s:`${(D.registeredW * 100).toFixed(0)}% of your portfolio sits in registered (TFSA/RRSP) accounts — keep growth assets here.` });
  if (D.best) insights.push({ col:G, bg:'var(--mint-100)', icon:<ANI.trend size={16}/>, t:`${D.best.symbol} is your top mover`,
    s:`Up ${D.best.gainPct.toFixed(1)}% on cost — ${fmt(D.best.gain)} of unrealized gain.` });
  const insights3 = insights.slice(0, 3);

  /* holdings table (Holdings tab) */
  const cols = [
    { header:'Holding', cell:r => <DataTable.Lead thumb={<Badge ini={r.symbol.slice(0, 4)} color={typeMeta(r.type).color}/>} primary={r.symbol} sub={r.name}/> },
    { header:'Type', cell:r => <span className="cm-cell-meta">{typeMeta(r.type).label}</span> },
    { header:'Account', cell:r => <DataTable.Stack strong={r.account} meta={r.broker}/> },
    { header:'Shares', cell:r => <span className="cm-cell-meta num">{r.shares}</span> },
    { header:'Avg Cost', cell:r => <span className="cm-cell-meta num">{fmt2(r.avgCost)}</span> },
    { header:'Price', cell:r => <span className="cm-cell-strong num">{fmt2(r.price)}</span> },
    { header:'Market Value', cell:r => <span className="cm-cell-strong num">{fmt(r.mv)}</span> },
    { header:'Gain / Loss', cell:r => <DataTable.Stack color={r.gain >= 0 ? 'var(--green-600)' : 'var(--red-500)'}
        strong={(r.gain >= 0 ? '+' : '-') + fmt(Math.abs(r.gain)).replace('$', '$')} meta={(r.gain >= 0 ? '+' : '') + r.gainPct.toFixed(1) + '%'}/> },
    { header:'', align:'right', cell:r => (
      <div style={{ display:'flex', gap:6, justifyContent:'flex-end' }}>
        {r.fromStatement && <span title="Imported from a statement" style={{ alignSelf:'center', fontSize:9.5, fontWeight:800, letterSpacing:'.04em', color:'var(--ink-400)', border:'1px solid var(--line)', borderRadius:6, padding:'2px 6px', marginRight:2 }}>STMT</span>}
        <button title="Edit" onClick={() => setHoldModal({ idx:r._i })} style={ICON_BTN}><IcEdit/></button>
        <button title={r.excluded ? 'Include in totals' : 'Exclude from totals'} onClick={() => toggleExclude(r._i)} style={ICON_BTN}>{r.excluded ? <IcEyeOff/> : <IcEye/>}</button>
        <button title="Delete" onClick={() => setDelHold({ idx:r._i, name:r.name })} style={ICON_BTN_DANGER}><IcTrash/></button>
      </div>) },
  ];

  /* Holdings tab — filter / search / sort options (ported from reference build) */
  const acctOpts = [...new Set(D.rows.map(r => r.broker))];
  const ccyOpts  = [...new Set(D.rows.map(r => r.currency))];
  const hRows = D.rows.filter(r => {
    if (hType && r.type !== hType) return false;
    if (hAcct && r.broker !== hAcct) return false;
    if (hCcy && r.currency !== hCcy) return false;
    if (hq) { const s = hq.toLowerCase(); if (((r.symbol || '') + ' ' + (r.name || '')).toLowerCase().indexOf(s) < 0) return false; }
    return true;
  }).sort((a, b) => hSort === 'gain' ? b.gainPct - a.gainPct : hSort === 'name' ? a.symbol.localeCompare(b.symbol) : b.mv - a.mv);

  const asOf = stamp instanceof Date ? `Updated ${stamp.toLocaleTimeString('en-CA', { hour:'numeric', minute:'2-digit' })}` : `As of ${CM.FD(new Date())}`;

  /* ─── shared header (tabs + live controls) ─── */
  const Tabs = (
    <div className="inv-tabbar">
      <div className="inv-tabs">
        {tabs.map(t => <div key={t} className={`inv-tab ${t === tab ? 'active' : ''}`} onClick={() => setTab(t)}>{t}</div>)}
      </div>
      <div className="inv-tabctrls">
        <div className="cm-seg" title="Display currency">{['CAD', 'USD'].map(c => <button key={c} className={c === displayCcy ? 'active' : ''} onClick={() => setDisplayCcy(c)}>{c}</button>)}</div>
        <span className="st-control"><ANI.cal size={15}/>{asOf}</span>
        <button className="cm-btn cm-btn-primary" disabled={busy} onClick={refreshPrices}
          style={{ display:'inline-flex', alignItems:'center', gap:7, opacity:busy ? .7 : 1 }}>
          <span style={{ display:'inline-flex', animation:busy ? 'cm-spin 0.9s linear infinite' : 'none' }}><ANI.refresh size={15}/></span>
          {busy ? 'Refreshing…' : 'Refresh prices'}
        </button>
      </div>
    </div>
  );

  const NoteBar = note && (
    <div className="cm-mb-24" style={{ display:'flex', alignItems:'center', gap:10, padding:'11px 16px', borderRadius:'var(--r-ctrl)',
      background:note.ok ? 'var(--mint-50)' : '#FBF3E8', color:note.ok ? 'var(--green-600)' : '#A9772A',
      border:`1px solid ${note.ok ? 'var(--mint-100)' : 'var(--gold-soft)'}`, fontWeight:700, fontSize:13 }}>
      {note.ok ? <ANI.check size={16}/> : <ANI.info size={16}/>}{note.msg}
    </div>
  );

  return (
    <div>
      {Tabs}
      {NoteBar}
      {addFlows}
      {holdModal && <AddHoldingModal CM={CM} idx={holdModal.idx} holding={holdModal.idx >= 0 ? invs[holdModal.idx] : null} onClose={() => setHoldModal(null)}/>}
      {delHold && <ConfirmModal title="Remove holding?" message={`Remove "${delHold.name}" from your portfolio? This can't be undone.`} confirmLabel="Delete"
        onConfirm={() => { CM.mutate(() => (CM.S.investments || []).splice(delHold.idx, 1)); setDelHold(null); }} onClose={() => setDelHold(null)}/>}

      {tab === 'Portfolio' && <>
        <div className="an-exec cm-mb-24">{KPIS.map((k, i) => <KpiTile key={i} {...k}/>)}</div>
        <InvestmentAccountsCard CM={CM} fmt={fmt} displayCcy={displayCcy} usdToCad={usdToCad}/>

        <div className="cm-grid-2a cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:4 }}>
              <div><div className="title">Portfolio Performance</div><div className="st-mini-sub">Value vs invested cost · last 12 months</div></div>
              <div className="cm-seg">{['1M', '3M', '6M', 'YTD', '1Y'].map(t => <button key={t} className={t === '1Y' ? 'active' : ''}>{t}</button>)}</div>
            </div>
            <div className="an-legend" style={{ margin:'12px 0 6px' }}>
              <span style={{ color:G }}><i style={{ background:G }}/>Portfolio Value</span>
              <span style={{ color:BL }}><i className="dash" style={{ color:BL }}/>Invested Cost</span>
            </div>
            <PerfChart vals={perfVals} inv={perfInv} fmt={fmt} height={300}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:14 }}><div className="title">Asset Allocation</div></div>
            <div style={{ display:'flex', justifyContent:'center', marginBottom:16 }}>
              <DonutChart size={172} thickness={24} segments={donutType.map(a => ({ value:a.v, color:a.color }))}
                center={{ value:fmt(D.totalMV), label:'Total Value' }}/>
            </div>
            <Legend items={donutType}/>
            <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginTop:16, paddingTop:14, borderTop:'1px solid var(--line)' }}>
              <span className="cm-row" style={{ gap:8, fontSize:13, fontWeight:700, color:'var(--ink-700)' }}>
                <span style={{ color:GOLD }}><ANI.target size={16}/></span>{D.byType.length} asset classes</span>
              <ViewLink onClick={() => setTab('Analysis')}>Analyze</ViewLink>
            </div>
          </ChartCard>
        </div>

        <div className="cm-grid-3 cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}>
              <div><div className="title">Holdings by Account</div><div className="st-mini-sub">By brokerage</div></div>
              <ViewLink onClick={() => setTab('Holdings')}>View all</ViewLink>
            </div>
            <BarList rows={brokerRows} total={D.totalMV} fmt={fmt}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}>
              <div><div className="title">Account Type</div><div className="st-mini-sub">Registered vs non-registered</div></div>
            </div>
            <BarList rows={accountRows} total={D.totalMV} fmt={fmt}/>
            <div style={{ marginTop:14, padding:'10px 14px', borderRadius:'var(--r-ctrl)', background:'var(--mint-50)',
              border:'1px solid var(--mint-100)', display:'flex', alignItems:'center', gap:8, color:'var(--green-600)', fontWeight:700, fontSize:12.5 }}>
              <ANI.shield size={15}/>{(D.registeredW * 100).toFixed(0)}% tax-sheltered
            </div>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}>
              <div><div className="title">Currency Exposure</div><div className="st-mini-sub">By holding value</div></div>
            </div>
            <BarList rows={ccyRows} total={D.totalMV} fmt={fmt}/>
          </ChartCard>
        </div>

        <div className="cm-grid-3 cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}>
              <div><div className="title">Top Holdings</div><div className="st-mini-sub">% of portfolio</div></div>
              <ViewLink onClick={() => setTab('Holdings')}>View all</ViewLink>
            </div>
            <div>
              {D.rows.slice(0, 5).map((h, i) => (
                <div className="inv-row" key={i}>
                  <Badge ini={h.symbol.slice(0, 4)} color={typeMeta(h.type).color}/>
                  <div className="inv-row-main"><div className="inv-row-name">{h.name}</div><div className="inv-row-sub">{h.symbol} · {h.account}</div></div>
                  <div className="inv-row-val">
                    <div className="v num">{(h.weight * 100).toFixed(1)}%</div>
                    <div className="p num" style={{ color:h.gain >= 0 ? G : RD, display:'inline-flex', alignItems:'center', gap:2 }}>
                      {h.gain >= 0 ? <ANI.arrUp size={11}/> : <ANI.arrDn size={11}/>}{Math.abs(h.gainPct).toFixed(1)}%</div>
                  </div>
                </div>
              ))}
            </div>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:8 }}><div className="title">Gain / Loss by Holding</div><div className="st-mini-sub">% on cost</div></div>
            <GainBars rows={D.movers}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:6 }}>
              <div className="cm-row" style={{ gap:8 }}><div className="title">AI Investment Insights</div>
                <span className="cm-pill cm-pill-green" style={{ fontSize:10 }}>Live</span></div>
            </div>
            <div style={{ display:'flex', flexDirection:'column', gap:12 }}>
              {insights3.map((it, i) => (
                <div key={i} style={{ display:'flex', gap:11, padding:'12px 13px', borderRadius:'var(--r-ctrl)', background:'var(--mint-50)', border:'1px solid var(--line)' }}>
                  <span style={{ width:30, height:30, flex:'0 0 30px', borderRadius:9, background:it.bg, color:it.col, display:'flex', alignItems:'center', justifyContent:'center' }}>{it.icon}</span>
                  <div><div style={{ fontSize:13, fontWeight:800, color:'var(--ink-900)' }}>{it.t}</div>
                    <div style={{ fontSize:12, fontWeight:600, color:'var(--ink-500)', marginTop:3, lineHeight:1.45 }}>{it.s}</div></div>
                </div>
              ))}
            </div>
          </ChartCard>
        </div>
      </>}

      {tab === 'Holdings' && <>
        <div className="cm-grid-4 cm-mb-24">
          <div className="cm-ministat"><div className="v num">{fmt(D.totalMV)}</div><div className="k">Market value · {D.rows.length} holdings</div></div>
          <div className="cm-ministat"><div className="v num">{fmt(D.totalCost)}</div><div className="k">Total invested</div></div>
          <div className="cm-ministat"><div className="v num" style={{ color:D.totalGain >= 0 ? G : RD }}>{(D.totalGain >= 0 ? '+' : '-') + fmt(Math.abs(D.totalGain)).replace('$', '$')}</div><div className="k">Unrealized gain/loss</div></div>
          <div className="cm-ministat"><div className="v num">{D.brokers}</div><div className="k">Brokerage accounts</div></div>
        </div>
        <ChartCard title="All Holdings"
          action={<button className="cm-btn cm-btn-soft" disabled={busy} onClick={refreshPrices} style={{ display:'inline-flex', alignItems:'center', gap:7 }}>
            <span style={{ display:'inline-flex', animation:busy ? 'cm-spin 0.9s linear infinite' : 'none' }}><ANI.refresh size={14}/></span>{busy ? 'Refreshing…' : 'Refresh prices'}</button>}>
          <div style={{ display:'flex', gap:10, alignItems:'center', marginBottom:14, flexWrap:'wrap' }}>
            <div className="st-searchbox" style={{ maxWidth:240, flex:'1 1 170px' }}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
              <input placeholder="Search symbol or name…" value={hq} onChange={e => setHq(e.target.value)}/>
            </div>
            <select className="cm-select" value={hType} onChange={e => setHType(e.target.value)} aria-label="Type">
              <option value="">All Types</option><option value="stock">Stocks</option><option value="etf">ETFs</option><option value="mutual_fund">Mutual Funds</option><option value="crypto">Crypto</option><option value="bond">Bonds</option><option value="real_estate">REITs</option>
            </select>
            <select className="cm-select" value={hAcct} onChange={e => setHAcct(e.target.value)} aria-label="Account"><option value="">All Accounts</option>{acctOpts.map(a => <option key={a} value={a}>{a}</option>)}</select>
            <select className="cm-select" value={hCcy} onChange={e => setHCcy(e.target.value)} aria-label="Currency"><option value="">All Currencies</option>{ccyOpts.map(c => <option key={c} value={c}>{c}</option>)}</select>
            <select className="cm-select" value={hSort} onChange={e => setHSort(e.target.value)} aria-label="Sort"><option value="value">Sort: Value</option><option value="gain">Sort: Return %</option><option value="name">Sort: Symbol A–Z</option></select>
            <span className="t-caption" style={{ marginLeft:'auto' }}>{hRows.length} of {D.rows.length}</span>
          </div>
          {hRows.length ? <DataTable columns={cols} rows={hRows} minWidth={1040} rowStyle={r => r.excluded ? { opacity:.45 } : undefined}/> : <div className="t-caption" style={{ textAlign:'center', padding:'34px 0' }}>No holdings match your filters.</div>}
        </ChartCard>
      </>}

      {tab === 'Performance' && <>
        <ChartCard className="cm-mb-24">
          <div className="cm-card-head" style={{ marginBottom:4 }}>
            <div><div className="title">Portfolio Performance</div><div className="st-mini-sub">Value vs invested cost · last 12 months</div></div>
            <div className="cm-seg">{['1M', '3M', '6M', 'YTD', '1Y'].map(t => <button key={t} className={t === '1Y' ? 'active' : ''}>{t}</button>)}</div>
          </div>
          <div className="an-legend" style={{ margin:'12px 0 6px' }}>
            <span style={{ color:G }}><i style={{ background:G }}/>Portfolio Value</span>
            <span style={{ color:BL }}><i className="dash" style={{ color:BL }}/>Invested Cost</span>
          </div>
          <PerfChart vals={perfVals} inv={perfInv} fmt={fmt} height={300}/>
        </ChartCard>
        <div className="cm-grid-2a cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:6 }}><div className="title">Risk & Concentration</div></div>
            <div className="st-mini-sub" style={{ marginBottom:6 }}>Concentration score (single-name + crypto weighting)</div>
            <div style={{ display:'flex', alignItems:'center', gap:8 }}>
              <div style={{ flex:'0 0 auto' }}>
                <div className="num" style={{ fontFamily:'var(--font-serif)', fontSize:46, fontWeight:700, color:'var(--ink-900)', lineHeight:1 }}>{D.risk}</div>
                <div style={{ fontSize:13, fontWeight:800, color:D.riskBand[1], marginTop:2 }}>{D.riskBand[0]}</div>
              </div>
              <div style={{ flex:1 }}><RiskGauge score={D.risk} size={220}/></div>
            </div>
            <div style={{ marginTop:6 }}>
              {[
                ['Largest position', `${D.rows[0].symbol} · ${(D.topW * 100).toFixed(1)}%`],
                ['Top 3 concentration', `${(D.top3W * 100).toFixed(1)}%`],
                ['Crypto allocation', `${(D.cryptoW * 100).toFixed(1)}%`],
                ['Registered (tax-sheltered)', `${(D.registeredW * 100).toFixed(1)}%`],
                ['Unrealized return', `${(D.gainPct >= 0 ? '+' : '') + D.gainPct.toFixed(1)}%`],
              ].map(([k, v], i) => (
                <div key={i} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'8.5px 0', borderBottom:i < 4 ? '1px solid var(--line)' : '0' }}>
                  <span className="cm-row" style={{ gap:8, fontSize:12.5, fontWeight:600, color:'var(--ink-500)' }}>
                    <span style={{ color:GOLD, display:'inline-flex' }}><ANI.gauge size={14}/></span>{k}</span>
                  <span className="num" style={{ fontWeight:800, color:'var(--ink-900)', fontSize:13 }}>{v}</span>
                </div>
              ))}
            </div>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:14 }}><div className="title">Allocation by Asset Class</div></div>
            <div style={{ display:'flex', justifyContent:'center', marginBottom:16 }}>
              <DonutChart size={172} thickness={28} segments={donutType.map(a => ({ value:a.v, color:a.color }))}
                center={{ value:fmt(D.totalMV), label:'Total Value' }}/>
            </div>
            <Legend items={donutType}/>
          </ChartCard>
        </div>

        <div className="cm-grid-2a cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:8 }}><div className="title">Gain / Loss by Holding</div><div className="st-mini-sub">% on cost</div></div>
            <GainBars rows={D.movers}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:8 }}>
              <div className="cm-row" style={{ gap:8 }}><div className="title">AI Investment Insights</div>
                <span className="cm-pill cm-pill-green" style={{ fontSize:10 }}>Live</span></div>
            </div>
            <div style={{ display:'flex', flexDirection:'column', gap:12 }}>
              {insights.map((it, i) => (
                <div key={i} style={{ display:'flex', gap:11, padding:'12px 13px', borderRadius:'var(--r-ctrl)', background:'var(--mint-50)', border:'1px solid var(--line)' }}>
                  <span style={{ width:30, height:30, flex:'0 0 30px', borderRadius:9, background:it.bg, color:it.col, display:'flex', alignItems:'center', justifyContent:'center' }}>{it.icon}</span>
                  <div><div style={{ fontSize:13, fontWeight:800, color:'var(--ink-900)' }}>{it.t}</div>
                    <div style={{ fontSize:12, fontWeight:600, color:'var(--ink-500)', marginTop:3, lineHeight:1.45 }}>{it.s}</div></div>
                </div>
              ))}
            </div>
          </ChartCard>
        </div>
      </>}

      {tab === 'Allocation' && <>
        <div className="cm-grid-2a cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:14 }}><div><div className="title">Asset Allocation</div><div className="st-mini-sub">By asset class</div></div></div>
            <div style={{ display:'flex', justifyContent:'center', marginBottom:16 }}>
              <DonutChart size={188} thickness={28} segments={donutType.map(a => ({ value:a.v, color:a.color }))} center={{ value:fmt(D.totalMV), label:'Total Value' }}/>
            </div>
            <Legend items={donutType}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}><div><div className="title">Holdings by Account</div><div className="st-mini-sub">By brokerage</div></div></div>
            <BarList rows={brokerRows} total={D.totalMV} fmt={fmt}/>
            <div style={{ marginTop:14, padding:'10px 14px', borderRadius:'var(--r-ctrl)', background:'var(--mint-50)', border:'1px solid var(--mint-100)', display:'flex', alignItems:'center', gap:8, color:'var(--green-600)', fontWeight:700, fontSize:12.5 }}>
              <ANI.shield size={15}/>{(D.registeredW * 100).toFixed(0)}% tax-sheltered
            </div>
          </ChartCard>
        </div>
        <div className="cm-grid-2a cm-mb-24">
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}><div><div className="title">Account Type</div><div className="st-mini-sub">Registered vs non-registered</div></div></div>
            <BarList rows={accountRows} total={D.totalMV} fmt={fmt}/>
          </ChartCard>
          <ChartCard>
            <div className="cm-card-head" style={{ marginBottom:2 }}><div><div className="title">Currency Exposure</div><div className="st-mini-sub">By holding value</div></div></div>
            <BarList rows={ccyRows} total={D.totalMV} fmt={fmt}/>
          </ChartCard>
        </div>
      </>}

      {tab === 'Market Context' && <MarketContext invs={invs} fmt={fmt} D={D}/>}

      {/* footer feature band */}
      <ChartCard className="cm-card-dark" pad={false}>
        <div className="inv-foot">
          {FOOT.map((f, i) => (
            <div className="inv-foot-item" key={i}>
              <span className="inv-foot-ic">{f.icon}</span>
              <div><div className="inv-foot-t">{f.t}</div><div className="inv-foot-s">{f.s}</div></div>
            </div>
          ))}
        </div>
      </ChartCard>
    </div>
  );
}
/* ───────────────── Market Context tab ─────────────────
   Live macro context for the portfolio: GoC yields, CAD/USD FX, plain-English drivers, and
   holding-aware market news. Lazy-loaded on first open, cached in MC_CACHE for the session. */
let MC_CACHE = null;

function mcRelTime(iso) {
  const t = Date.parse(iso || ''); if (!t) return '';
  const s = Math.max(0, (Date.now() - t) / 1000);
  if (s < 3600) return Math.round(s / 60) + 'm ago';
  if (s < 86400) return Math.round(s / 3600) + 'h ago';
  return Math.round(s / 86400) + 'd ago';
}
const MC_SENT = {
  bullish: { bg:'var(--mint-100)', col:'var(--green-600)', label:'Bullish' },
  bearish: { bg:'#FBE9E7',         col:'#C0392B',          label:'Bearish' },
  neutral: { bg:'#EEF1EF',         col:'var(--ink-500)',   label:'Neutral' },
};

function McSkeleton({ h = 120 }) {
  return <div style={{ height:h, borderRadius:'var(--r-ctrl)', background:'var(--mint-50)', border:'1px solid var(--line)' }}/>;
}

/* Small SVG yield curve (2Y/5Y/10Y/30Y) in the file's existing line-chart style. */
function McYieldCurve({ data }) {
  const pts = data.filter(d => d && isFinite(d.value));
  if (pts.length < 2) return null;
  const W = 320, H = 132, padX = 30, padY = 26;
  const vals = pts.map(p => p.value);
  const min = Math.min(...vals), max = Math.max(...vals), span = (max - min) || 1;
  const x = i => padX + i * ((W - padX * 2) / (pts.length - 1));
  const y = v => H - padY - ((v - min) / span) * (H - padY * 2);
  const line = pts.map((p, i) => `${i ? 'L' : 'M'}${x(i).toFixed(1)} ${y(p.value).toFixed(1)}`).join(' ');
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} style={{ display:'block' }}>
      <path d={line} fill="none" stroke="var(--green-500)" strokeWidth="2.4" strokeLinejoin="round" strokeLinecap="round"/>
      {pts.map((p, i) => (
        <g key={i}>
          <circle cx={x(i)} cy={y(p.value)} r="3.6" fill="var(--green-600)"/>
          <text x={x(i)} y={y(p.value) - 9} textAnchor="middle" fontSize="10.5" fontWeight="700" fill="var(--ink-700)">{p.value.toFixed(2)}%</text>
          <text x={x(i)} y={H - 6} textAnchor="middle" fontSize="10.5" fill="var(--ink-400)">{p.label}</text>
        </g>
      ))}
    </svg>
  );
}

function McExplain({ children }) {
  return <div style={{ marginTop:12, fontSize:12.5, color:'var(--ink-500)', lineHeight:1.5, fontWeight:600 }}>{children}</div>;
}

function McNewsList({ articles }) {
  const [n, setN] = React.useState(8);
  return (
    <div>
      {articles.slice(0, n).map((a, i) => {
        const s = MC_SENT[a.sentiment] || MC_SENT.neutral;
        return (
          <a key={i} href={a.url} target="_blank" rel="noopener noreferrer"
            style={{ display:'flex', gap:12, padding:'13px 4px', borderBottom:'1px solid var(--line)', textDecoration:'none' }}>
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontSize:13.5, fontWeight:700, color:'var(--ink-900)', lineHeight:1.4 }}>{a.title}</div>
              <div style={{ fontSize:11.5, color:'var(--ink-400)', fontWeight:600, marginTop:4 }}>{a.source}{a.publishedAt ? (' · ' + mcRelTime(a.publishedAt)) : ''}</div>
            </div>
            <span style={{ flex:'0 0 auto', alignSelf:'flex-start', fontSize:10.5, fontWeight:800, padding:'3px 9px', borderRadius:999, background:s.bg, color:s.col }}>{s.label}</span>
          </a>
        );
      })}
      {n < articles.length && (
        <div style={{ textAlign:'center', marginTop:12 }}>
          <button className="cm-btn cm-btn-ghost" onClick={() => setN(n + 8)}>Load more</button>
        </div>
      )}
    </div>
  );
}

function MarketContext({ invs }) {
  const [st, setSt] = React.useState(MC_CACHE || { loading:true });

  React.useEffect(() => {
    if (MC_CACHE) { setSt(MC_CACHE); return; }
    let alive = true;
    const tickers = (invs || []).map(i => i.symbol).filter(Boolean).slice(0, 10).join(',');
    const getJSON = u => fetch(u).then(r => r.json()).catch(() => null);
    Promise.all([
      getJSON('/api/yields'),
      getJSON('/api/fx?base=USD&quote=CAD'),
      getJSON('/api/news' + (tickers ? ('?tickers=' + encodeURIComponent(tickers)) : '')),
    ]).then(([yields, fx, news]) => {
      if (!alive) return;
      const out = { loading:false, yields, fx, news };
      MC_CACHE = out; setSt(out);
    }).catch(() => { if (alive) setSt({ loading:false }); });
    return () => { alive = false; };
  }, []);

  const yData = (st.yields && st.yields.data)
    ? ['2Y', '5Y', '10Y', '30Y', 'Long'].map(k => {
        const o = st.yields.data[k];
        return o && o.value != null ? { label: k === 'Long' ? '30Y' : k, value:+o.value } : null;
      }).filter(Boolean)
    : [];
  const y10 = yData.find(d => d.label === '10Y');
  const fxRate = st.fx && st.fx.rate;
  const fxAsOf = st.fx && st.fx.ts ? new Date(st.fx.ts).toISOString().slice(0, 10) : null;
  const articles = (st.news && Array.isArray(st.news.articles)) ? st.news.articles : [];

  return (
    <div>
      <div className="cm-grid-2a cm-mb-24">
        <ChartCard>
          <div className="cm-card-head" style={{ marginBottom:8 }}>
            <div><div className="title">Government of Canada Bond Yields</div><div className="st-mini-sub">{st.yields && st.yields.asOf ? ('Benchmark curve · ' + st.yields.asOf) : 'Benchmark curve'}</div></div>
          </div>
          {st.loading ? <McSkeleton/> : yData.length
            ? <McYieldCurve data={yData}/>
            : <div style={{ padding:'22px 0', color:'var(--ink-400)', fontSize:13, fontWeight:600 }}>Yield data unavailable right now.</div>}
          <McExplain>Rising government yields make bonds more attractive and can pressure stock valuations.</McExplain>
        </ChartCard>

        <ChartCard>
          <div className="cm-card-head" style={{ marginBottom:8 }}>
            <div><div className="title">USD → CAD Exchange Rate</div><div className="st-mini-sub">{fxAsOf ? ('As of ' + fxAsOf) : 'Live rate'}</div></div>
          </div>
          {st.loading ? <McSkeleton/> : fxRate
            ? <div style={{ display:'flex', alignItems:'baseline', gap:10, padding:'18px 0' }}>
                <div className="num" style={{ fontSize:42, fontWeight:800, color:'var(--ink-900)' }}>{(+fxRate).toFixed(4)}</div>
                <div style={{ fontSize:14, fontWeight:700, color:'var(--ink-500)' }}>CAD per 1 USD</div>
              </div>
            : <div style={{ padding:'22px 0', color:'var(--ink-400)', fontSize:13, fontWeight:600 }}>Exchange rate unavailable right now.</div>}
          <McExplain>A weaker Canadian dollar increases the CAD value of your US holdings, and vice versa.</McExplain>
        </ChartCard>
      </div>

      <div className="cm-card-head" style={{ marginBottom:14 }}><div className="title">What's Moving Markets</div></div>
      <div className="cm-grid-3 cm-mb-24">
        {[
          { ic:<ANI.trend size={16}/>, t:'Interest Rates', s: y10
              ? `Canada's 10-year yield is ${y10.value.toFixed(2)}%. Higher rates lift borrowing costs and tend to weigh on stock and bond prices.`
              : 'Central-bank rates set borrowing costs; when they rise, stock and bond prices usually come under pressure.' },
          { ic:<ANI.info size={16}/>, t:'Inflation', s:'When prices rise faster than expected, central banks keep rates higher for longer — a headwind for richly-valued growth stocks.' },
          { ic:<ANI.refresh size={16}/>, t:'Currency', s: fxRate
              ? `At ${(+fxRate).toFixed(4)} CAD/USD, every US dollar of holdings converts to about $${(+fxRate).toFixed(2)} CAD.`
              : 'A weaker loonie lifts the CAD value of your US holdings; a stronger loonie trims it.' },
        ].map((c, i) => (
          <ChartCard key={i}>
            <div className="cm-row" style={{ gap:9, marginBottom:8 }}>
              <span style={{ width:30, height:30, flex:'0 0 30px', borderRadius:9, background:'var(--mint-50)', color:'var(--green-600)', display:'flex', alignItems:'center', justifyContent:'center' }}>{c.ic}</span>
              <div className="title" style={{ fontSize:15 }}>{c.t}</div>
            </div>
            <div style={{ fontSize:12.5, color:'var(--ink-500)', fontWeight:600, lineHeight:1.5 }}>{c.s}</div>
          </ChartCard>
        ))}
      </div>

      <ChartCard>
        <div className="cm-card-head" style={{ marginBottom:10 }}>
          <div><div className="title">Market News</div><div className="st-mini-sub">{articles.length ? ('Latest headlines' + (st.news && st.news.source ? (' · ' + st.news.source) : '')) : 'Headlines for your holdings'}</div></div>
        </div>
        {st.loading
          ? <div style={{ display:'flex', flexDirection:'column', gap:10 }}><McSkeleton h={56}/><McSkeleton h={56}/><McSkeleton h={56}/></div>
          : articles.length
            ? <McNewsList articles={articles}/>
            : <div style={{ padding:'24px 0', textAlign:'center', color:'var(--ink-400)', fontSize:13, fontWeight:600 }}>Market news unavailable right now.</div>}
      </ChartCard>
    </div>
  );
}

/* ───────────────── Add / Edit Holding modal ─────────────────
   Inline mint modal (matches the CardModal pattern) — replaces the old native prompt() flow.
   Ticker blank → isFund:true (live quote/news are skipped for it). Value auto-calcs units×price
   but stays editable. `account` = register type, `broker` = account/source (matches derive()). */
const HOLD_TYPES = [['stock', 'Stock'], ['etf', 'ETF'], ['mutual_fund', 'Mutual Fund'], ['bond', 'Bond'], ['crypto', 'Crypto'], ['cash', 'Cash'], ['other', 'Other']];
const HOLD_SOURCES = ['Wealthsimple', 'Questrade', 'Sun Life', 'Manulife', 'Moomoo', 'TD', 'RBC', 'BMO', 'Scotiabank', 'CIBC', 'National Bank'];
const HOLD_REGISTERS = ['Non-registered', 'RRSP', 'TFSA', 'FHSA', 'RESP', 'DPSP', 'Pension', 'LIRA'];

/* row-action icons + inline confirm modal (no native confirm) */
const ICON_BTN = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 28, height: 28, borderRadius: 8, border: '1px solid var(--line)', background: '#fff', color: 'var(--ink-500)', cursor: 'pointer', padding: 0 };
const ICON_BTN_DANGER = Object.assign({}, ICON_BTN, { color: RD });
function IcEdit() { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>; }
function IcEye() { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7Z"/><circle cx="12" cy="12" r="3"/></svg>; }
function IcEyeOff() { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9.9 4.2A11 11 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.6 3.3M6.6 6.6A18.5 18.5 0 0 0 1 12s4 8 11 8a10.9 10.9 0 0 0 5.4-1.4"/><line x1="2" y1="2" x2="22" y2="22"/></svg>; }
function IcTrash() { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m2 0-1 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1L5 6"/></svg>; }

function ConfirmModal({ title, message, confirmLabel, onConfirm, onClose }) {
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 1100, background: 'rgba(13,33,26,.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={(e) => e.stopPropagation()} className="cm-card" style={{ width: 420, maxWidth: '100%', borderRadius: 18, padding: '22px 24px', boxShadow: '0 24px 64px rgba(13,42,33,.28)' }}>
        <div className="t-h2" style={{ fontSize: 17, marginBottom: 6 }}>{title}</div>
        <div className="t-caption" style={{ marginBottom: 20, lineHeight: 1.5 }}>{message}</div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
          <button onClick={onClose} className="cm-btn cm-btn-ghost">Cancel</button>
          <button onClick={onConfirm} className="cm-btn" style={{ background: RD, color: '#fff', border: '1px solid ' + RD }}>{confirmLabel || 'Confirm'}</button>
        </div>
      </div>
    </div>
  );
}

/* ───────────────── Investment Accounts (imported from statements) ───────────────── */
const REG_COLORS_INV = { TFSA: G, RRSP: BL, RESP: AM, FHSA: PINK, LIRA: TEAL, RRIF: INDIGO, DPSP: PU, MBC: '#B5740F', Pension: PU };

function InvestmentAccountsCard({ CM, fmt, displayCcy, usdToCad }) {
  const accts = CM.S.investmentAccounts || [];
  const conv = (v, ccy) => fxConv(v, ccy, displayCcy || 'CAD', usdToCad || 0);
  const [busy, setBusy] = React.useState(false);
  const [note, setNote] = React.useState(null);
  const [open, setOpen] = React.useState({});
  const fileRef = React.useRef(null);

  const onFile = (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!file) return;
    setBusy(true); setNote(null);
    const reader = new FileReader();
    reader.onerror = () => { setBusy(false); setNote({ ok: false, msg: 'Could not read that file.' }); };
    reader.onload = () => {
      const b64 = String(reader.result).split(',')[1] || '';
      fetch((window.CM_API_BASE || '/api') + '/parse-statement', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pdf: b64 }) })
        .then(r => r.json()).then(d => {
          // Insurance document → store as a policy (Insurance tab reads S.insurance; premium monthly).
          if (d && d.docType === 'insurance' && d.insurance) {
            const ins = d.insurance;
            const cov = ins.type === 'auto'
              ? [ins.vehicle && ins.vehicle.description, (ins.deductibles && (ins.deductibles.collision || ins.deductibles.comprehensive)) ? ('Deductible $' + (ins.deductibles.collision || ins.deductibles.comprehensive)) : null, (ins.coverages || []).join(', ')].filter(Boolean).join(' · ')
              : ((ins.coverages || []).join(', ') || (ins.type + ' policy'));
            CM.mutate(() => {
              (CM.S.insurance = CM.S.insurance || []).push({
                name: ins.insurer + (ins.type === 'home' ? ' Home' : ins.type === 'auto' ? ' Auto' : '') + ' Insurance',
                type: ins.type, provider: ins.insurer,
                premium: ins.premium ? Math.round(ins.premium / 12 * 100) / 100 : 0, frequency: 'Monthly',
                renewal: ins.renewalDate || ins.expiryDate || null, coverage: 0, coverageText: cov, policyNum: ins.policyNumber, policy: ins.policyNumber,
                effectiveDate: ins.effectiveDate, expiryDate: ins.expiryDate, termPremium: ins.premium,
                vehicle: ins.vehicle, deductibles: ins.deductibles, coverages: ins.coverages, fromStatement: true,
              });
            });
            try { CM.logActivity && CM.logActivity('imported an insurance policy', ins.insurer + ' · ' + ins.type, { cat: 'insurance' }); } catch (e) {}
            setNote({ ok: true, msg: 'Imported a ' + ins.type + ' insurance policy from ' + ins.insurer + ' — see the Insurance tab.' });
            return;
          }
          const subs = (d && d.subAccounts) || [];
          if (!subs.length) { setNote({ ok: false, msg: (d && d.issuer && d.issuer !== 'Unknown') ? (d.issuer + ' statement read, but no investment accounts were found.') : ((d && d.warnings && d.warnings[0]) || 'No investment accounts found in that statement.') }); return; }
          const src = d.provider || d.issuer || 'Statement';
          CM.mutate(() => {
            let list = (CM.S.investmentAccounts || []).filter(a => a.source !== src); // re-import replaces same provider
            for (const sa of subs) list.push(normalizeAccount(sa, { inputMethod: 'statement', source: src, importedAt: Date.now() }));
            CM.S.investmentAccounts = list;
          });
          try { CM.logActivity && CM.logActivity('imported a statement', src + ' · ' + subs.length + ' accounts', { cat: 'investments' }); } catch (e) {}
          setNote({ ok: true, msg: 'Imported ' + subs.length + ' account' + (subs.length > 1 ? 's' : '') + ' from ' + (d.issuer || src) + '.' });
        })
        .catch(() => setNote({ ok: false, msg: 'Could not read that statement — try a PDF export from your provider.' }))
        .finally(() => setBusy(false));
    };
    reader.readAsDataURL(file);
  };
  const removeAcct = (i) => CM.mutate(() => (CM.S.investmentAccounts || []).splice(i, 1));
  const total = accts.reduce((s, a) => s + conv(+a.balance || 0, a.currency), 0);

  return (
    <ChartCard className="cm-mb-24">
      <div className="cm-card-head" style={{ marginBottom: 6 }}>
        <div><div className="title">Investment Accounts</div><div className="st-mini-sub">{accts.length ? (accts.length + ' account' + (accts.length > 1 ? 's' : '') + ' · ' + fmt(total)) : 'Import a brokerage or group-retirement statement'}</div></div>
        <button className="cm-btn cm-btn-soft" disabled={busy} onClick={() => fileRef.current && fileRef.current.click()} style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
          <span style={{ display: 'inline-flex', animation: busy ? 'cm-spin 0.9s linear infinite' : 'none' }}><ANI.refresh size={14}/></span>{busy ? 'Reading…' : 'Import statement'}
        </button>
        <input ref={fileRef} type="file" accept="application/pdf,.pdf" style={{ display: 'none' }} onChange={onFile}/>
      </div>
      {note && <div style={{ margin: '4px 0 12px', padding: '9px 13px', borderRadius: 'var(--r-ctrl)', fontWeight: 700, fontSize: 12.5, background: note.ok ? 'var(--mint-50)' : '#FBF3E8', color: note.ok ? 'var(--green-600)' : '#A9772A', border: '1px solid ' + (note.ok ? 'var(--mint-100)' : 'var(--gold-soft)') }}>{note.msg}</div>}
      {!accts.length
        ? <div style={{ padding: '26px 0', textAlign: 'center', color: 'var(--ink-400)', fontSize: 13, fontWeight: 600 }}>No imported accounts yet. Import a PDF statement (Sun Life, Questrade, Wealthsimple…) to pull in your plans &amp; holdings.</div>
        : accts.map((a, i) => {
            const reg = REG_COLORS_INV[a.planType] || SL;
            const isOpen = !!open[i];
            return (
              <div key={i} style={{ borderTop: '1px solid var(--line)', padding: '12px 0' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
                  <span style={{ fontSize: 10.5, fontWeight: 800, letterSpacing: '.04em', color: reg, background: reg + '18', borderRadius: 7, padding: '3px 8px', flex: '0 0 auto' }}>{a.planType}</span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13.5, fontWeight: 700, color: 'var(--ink-900)' }}>{a.planName}{a.fromStatement && <span title="Imported from a statement" style={{ fontSize: 9.5, fontWeight: 800, color: 'var(--ink-400)', border: '1px solid var(--line)', borderRadius: 6, padding: '1px 5px', marginLeft: 8 }}>STMT</span>}</div>
                    <div style={{ fontSize: 11.5, color: 'var(--ink-400)', fontWeight: 600 }}>{a.source} · {a.currency}{a.registered ? ' · Registered' : ''}{a.holdings && a.holdings.length ? ' · ' + a.holdings.length + ' holdings' : ''}</div>
                  </div>
                  <div className="num" style={{ fontWeight: 800, color: 'var(--ink-900)' }}>{fmt(conv(+a.balance || 0, a.currency))}</div>
                  {a.holdings && a.holdings.length ? <button onClick={() => setOpen(o => Object.assign({}, o, { [i]: !o[i] }))} style={ICON_BTN} title={isOpen ? 'Collapse' : 'Expand'}>{isOpen ? '−' : '+'}</button> : null}
                  <button onClick={() => removeAcct(i)} style={ICON_BTN_DANGER} title="Remove account"><IcTrash/></button>
                </div>
                {isOpen && a.holdings && a.holdings.length ? (
                  <div style={{ marginTop: 10, paddingLeft: 8 }}>
                    {a.holdings.map((h, j) => (
                      <div key={j} style={{ display: 'flex', justifyContent: 'space-between', gap: 12, padding: '5px 0', fontSize: 12.5, color: 'var(--ink-700)' }}>
                        <span style={{ flex: 1, minWidth: 0 }}>{h.fundName}{h.assetClass ? <span style={{ color: 'var(--ink-400)', marginLeft: 6, fontSize: 11 }}>· {h.assetClass}</span> : null}</span>
                        <span className="num" style={{ fontWeight: 700, flex: '0 0 auto' }}>{fmt(conv(+h.value || 0, h.currency || a.currency))}</span>
                      </div>
                    ))}
                    <div style={{ display: 'flex', justifyContent: 'space-between', paddingTop: 8, marginTop: 4, borderTop: '1px solid var(--line)', fontSize: 12.5, fontWeight: 800, color: 'var(--ink-900)' }}>
                      <span>Subtotal</span><span className="num">{fmt(a.holdings.reduce((s, h) => s + conv(+h.value || 0, h.currency || a.currency), 0))}</span>
                    </div>
                  </div>
                ) : null}
              </div>
            );
          })}
    </ChartCard>
  );
}

function AddHoldingModal({ CM, idx, holding, onClose }) {
  const adding = idx == null || idx < 0;
  const h = holding || {};
  const num = (v) => (v != null && v !== '' ? String(v) : '');
  const [f, setF] = React.useState(() => ({
    name: h.name || '',
    ticker: h.symbol && h.symbol !== '—' ? h.symbol : '',
    currency: (h.currency || 'CAD').toUpperCase(),
    type: h.type || 'stock',
    source: h.broker || 'Wealthsimple',
    register: h.account || 'Non-registered',
    units: num(h.shares),
    price: num(h.currentPrice != null ? h.currentPrice : h.avgCost),
    value: num(h.marketValue),
  }));
  const [valTouched, setValTouched] = React.useState(h.marketValue != null);
  const [err, setErr] = React.useState('');
  React.useEffect(() => {
    if (valTouched) return;
    const u = parseFloat(f.units), p = parseFloat(f.price);
    if (isNaN(u) || isNaN(p)) return;
    setF((s) => ({ ...s, value: String(Math.round(u * p * 100) / 100) }));
  }, [f.units, f.price]);
  const set = (k) => (e) => { if (k === 'value') setValTouched(true); setF((s) => ({ ...s, [k]: e.target.value })); };

  // Ticker auto-fill: on blur, look up the live price via /api/quote and fill it. Quote returns price
  // only (no currency/exchange), so currency stays a manual field. Not found → manual price + isFund.
  const [live, setLive] = React.useState(null); // null | 'loading' | 'ok' | 'miss'
  const onTickerBlur = () => {
    const t = (f.ticker || '').trim().toUpperCase();
    if (!t) { setLive(null); return; }
    setLive('loading');
    CM.api.quote(t).then((d) => {
      const row = ((d && d.quotes) || []).find((q) => q && String(q.symbol).toUpperCase() === t);
      const px = row && (row.price || row.regularMarketPrice || row.c);
      if (px) { setLive('ok'); setValTouched(false); setF((s) => ({ ...s, price: String(+px) })); }
      else { setLive('miss'); }
    }).catch(() => setLive('miss'));
  };

  const inp = { width: '100%', fontFamily: 'inherit', fontSize: 13.5, color: 'var(--ink-900)', border: '1px solid var(--line)', borderRadius: 10, padding: '9px 11px', background: '#fff', outline: 'none', boxSizing: 'border-box' };
  const lab = { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--ink-400)', margin: '0 0 5px' };
  const Field = ({ label, children, full }) => <div style={full ? { gridColumn: '1 / -1' } : undefined}><div style={lab}>{label}</div>{children}</div>;

  const save = () => {
    if (!f.name.trim()) { setErr('Investment name is required.'); return; }
    const ticker = (f.ticker || '').trim().toUpperCase();
    const units = parseFloat(f.units) || 0, price = parseFloat(f.price) || 0;
    const value = f.value !== '' ? (parseFloat(f.value) || 0) : Math.round(units * price * 100) / 100;
    // Converge through the shared normalizer so manual records match imported/integration ones.
    const rec = normalizeHolding({
      symbol: ticker, name: f.name.trim(), type: f.type,
      shares: units, avgCost: (h.avgCost != null ? +h.avgCost : price), currentPrice: price, marketValue: value,
      account: f.register, broker: f.source, currency: f.currency, isFund: !ticker,
      excluded: !!h.excluded,
    }, { inputMethod: (!adding && h.inputMethod) ? h.inputMethod : 'manual', source: f.source });
    if (!adding && h.fromStatement) rec.fromStatement = true; // keep statement origin when editing an import
    CM.mutate(function () {
      const arr = (CM.S.investments = CM.S.investments || []);
      if (adding) arr.push(rec); else Object.assign(arr[idx], rec);
    });
    try { CM.logActivity && CM.logActivity(adding ? 'added a holding' : 'updated a holding', rec.name, { cat: 'investments' }); } catch (e) {}
    onClose();
  };

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 1000, background: 'rgba(13,33,26,.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
      <div onClick={(e) => e.stopPropagation()} className="cm-card" style={{ width: 600, maxWidth: '100%', maxHeight: '92vh', overflowY: 'auto', borderRadius: 18, padding: '22px 24px', boxShadow: '0 24px 64px rgba(13,42,33,.28)' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
          <div className="t-h2" style={{ fontSize: 18 }}>{adding ? 'Add Holding' : 'Edit Holding'}</div>
          <button onClick={onClose} aria-label="Close" style={{ border: 0, background: 'none', cursor: 'pointer', color: 'var(--ink-400)', fontSize: 20, lineHeight: 1, padding: 4 }}>×</button>
        </div>
        <div className="t-caption" style={{ marginBottom: 16 }}>Leave the ticker blank for mutual funds — Clear Mint skips live quotes &amp; news for those.</div>
        {err && <div style={{ marginBottom: 12, padding: '9px 12px', borderRadius: 10, background: '#FBF3E8', color: '#A9772A', fontWeight: 700, fontSize: 12.5, border: '1px solid var(--gold-soft)' }}>{err}</div>}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '13px 14px' }}>
          <Field label="Investment name" full><input style={inp} value={f.name} onChange={set('name')} placeholder="e.g. Sun Life MFS Canadian Equity"/></Field>
          <Field label="Ticker (optional)">
            <input style={inp} value={f.ticker} onChange={(e)=>{ setLive(null); set('ticker')(e); }} onBlur={onTickerBlur} placeholder="blank for funds"/>
            {live === 'loading' && <div className="t-caption" style={{ marginTop:4, color:'var(--ink-400)', fontWeight:700 }}>Looking up price…</div>}
            {live === 'ok' && <div className="t-caption" style={{ marginTop:4, color:'var(--green-600)', fontWeight:800, display:'inline-flex', alignItems:'center', gap:4 }}><ANI.check size={12}/>live price ✓</div>}
            {live === 'miss' && <div className="t-caption" style={{ marginTop:4, color:'#A9772A', fontWeight:700 }}>No live quote — enter price manually.</div>}
          </Field>
          <Field label="Currency"><select style={inp} value={f.currency} onChange={set('currency')}><option>CAD</option><option>USD</option></select></Field>
          <Field label="Type"><select style={inp} value={f.type} onChange={set('type')}>{HOLD_TYPES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}</select></Field>
          <Field label="Register type"><select style={inp} value={f.register} onChange={set('register')}>{HOLD_REGISTERS.map((r) => <option key={r}>{r}</option>)}</select></Field>
          <Field label="Account / Source" full>
            <input style={inp} list="cm-hold-src" value={f.source} onChange={set('source')} placeholder="Wealthsimple, Questrade, Sun Life…"/>
            <datalist id="cm-hold-src">{HOLD_SOURCES.map((s) => <option key={s} value={s}/>)}</datalist>
          </Field>
          <Field label="Units"><input style={inp} value={f.units} onChange={set('units')} inputMode="decimal" placeholder="0"/></Field>
          <Field label="Price"><input style={inp} value={f.price} onChange={set('price')} inputMode="decimal" placeholder="0.00"/></Field>
          <Field label="Value (auto = units × price, editable)" full><input style={inp} value={f.value} onChange={set('value')} inputMode="decimal" placeholder="0.00"/></Field>
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 20 }}>
          <button onClick={onClose} className="cm-btn cm-btn-ghost">Cancel</button>
          <button onClick={save} className="cm-btn cm-btn-primary">{adding ? 'Add Holding' : 'Save Changes'}</button>
        </div>
      </div>
    </div>
  );
}

/* ───────────────── unified "Add Investment" entry: source picker (3 paths) ───────────────── */
function SourcePickerModal({ onManual, onConnect, onFile, busy, error, onClose }) {
  const fileRef = React.useRef(null);
  React.useEffect(() => {
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const pick = (e) => { const file = e.target.files && e.target.files[0]; e.target.value = ''; if (file) onFile(file); };
  const Card = ({ icon, title, sub, onClick, tint, color }) => (
    <button onClick={onClick} disabled={busy} style={{ textAlign:'left', cursor:busy?'default':'pointer', opacity:busy?.6:1, border:'1px solid var(--line)', borderRadius:14, padding:'16px 16px', background:'#fff', display:'flex', gap:13, alignItems:'flex-start', width:'100%', fontFamily:'inherit' }}>
      <span style={{ width:42, height:42, flex:'0 0 42px', borderRadius:12, background:tint, color, display:'flex', alignItems:'center', justifyContent:'center' }}>{icon}</span>
      <span style={{ flex:1, minWidth:0 }}>
        <span style={{ display:'block', fontSize:14.5, fontWeight:800, color:'var(--ink-900)' }}>{title}</span>
        <span style={{ display:'block', fontSize:12.5, color:'var(--ink-400)', fontWeight:600, marginTop:2, lineHeight:1.4 }}>{sub}</span>
      </span>
      <span style={{ color:'var(--ink-400)', alignSelf:'center' }}><ANI.chevR size={16}/></span>
    </button>
  );
  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:1000, background:'rgba(13,33,26,.45)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div onClick={e => e.stopPropagation()} className="cm-card" style={{ width:520, maxWidth:'100%', borderRadius:18, padding:'22px 24px', boxShadow:'0 24px 64px rgba(13,42,33,.28)' }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4 }}>
          <div className="t-h2" style={{ fontSize:18 }}>Add Investment</div>
          <button onClick={onClose} aria-label="Close" style={{ border:0, background:'none', cursor:'pointer', color:'var(--ink-400)', fontSize:20, lineHeight:1, padding:4 }}>×</button>
        </div>
        <div className="t-caption" style={{ marginBottom:16 }}>Choose how to add holdings — all three keep your portfolio in one place.</div>
        {error && <div style={{ marginBottom:12, padding:'9px 12px', borderRadius:10, background:'#FBF3E8', color:'#A9772A', fontWeight:700, fontSize:12.5, border:'1px solid var(--gold-soft)' }}>{error}</div>}
        <div style={{ display:'flex', flexDirection:'column', gap:11 }}>
          <Card icon={busy ? <span style={{ display:'inline-flex', animation:'cm-spin .9s linear infinite' }}><ANI.refresh size={20}/></span> : <ANI.download size={20}/>} tint="var(--mint-100)" color={G}
            title={busy ? 'Reading statement…' : 'Import from statement'} sub="Upload a PDF, OFX or CSV — we’ll parse accounts & holdings for you to review." onClick={() => !busy && fileRef.current && fileRef.current.click()}/>
          <Card icon={<ANI.bank size={20}/>} tint="#E7EEFE" color={BL}
            title="Connect an account" sub="Wealthsimple, Questrade, Sun Life, Manulife, Moomoo and more." onClick={onConnect}/>
          <Card icon={<ANI.wallet size={20}/>} tint="#EDE7F9" color={PU}
            title="Add manually" sub="Enter a holding by hand — type a ticker to auto-fill its live price." onClick={onManual}/>
        </div>
        <input ref={fileRef} type="file" accept="application/pdf,.pdf,.ofx,.csv,text/csv" style={{ display:'none' }} onChange={pick}/>
      </div>
    </div>
  );
}

/* Statement import → review/select before merge. Investment statements list accounts + holdings with
   checkboxes (default all on); confirming writes the selection to S.investmentAccounts via writeAccounts. */
function ImportPreviewModal({ data, CM, onClose }) {
  const accts = (data && data.subAccounts) || [];
  const source = (data && (data.provider || data.issuer)) || 'Statement';
  const warnings = (data && data.warnings) || [];
  const isInsurance = data && data.docType === 'insurance';
  const [sel, setSel] = React.useState(() => accts.map(a => ({ on:true, holds:(a.holdings || []).map(() => true) })));
  React.useEffect(() => {
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const toggleAcc = i => setSel(s => s.map((x, j) => j === i ? Object.assign({}, x, { on:!x.on }) : x));
  const toggleHold = (i, k) => setSel(s => s.map((x, j) => j === i ? Object.assign({}, x, { holds:x.holds.map((b, m) => m === k ? !b : b) }) : x));
  const chosenCount = sel.filter(x => x.on).length;
  const confirm = () => {
    const chosen = accts.map((a, i) => ({ a, s:sel[i] })).filter(p => p.s && p.s.on).map(p => {
      const holds = (p.a.holdings || []).filter((_, k) => p.s.holds[k]);
      return normalizeAccount(Object.assign({}, p.a, { holdings: holds }), { inputMethod:'statement', source, importedAt: Date.now() });
    });
    writeAccounts(CM, chosen, source);
    try { CM.logActivity && CM.logActivity('imported a statement', source + ' · ' + chosen.length + ' account' + (chosen.length === 1 ? '' : 's'), { cat:'investments' }); } catch (e) {}
    onClose();
  };
  const fmtMoney = (v, ccy) => (ccy === 'USD' ? 'US$' : '$') + Number(v || 0).toLocaleString('en-CA', { minimumFractionDigits:2, maximumFractionDigits:2 });
  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:1000, background:'rgba(13,33,26,.45)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div onClick={e => e.stopPropagation()} className="cm-card" style={{ width:600, maxWidth:'100%', maxHeight:'90vh', overflowY:'auto', borderRadius:18, padding:'22px 24px', boxShadow:'0 24px 64px rgba(13,42,33,.28)' }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4 }}>
          <div className="t-h2" style={{ fontSize:18 }}>Review import</div>
          <button onClick={onClose} aria-label="Close" style={{ border:0, background:'none', cursor:'pointer', color:'var(--ink-400)', fontSize:20, lineHeight:1, padding:4 }}>×</button>
        </div>
        <div className="t-caption" style={{ marginBottom:14 }}>Parsed from <b style={{ color:'var(--ink-700)' }}>{source}</b> — untick anything you don’t want to import.</div>
        {warnings.length > 0 && (
          <div style={{ marginBottom:14, padding:'10px 13px', borderRadius:10, background:'#FBF3E8', color:'#A9772A', fontWeight:600, fontSize:12.5, border:'1px solid var(--gold-soft)' }}>
            <div style={{ display:'flex', alignItems:'center', gap:7, fontWeight:800, marginBottom:warnings.length?4:0 }}><ANI.alert size={14}/>Parser notes</div>
            {warnings.map((w, i) => <div key={i} style={{ paddingLeft:21 }}>• {w}</div>)}
          </div>
        )}
        {isInsurance ? (
          <div style={{ padding:'18px 0', textAlign:'center', color:'var(--ink-500)', fontSize:13, fontWeight:600 }}>
            This looks like an <b>insurance policy</b>, not an investment statement. Import it from the <b>Insurance</b> tab instead.
          </div>
        ) : !accts.length ? (
          <div style={{ padding:'18px 0', textAlign:'center', color:'var(--ink-500)', fontSize:13, fontWeight:600 }}>No investment accounts were found in this document.</div>
        ) : (
          <div style={{ display:'flex', flexDirection:'column', gap:12 }}>
            {accts.map((a, i) => (
              <div key={i} style={{ border:'1px solid var(--line)', borderRadius:12, padding:'12px 14px', opacity:sel[i] && sel[i].on ? 1 : .5 }}>
                <label style={{ display:'flex', alignItems:'center', gap:10, cursor:'pointer' }}>
                  <input type="checkbox" checked={!!(sel[i] && sel[i].on)} onChange={() => toggleAcc(i)}/>
                  <span style={{ fontSize:10.5, fontWeight:800, letterSpacing:'.04em', color:acctColor(a.planType), background:acctColor(a.planType) + '18', borderRadius:7, padding:'3px 8px' }}>{a.planType || 'Account'}</span>
                  <span style={{ flex:1, minWidth:0 }}>
                    <span style={{ display:'block', fontSize:13.5, fontWeight:700, color:'var(--ink-900)' }}>{a.planName || a.planType}</span>
                    <span style={{ display:'block', fontSize:11.5, color:'var(--ink-400)', fontWeight:600 }}>{(a.currency || 'CAD')}{a.registered !== false ? ' · Registered' : ''}{(a.holdings && a.holdings.length) ? ' · ' + a.holdings.length + ' holding' + (a.holdings.length === 1 ? '' : 's') : ''}</span>
                  </span>
                  <span className="num" style={{ fontWeight:800, color:'var(--ink-900)' }}>{fmtMoney(a.closingValue, a.currency)}</span>
                </label>
                {a.holdings && a.holdings.length ? (
                  <div style={{ marginTop:10, paddingLeft:30, display:'flex', flexDirection:'column', gap:6 }}>
                    {a.holdings.map((h, k) => (
                      <label key={k} style={{ display:'flex', alignItems:'center', gap:9, cursor:'pointer', fontSize:12.5, color:'var(--ink-700)' }}>
                        <input type="checkbox" disabled={!(sel[i] && sel[i].on)} checked={!!(sel[i] && sel[i].holds[k])} onChange={() => toggleHold(i, k)}/>
                        <span style={{ flex:1, minWidth:0 }}>{h.fundName || h.name || 'Holding'}{h.ticker ? '' : <span style={{ color:'var(--ink-400)', marginLeft:6, fontSize:11 }}>· fund</span>}</span>
                        <span className="num" style={{ fontWeight:700 }}>{fmtMoney(h.value, h.currency)}</span>
                      </label>
                    ))}
                  </div>
                ) : null}
              </div>
            ))}
          </div>
        )}
        <div style={{ display:'flex', gap:10, justifyContent:'flex-end', marginTop:20 }}>
          <button onClick={onClose} className="cm-btn cm-btn-ghost">Cancel</button>
          {!isInsurance && accts.length > 0 && <button onClick={confirm} disabled={!chosenCount} className="cm-btn cm-btn-primary" style={{ opacity:chosenCount ? 1 : .5 }}>Import {chosenCount} account{chosenCount === 1 ? '' : 's'}</button>}
        </div>
      </div>
    </div>
  );
}

/* "Connect an account" — integrations. No live broker connection exists yet, so this is an honest
   Coming-soon + waitlist (interest stored in S.brokerWaitlist), NOT a faked OAuth flow. connectBroker()
   is the single pluggable hook: wiring Flinks Connect later means filling it in and flipping LIVE — the
   cards already exist, so no UI rewrite. (Project note: Flinks Connect is the preferred provider.) */
const BROKERS = [
  { id:'wealthsimple', name:'Wealthsimple' }, { id:'questrade', name:'Questrade' },
  { id:'sunlife', name:'Sun Life' }, { id:'manulife', name:'Manulife' },
  { id:'moomoo', name:'Moomoo' }, { id:'flinks', name:'Other (Flinks)' },
];
const INTEGRATIONS_LIVE = false; // flip to true once connectBroker() is wired to Flinks Connect
function connectBroker(/* providerId, CM */) {
  // TODO(integration): launch Flinks Connect, e.g. window.open(flinksConnectUrl(providerId), 'cm-connect').
  // On success, run parsed accounts/holdings through normalizeAccount/normalizeHolding + writeAccounts.
}
function ConnectAccountModal({ CM, onClose }) {
  React.useEffect(() => {
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const waitlist = (CM.S && CM.S.brokerWaitlist) || {};
  const [, force] = React.useState(0);
  const toggleWait = (id) => { CM.mutate(() => { const w = (CM.S.brokerWaitlist = CM.S.brokerWaitlist || {}); w[id] = !w[id]; }); force(n => n + 1); };
  const onConnect = (id) => { if (INTEGRATIONS_LIVE) connectBroker(id, CM); };
  return (
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:1000, background:'rgba(13,33,26,.45)', display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div onClick={e => e.stopPropagation()} className="cm-card" style={{ width:540, maxWidth:'100%', maxHeight:'90vh', overflowY:'auto', borderRadius:18, padding:'22px 24px', boxShadow:'0 24px 64px rgba(13,42,33,.28)' }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:4 }}>
          <div className="t-h2" style={{ fontSize:18 }}>Connect an account</div>
          <button onClick={onClose} aria-label="Close" style={{ border:0, background:'none', cursor:'pointer', color:'var(--ink-400)', fontSize:20, lineHeight:1, padding:4 }}>×</button>
        </div>
        <div className="t-caption" style={{ marginBottom:16 }}>Secure bank-grade connections are coming soon. Join the waitlist and we’ll turn yours on first.</div>
        <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
          {BROKERS.map(b => {
            const on = !!waitlist[b.id];
            return (
              <div key={b.id} style={{ display:'flex', alignItems:'center', gap:12, border:'1px solid var(--line)', borderRadius:12, padding:'12px 14px' }}>
                <span style={{ width:36, height:36, flex:'0 0 36px', borderRadius:10, background:'var(--mint-50)', color:G, display:'flex', alignItems:'center', justifyContent:'center', fontWeight:800, fontSize:13 }}>{b.name.replace(/[^A-Za-z]/g, '').slice(0, 2).toUpperCase()}</span>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:13.5, fontWeight:700, color:'var(--ink-900)' }}>{b.name}</div>
                  <div style={{ fontSize:11.5, color:'var(--ink-400)', fontWeight:700 }}>{INTEGRATIONS_LIVE ? 'Ready to connect' : 'Coming soon'}</div>
                </div>
                {INTEGRATIONS_LIVE
                  ? <button onClick={() => onConnect(b.id)} className="cm-btn cm-btn-primary" style={{ padding:'7px 14px' }}>Connect</button>
                  : <button onClick={() => toggleWait(b.id)} className={on ? 'cm-btn cm-btn-primary' : 'cm-btn cm-btn-soft'} style={{ padding:'7px 14px' }}>{on ? <><ANI.check size={14}/> On the waitlist</> : 'Join waitlist'}</button>}
              </div>
            );
          })}
        </div>
        <div style={{ display:'flex', justifyContent:'flex-end', marginTop:20 }}>
          <button onClick={onClose} className="cm-btn cm-btn-ghost">Done</button>
        </div>
      </div>
    </div>
  );
}

window.PageInvestments = PageInvestments;

/* "+ Add Investment" (app header / empty state) → open the unified source picker (import / connect /
   manual). PageInvestments listens for cm:add-investment. No native prompts. */
window.addInvestmentPrompt = function(){
  if(window.CMAccess && !CMAccess.requirePremium('investments','Investment tracking')) return;
  try{ window.dispatchEvent(new CustomEvent('cm:add-investment')); }catch(e){}
};
})();
