// Activity page: live filing stream + amendment activity
// Research page: aggregate analytics — capital concentration, vintage curves, sponsor velocity

const { useState: useS_A, useEffect: useE_A, useMemo: useM_A } = React;

// ───────────────────────────────────────────────────────────────────────────
// ACTIVITY PAGE
// ───────────────────────────────────────────────────────────────────────────

function ActivityPage({ navigate }) {
  const [recent, setRecent] = useS_A(null);
  const [filter, setFilter] = useS_A('all');     // all | new | amend | mega
  const [industry, setIndustry] = useS_A('all'); // all | COMMERCIAL | RESIDENTIAL | OTHER
  const [autoRefresh, setAutoRefresh] = useS_A(true);
  const [tick, setTick] = useS_A(0);

  useE_A(() => {
    window.api('/api/deals?real_estate=1&limit=300&sort=latest_filing_date').then(r => setRecent(r.results));
  }, [tick]);

  useE_A(() => {
    if (!autoRefresh) return;
    const id = setInterval(() => setTick(t => t + 1), 60_000);
    return () => clearInterval(id);
  }, [autoRefresh]);

  // Derive activity from deal records (latest_filing_date + amendment_no)
  const activity = useM_A(() => {
    if (!recent) return null;
    return recent
      .filter(r => r.industry_group_type) // only known industry
      .map(r => ({
        ...r,
        kind: r.amendment_no > 0 ? 'amend' : 'new',
        ts: r.latest_filing_date,
      }))
      .sort((a, b) => (a.ts < b.ts ? 1 : -1));
  }, [recent]);

  const filtered = useM_A(() => {
    if (!activity) return null;
    return activity.filter(r => {
      if (filter === 'new'   && r.kind !== 'new') return false;
      if (filter === 'amend' && r.kind !== 'amend') return false;
      if (filter === 'mega'  && (r.total_raised || 0) < 100_000_000) return false;
      if (industry !== 'all' && r.industry_group_type !== industry) return false;
      return true;
    });
  }, [activity, filter, industry]);

  // Bucket by day for the activity histogram (last 30 days)
  const histogram = useM_A(() => {
    if (!activity) return null;
    const days = 30;
    const today = new Date(); today.setHours(0, 0, 0, 0);
    const buckets = Array.from({ length: days }, (_, i) => {
      const d = new Date(today); d.setDate(d.getDate() - (days - 1 - i));
      return { date: d.toISOString().slice(0, 10), count: 0, raised: 0, mega: 0 };
    });
    const idx = Object.fromEntries(buckets.map((b, i) => [b.date, i]));
    for (const r of activity) {
      const k = (r.latest_filing_date || '').slice(0, 10);
      if (idx[k] !== undefined) {
        buckets[idx[k]].count++;
        buckets[idx[k]].raised += r.total_raised || 0;
        if ((r.total_raised || 0) >= 100_000_000) buckets[idx[k]].mega++;
      }
    }
    return buckets;
  }, [activity]);

  // Group filtered into "today / yesterday / earlier" sections
  const sections = useM_A(() => {
    if (!filtered) return null;
    const today = new Date().toISOString().slice(0, 10);
    const yest = new Date(); yest.setDate(yest.getDate() - 1);
    const yesterday = yest.toISOString().slice(0, 10);
    const out = { today: [], yesterday: [], thisWeek: [], earlier: [] };
    const weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7);
    for (const r of filtered) {
      const k = (r.latest_filing_date || '').slice(0, 10);
      if (k === today) out.today.push(r);
      else if (k === yesterday) out.yesterday.push(r);
      else if (new Date(r.latest_filing_date) >= weekAgo) out.thisWeek.push(r);
      else out.earlier.push(r);
    }
    return out;
  }, [filtered]);

  const stats = useM_A(() => {
    if (!filtered) return null;
    const last24 = filtered.filter(r => {
      const ms = Date.now() - new Date(r.latest_filing_date).getTime();
      return ms <= 24 * 3600 * 1000;
    });
    const last7d = filtered.filter(r => {
      const ms = Date.now() - new Date(r.latest_filing_date).getTime();
      return ms <= 7 * 24 * 3600 * 1000;
    });
    return {
      filings24: last24.length,
      raised24: last24.reduce((a, b) => a + (b.total_raised || 0), 0),
      filings7d: last7d.length,
      raised7d: last7d.reduce((a, b) => a + (b.total_raised || 0), 0),
      megaCount: filtered.filter(r => (r.total_raised || 0) >= 100_000_000).length,
      newCount: filtered.filter(r => r.kind === 'new').length,
      amendCount: filtered.filter(r => r.kind === 'amend').length,
    };
  }, [filtered]);

  return (
    <>
      <div className="page-head">
        <div>
          <div className="page-eyebrow">
            <span className="live-dot"></span>
            {autoRefresh ? 'LIVE · refreshes every 60s' : 'PAUSED'}
          </div>
          <h1 className="page-title">Activity</h1>
          <p className="page-sub">
            The live raise stream. Every new filing and every amendment, in order, as they hit our index.
          </p>
        </div>
        <div className="activity-controls">
          <button className={`btn ${autoRefresh ? 'primary' : 'ghost'}`} onClick={() => setAutoRefresh(a => !a)}>
            <span className="live-dot" style={{ background: autoRefresh ? '#fff' : 'var(--ink-4)' }}></span>
            {autoRefresh ? 'Live' : 'Paused'}
          </button>
          <button className="btn ghost" onClick={() => setTick(t => t + 1)}>Refresh now</button>
        </div>
      </div>

      {/* Stat strip */}
      <div className="activity-stats">
        <div className="stat-tile">
          <div className="stat-label">Filings · 24h</div>
          <div className="stat-value">{stats ? stats.filings24 : '—'}</div>
          <div className="stat-sub">{stats ? window.fmtMoney(stats.raised24) : ''} raised</div>
        </div>
        <div className="stat-tile">
          <div className="stat-label">Filings · 7d</div>
          <div className="stat-value">{stats ? stats.filings7d : '—'}</div>
          <div className="stat-sub">{stats ? window.fmtMoney(stats.raised7d) : ''} raised</div>
        </div>
        <div className="stat-tile">
          <div className="stat-label">New raises</div>
          <div className="stat-value">{stats ? stats.newCount : '—'}</div>
          <div className="stat-sub">in current view</div>
        </div>
        <div className="stat-tile">
          <div className="stat-label">Amendments</div>
          <div className="stat-value">{stats ? stats.amendCount : '—'}</div>
          <div className="stat-sub">in current view</div>
        </div>
        <div className="stat-tile accent">
          <div className="stat-label">$100M+ raises</div>
          <div className="stat-value">{stats ? stats.megaCount : '—'}</div>
          <div className="stat-sub">institutional scale</div>
        </div>
      </div>

      {/* Spotlight: biggest raise + most-amended in current window */}
      {filtered && filtered.length > 0 && <ActivitySpotlight rows={filtered} navigate={navigate} />}

      {/* Histogram */}
      <div className="card">
        <div className="card-header">
          <h3>Filing volume · last 30 days</h3>
          <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
            BARS = FILING COUNT · DOTS = $100M+ MEGA-RAISES
          </span>
        </div>
        <div style={{ padding: '16px 22px 18px' }}>
          {histogram ? <ActivityHistogram data={histogram} /> :
            <div className="skel" style={{ height: 140, width: '100%' }}></div>}
        </div>
      </div>

      {/* Filter chips */}
      <div className="filter-bar">
        <div className="filter-group">
          <span className="filter-label">Type</span>
          {[
            { k: 'all',   l: 'All' },
            { k: 'new',   l: 'New raises' },
            { k: 'amend', l: 'Amendments' },
            { k: 'mega',  l: '$100M+' },
          ].map(o => (
            <button key={o.k} className={`chip ${filter === o.k ? 'active' : ''}`} onClick={() => setFilter(o.k)}>
              {o.l}
            </button>
          ))}
        </div>
        <div className="filter-group">
          <span className="filter-label">Industry</span>
          {[
            { k: 'all',         l: 'All' },
            { k: 'COMMERCIAL',  l: 'Commercial' },
            { k: 'RESIDENTIAL', l: 'Residential' },
            { k: 'OTHER',       l: 'Other RE' },
          ].map(o => (
            <button key={o.k} className={`chip ${industry === o.k ? 'active' : ''}`} onClick={() => setIndustry(o.k)}>
              {o.l}
            </button>
          ))}
        </div>
      </div>

      {/* Activity stream */}
      <div className="activity-stream">
        {!sections && Array.from({ length: 6 }).map((_, i) => (
          <div className="activity-row" key={i}>
            <div className="skel" style={{ height: 12, width: 60 }}></div>
            <div className="skel" style={{ height: 14, width: '70%' }}></div>
            <div className="skel" style={{ height: 12, width: 80 }}></div>
          </div>
        ))}
        {sections && sections.today.length > 0 && (
          <ActivitySection title="Today" rows={sections.today} navigate={navigate} fresh />
        )}
        {sections && sections.yesterday.length > 0 && (
          <ActivitySection title="Yesterday" rows={sections.yesterday} navigate={navigate} />
        )}
        {sections && sections.thisWeek.length > 0 && (
          <ActivitySection title="Earlier this week" rows={sections.thisWeek} navigate={navigate} />
        )}
        {sections && sections.earlier.length > 0 && (
          <ActivitySection title="Earlier" rows={sections.earlier} navigate={navigate} />
        )}
        {sections && Object.values(sections).every(s => s.length === 0) && (
          <div className="empty">No activity matches the current filters.</div>
        )}
      </div>
    </>
  );
}

function ActivitySpotlight({ rows, navigate }) {
  // Biggest raise in window
  const biggest = [...rows].sort((a, b) => (b.total_raised || 0) - (a.total_raised || 0))[0];
  // Most amendments in window
  const mostAmended = [...rows].sort((a, b) => (b.amendment_no || 0) - (a.amendment_no || 0))[0];
  // First-time filers (amendment_no === 0 AND first_filing_date is recent)
  const firstTimers = rows.filter(r => r.amendment_no === 0 && (r.total_raised || 0) > 0).slice(0, 3);

  return (
    <div className="spotlight-grid">
      {biggest && (biggest.total_raised || 0) > 0 && (
        <div className="spotlight-card biggest" onClick={() => navigate({ name: 'deal', id: biggest.deal_id })}>
          <div className="spotlight-eyebrow">★ Biggest in window</div>
          <div className="spotlight-amount">{window.fmtMoney(biggest.total_raised)}</div>
          <div className="spotlight-name">{biggest.deal_name}</div>
          <div className="spotlight-meta">
            <span className={`industry-tag ${window.industryClass(biggest.industry_group_type)}`}>
              {window.industryShort(biggest.industry_group_type)}
            </span>
            <span>· {biggest.filings_count} filings</span>
            <span>· {window.relTime(biggest.latest_filing_date)}</span>
          </div>
        </div>
      )}
      {mostAmended && (mostAmended.amendment_no || 0) > 0 && (
        <div className="spotlight-card amend" onClick={() => navigate({ name: 'deal', id: mostAmended.deal_id })}>
          <div className="spotlight-eyebrow">↻ Most amended</div>
          <div className="spotlight-amount">AMD {mostAmended.amendment_no}</div>
          <div className="spotlight-name">{mostAmended.deal_name}</div>
          <div className="spotlight-meta">
            <span>{window.fmtMoney(mostAmended.total_raised || 0)} raised</span>
            <span>· active since {new Date(mostAmended.first_filing_date).getFullYear()}</span>
          </div>
        </div>
      )}
      {firstTimers.length > 0 && (
        <div className="spotlight-card firsts">
          <div className="spotlight-eyebrow">◆ Fresh raises</div>
          <div className="spotlight-firsts">
            {firstTimers.map(r => (
              <div className="spotlight-first-row" key={r.deal_id} onClick={() => navigate({ name: 'deal', id: r.deal_id })}>
                <div className="spotlight-first-name">{r.deal_name}</div>
                <div className="spotlight-first-amt">{window.fmtMoney(r.total_raised)}</div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function ActivitySection({ title, rows, navigate, fresh }) {
  return (
    <div className="activity-section">
      <div className="activity-section-head">
        <span className="activity-section-title">{title}</span>
        <span className="activity-section-count">{rows.length}</span>
      </div>
      <div>
        {rows.map((r, i) => (
          <div className={`activity-row ${fresh && i < 3 ? 'fresh' : ''}`} key={r.deal_id}
               onClick={() => navigate({ name: 'deal', id: r.deal_id })}>
            <div className="activity-time">
              <span className="activity-time-rel">{window.relTime(r.latest_filing_date).toUpperCase()}</span>
              <span className="activity-time-abs">{new Date(r.latest_filing_date).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span>
            </div>
            <div className={`activity-kind kind-${r.kind}`}>
              {r.kind === 'new' ? 'NEW' : `AMD ${r.amendment_no}`}
            </div>
            <div className="activity-body">
              <div className="activity-name">{r.deal_name}</div>
              <div className="activity-meta">
                <span className={`industry-tag ${window.industryClass(r.industry_group_type)}`}>
                  {window.industryShort(r.industry_group_type)}
                </span>
                <span>· {r.filings_count} filing{r.filings_count > 1 ? 's' : ''}</span>
                {r.first_filing_date !== r.latest_filing_date && (
                  <span>· active since {new Date(r.first_filing_date).getFullYear()}</span>
                )}
              </div>
            </div>
            <div className={`activity-amount ${(r.total_raised || 0) >= 100_000_000 ? 'mega' : ''} ${!r.total_raised ? 'zero' : ''}`}>
              {r.total_raised ? window.fmtMoney(r.total_raised) : '—'}
              {(r.total_raised || 0) >= 100_000_000 && <span className="mega-mark">●</span>}
              {r.target_is_indefinite ? (
                <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 400, marginTop: 2, fontStyle: 'italic' }}>of Indefinite</div>
              ) : r.target_offering_amount ? (
                <div style={{ fontSize: 10, color: 'var(--ink-4)', fontWeight: 400, marginTop: 2 }}>
                  of {window.fmtMoney(r.target_offering_amount)}
                  {r.total_raised && r.target_offering_amount ? ` · ${Math.round((r.total_raised / r.target_offering_amount) * 100)}%` : ''}
                </div>
              ) : null}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function ActivityHistogram({ data }) {
  const max = Math.max(...data.map(d => d.count), 1);
  const W = 1200, H = 140, pad = { l: 36, r: 12, t: 10, b: 22 };
  const iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const xStep = iw / data.length;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} preserveAspectRatio="none">
      {[0, 0.5, 1].map((t, i) => (
        <g key={i}>
          <line x1={pad.l} x2={pad.l + iw} y1={pad.t + ih - t * ih} y2={pad.t + ih - t * ih}
                stroke="var(--line)" strokeDasharray={t === 0 ? null : '2 3'} />
          <text x={pad.l - 6} y={pad.t + ih - t * ih + 3} fontSize="10" fontFamily="var(--font-mono)"
                fill="var(--ink-4)" textAnchor="end">
            {Math.round(t * max)}
          </text>
        </g>
      ))}
      {data.map((d, i) => {
        const h = (d.count / max) * ih;
        const x = pad.l + i * xStep + 1;
        const y = pad.t + ih - h;
        return (
          <g key={i}>
            <rect x={x} y={y} width={Math.max(xStep - 2, 1)} height={h}
                  fill="var(--accent)" fillOpacity={d.count > 0 ? 0.85 : 0} />
            {d.mega > 0 && (
              <circle cx={x + (xStep - 2) / 2} cy={y - 6} r="3"
                      fill="var(--pos)" stroke="var(--bg)" strokeWidth="1.5" />
            )}
          </g>
        );
      })}
      {[0, Math.floor(data.length / 2), data.length - 1].map((idx, i) => (
        <text key={i} x={pad.l + idx * xStep + xStep / 2} y={pad.t + ih + 16} fontSize="10"
              fontFamily="var(--font-mono)" fill="var(--ink-4)"
              textAnchor={i === 0 ? 'start' : i === 2 ? 'end' : 'middle'}>
          {window.fmtDateShort(data[idx].date)}
        </text>
      ))}
    </svg>
  );
}

// ───────────────────────────────────────────────────────────────────────────
// RESEARCH PAGE
// ───────────────────────────────────────────────────────────────────────────

function ResearchPage({ navigate }) {
  const [topSponsors, setTopSponsors] = useS_A(null);   // by capital
  const [bySponsorCount, setBySpCount] = useS_A(null);  // by deal count
  const [recent, setRecent] = useS_A(null);
  const [topDeals, setTopDeals] = useS_A(null);
  const [section, setSection] = useS_A('concentration'); // concentration | vintage | velocity | size

  useE_A(() => {
    window.api('/api/sponsors?limit=200&sort=total_raised').then(r => setTopSponsors(r.results));
    window.api('/api/sponsors?limit=50&sort=deals_count').then(r => setBySpCount(r.results));
    window.api('/api/deals?real_estate=1&limit=400&sort=latest_filing_date').then(r => setRecent(r.results));
    window.api('/api/deals?real_estate=1&limit=20&sort=total_raised').then(r => setTopDeals(r.results));
  }, []);

  return (
    <>
      <div className="page-head">
        <div>
          <div className="page-eyebrow">RESEARCH · ANALYTICS</div>
          <h1 className="page-title">Research desk</h1>
          <p className="page-sub">
            How private real-estate capital is concentrated, when it gets raised, and which sponsors are accelerating.
          </p>
        </div>
      </div>

      <div className="research-tabs">
        {[
          { k: 'concentration', l: 'Capital concentration', sub: 'Who controls the dollars' },
          { k: 'vintage',       l: 'Vintage curves',        sub: 'Raises by year' },
          { k: 'velocity',      l: 'Sponsor velocity',      sub: 'Who is most active' },
          { k: 'size',          l: 'Deal size distribution',sub: 'Where the dollars sit' },
        ].map(t => (
          <button key={t.k} className={`research-tab ${section === t.k ? 'active' : ''}`} onClick={() => setSection(t.k)}>
            <div className="research-tab-l">{t.l}</div>
            <div className="research-tab-sub">{t.sub}</div>
          </button>
        ))}
      </div>

      {section === 'concentration' && (
        <ConcentrationView topSponsors={topSponsors} navigate={navigate} />
      )}
      {section === 'vintage' && (
        <VintageView recent={recent} topSponsors={topSponsors} />
      )}
      {section === 'velocity' && (
        <VelocityView bySponsorCount={bySponsorCount} navigate={navigate} />
      )}
      {section === 'size' && (
        <SizeView recent={recent} topDeals={topDeals} navigate={navigate} />
      )}
    </>
  );
}

// ── Concentration ──────────────────────────────────────────────────────────

function ConcentrationView({ topSponsors, navigate }) {
  const stats = useM_A(() => {
    if (!topSponsors) return null;
    // Total raised across the indexed top-200 (good proxy for the overall curve)
    const total = topSponsors.reduce((a, b) => a + (b.total_raised || 0), 0);
    let cum = 0;
    const cumPoints = topSponsors.map((s, i) => {
      cum += (s.total_raised || 0);
      return { i: i + 1, name: s.canonical_name, raised: s.total_raised, cum, pct: cum / total };
    });
    const findRank = (target) => cumPoints.find(p => p.pct >= target)?.i || topSponsors.length;
    return {
      total,
      cumPoints,
      top10Pct:  topSponsors.slice(0, 10).reduce((a, b) => a + (b.total_raised || 0), 0) / total,
      top25Pct:  topSponsors.slice(0, 25).reduce((a, b) => a + (b.total_raised || 0), 0) / total,
      top50Pct:  topSponsors.slice(0, 50).reduce((a, b) => a + (b.total_raised || 0), 0) / total,
      rankFor25: findRank(0.25),
      rankFor50: findRank(0.50),
      rankFor75: findRank(0.75),
    };
  }, [topSponsors]);

  if (!topSponsors || !stats) {
    return <div className="card"><div style={{ padding: 40 }}><div className="skel" style={{ height: 280 }}></div></div></div>;
  }

  return (
    <>
      <div className="research-summary">
        <div className="research-summary-row">
          <div className="research-summary-num">{(stats.top10Pct * 100).toFixed(1)}%</div>
          <div>
            <div className="research-summary-l">of top-200 capital sits with the top 10 sponsors</div>
            <div className="research-summary-sub">{window.fmtMoney(stats.top10Pct * stats.total)} of {window.fmtMoney(stats.total)} indexed</div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num">{stats.rankFor50}</div>
          <div>
            <div className="research-summary-l">sponsors hold half the indexed capital</div>
            <div className="research-summary-sub">A long-tail market with a heavy front. The 50% line falls inside the top {stats.rankFor50}.</div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num">{(stats.top50Pct * 100).toFixed(0)}%</div>
          <div>
            <div className="research-summary-l">controlled by the top 50</div>
            <div className="research-summary-sub">Top 50 = {window.fmtMoney(stats.top50Pct * stats.total)} · remaining 150 share the rest</div>
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>Cumulative share of capital · top 200 sponsors</h3>
          <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
            X = SPONSOR RANK · Y = % OF INDEXED CAPITAL
          </span>
        </div>
        <div style={{ padding: '20px 22px 22px' }}>
          <ConcentrationCurve points={stats.cumPoints} />
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>The top 20 by capital</h3>
          <a href="#/sponsors" className="btn ghost" onClick={(e) => { e.preventDefault(); navigate({ name: 'sponsors' }); }}>
            All sponsors <window.I.arrow />
          </a>
        </div>
        <div className="research-bars">
          {topSponsors.slice(0, 20).map((s, i) => {
            const pct = s.total_raised / topSponsors[0].total_raised;
            return (
              <div className="research-bar" key={s.sponsor_id} onClick={() => navigate({ name: 'sponsor', id: s.sponsor_id })}>
                <span className="research-bar-rank">{String(i + 1).padStart(2, '0')}</span>
                <div className="research-bar-name">{s.canonical_name}</div>
                <div className="research-bar-track">
                  <div className="research-bar-fill" style={{ width: `${pct * 100}%` }}></div>
                </div>
                <div className="research-bar-amt">{window.fmtMoney(s.total_raised)}</div>
                <div className="research-bar-deals">{s.deals_count} deals</div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

function ConcentrationCurve({ points }) {
  const W = 1200, H = 280, pad = { l: 56, r: 18, t: 14, b: 32 };
  const iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const x = (i) => pad.l + (i / (points.length - 1)) * iw;
  const y = (p) => pad.t + ih - p * ih;
  const line = points.map((p, i) => `${i === 0 ? 'M' : 'L'} ${x(i).toFixed(1)} ${y(p.pct).toFixed(1)}`).join(' ');
  const area = `M ${pad.l} ${pad.t + ih} ${points.map((p, i) => `L ${x(i).toFixed(1)} ${y(p.pct).toFixed(1)}`).join(' ')} L ${pad.l + iw} ${pad.t + ih} Z`;
  // Reference line: equal distribution (diagonal y=x)
  const refLine = `M ${pad.l} ${pad.t + ih} L ${pad.l + iw} ${pad.t}`;

  // Quartile markers
  const findRank = (t) => points.find(p => p.pct >= t);
  const markers = [0.25, 0.5, 0.75].map(t => {
    const p = findRank(t);
    return p ? { t, i: p.i - 1, p } : null;
  }).filter(Boolean);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} preserveAspectRatio="none">
      <defs>
        <linearGradient id="concGrad" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0%" stopColor="var(--accent)" stopOpacity="0.22"/>
          <stop offset="100%" stopColor="var(--accent)" stopOpacity="0"/>
        </linearGradient>
      </defs>
      {/* Y axis grid */}
      {[0, 0.25, 0.5, 0.75, 1].map(t => (
        <g key={t}>
          <line x1={pad.l} x2={pad.l + iw} y1={y(t)} y2={y(t)} stroke="var(--line)"
                strokeDasharray={t === 0 ? null : '2 3'} />
          <text x={pad.l - 8} y={y(t) + 3} fontSize="10" fontFamily="var(--font-mono)"
                fill="var(--ink-4)" textAnchor="end">{Math.round(t * 100)}%</text>
        </g>
      ))}
      {/* Reference (perfect equality) */}
      <path d={refLine} stroke="var(--ink-5)" strokeDasharray="3 3" fill="none" strokeWidth="1" />
      {/* Area + line */}
      <path d={area} fill="url(#concGrad)" />
      <path d={line} stroke="var(--accent)" strokeWidth="1.75" fill="none" />
      {/* Quartile callouts */}
      {markers.map(m => (
        <g key={m.t}>
          <line x1={x(m.i)} x2={x(m.i)} y1={y(m.t)} y2={pad.t + ih} stroke="var(--accent)"
                strokeDasharray="2 3" strokeOpacity="0.6" />
          <circle cx={x(m.i)} cy={y(m.t)} r="3.5" fill="var(--accent)" stroke="var(--bg)" strokeWidth="1.5"/>
          <text x={x(m.i) + 6} y={y(m.t) - 6} fontSize="11" fontFamily="var(--font-mono)"
                fill="var(--ink-2)" fontWeight="600">
            {Math.round(m.t * 100)}% at rank {m.p.i}
          </text>
        </g>
      ))}
      {/* X labels */}
      {[1, 50, 100, 150, 200].map(r => {
        if (r > points.length) return null;
        const i = Math.min(r - 1, points.length - 1);
        return (
          <text key={r} x={x(i)} y={pad.t + ih + 18} fontSize="10" fontFamily="var(--font-mono)"
                fill="var(--ink-4)" textAnchor={r === 1 ? 'start' : r === 200 ? 'end' : 'middle'}>
            #{r}
          </text>
        );
      })}
    </svg>
  );
}

// ── Vintage ─────────────────────────────────────────────────────────────────

function VintageView({ recent, topSponsors }) {
  const buckets = useM_A(() => {
    if (!recent) return null;
    const byYear = {};
    for (const r of recent) {
      const y = new Date(r.first_filing_date).getFullYear();
      if (!byYear[y]) byYear[y] = { year: y, count: 0, raised: 0, mega: 0 };
      byYear[y].count++;
      byYear[y].raised += r.total_raised || 0;
      if ((r.total_raised || 0) >= 100_000_000) byYear[y].mega++;
    }
    return Object.values(byYear).sort((a, b) => a.year - b.year);
  }, [recent]);

  const insights = useM_A(() => {
    if (!buckets || buckets.length < 2) return null;
    const peak = [...buckets].sort((a, b) => b.raised - a.raised)[0];
    const last = buckets[buckets.length - 1];
    const prev = buckets[buckets.length - 2];
    const yoyCap = prev && prev.raised > 0 ? ((last.raised - prev.raised) / prev.raised) * 100 : null;
    const yoyCnt = prev && prev.count > 0 ? ((last.count - prev.count) / prev.count) * 100 : null;
    return { peak, last, prev, yoyCap, yoyCnt };
  }, [buckets]);

  if (!buckets) return <div className="card"><div style={{ padding: 40 }}><div className="skel" style={{ height: 280 }}></div></div></div>;

  const maxCount = Math.max(...buckets.map(b => b.count), 1);
  const maxRaised = Math.max(...buckets.map(b => b.raised), 1);

  return (
    <>
      <div className="research-summary">
        <div className="research-summary-row">
          <div className="research-summary-num">{insights?.peak.year || '—'}</div>
          <div>
            <div className="research-summary-l">peak vintage by capital</div>
            <div className="research-summary-sub">
              {insights ? `${window.fmtMoney(insights.peak.raised)} raised across ${insights.peak.count} SPVs · ${insights.peak.mega} of them $100M+` : '—'}
            </div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num" style={insights?.yoyCap != null ? { color: insights.yoyCap >= 0 ? 'var(--pos)' : 'var(--neg)' } : undefined}>
            {insights?.yoyCap != null ? `${insights.yoyCap >= 0 ? '+' : ''}${insights.yoyCap.toFixed(0)}%` : '—'}
          </div>
          <div>
            <div className="research-summary-l">capital YoY · {insights?.last.year} vs {insights?.prev?.year}</div>
            <div className="research-summary-sub">
              {insights ? `${window.fmtMoney(insights.last.raised)} this year vs ${window.fmtMoney(insights.prev?.raised || 0)} last year` : '—'}
            </div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num">{buckets[buckets.length - 1]?.count || '—'}</div>
          <div>
            <div className="research-summary-l">new raises started in {buckets[buckets.length - 1]?.year}</div>
            <div className="research-summary-sub">
              {insights?.yoyCnt != null && (
                <span style={{ color: insights.yoyCnt >= 0 ? 'var(--pos)' : 'var(--neg)', fontWeight: 600 }}>
                  {insights.yoyCnt >= 0 ? '↑' : '↓'} {Math.abs(insights.yoyCnt).toFixed(0)}% vs prior year
                </span>
              )} · {window.fmtMoney(buckets[buckets.length - 1]?.raised || 0)} reported
            </div>
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>Raises started, by vintage year</h3>
          <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
            BARS = NEW RAISES · LINE = CAPITAL
          </span>
        </div>
        <div style={{ padding: '16px 22px 18px' }}>
          <VintageChart buckets={buckets} maxCount={maxCount} maxRaised={maxRaised} />
        </div>
        <div className="vintage-table">
          <div className="vintage-th"><span>Year</span><span>New raises</span><span>$100M+</span><span>Capital</span><span>Avg / raise</span></div>
          {[...buckets].reverse().slice(0, 8).map(b => (
            <div className="vintage-row" key={b.year}>
              <span style={{ fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{b.year}</span>
              <span className="mono">{b.count}</span>
              <span className="mono">{b.mega}</span>
              <span className="mono">{window.fmtMoney(b.raised)}</span>
              <span className="mono">{b.count ? window.fmtMoney(b.raised / b.count) : '—'}</span>
            </div>
          ))}
        </div>
      </div>
    </>
  );
}

function VintageChart({ buckets, maxCount, maxRaised }) {
  const W = 1200, H = 240, pad = { l: 48, r: 56, t: 16, b: 28 };
  const iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const xStep = iw / Math.max(buckets.length, 1);
  const ay = (v) => pad.t + ih - (v / maxCount) * ih;
  const ay2 = (v) => pad.t + ih - (v / maxRaised) * ih;
  const linePath = buckets.map((b, i) => `${i === 0 ? 'M' : 'L'} ${(pad.l + xStep * (i + 0.5)).toFixed(1)} ${ay2(b.raised).toFixed(1)}`).join(' ');

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H} preserveAspectRatio="none">
      {[0, 0.5, 1].map(t => (
        <line key={t} x1={pad.l} x2={pad.l + iw} y1={pad.t + ih - t * ih} y2={pad.t + ih - t * ih}
              stroke="var(--line)" strokeDasharray={t === 0 ? null : '2 3'} />
      ))}
      {buckets.map((b, i) => {
        const x = pad.l + i * xStep + 4;
        const w = xStep - 8;
        const h = (b.count / maxCount) * ih;
        return (
          <g key={b.year}>
            <rect x={x} y={pad.t + ih - h} width={w} height={h}
                  fill="color-mix(in oklab, var(--accent) 35%, var(--bg-sunk))" />
            <text x={x + w / 2} y={pad.t + ih + 16} fontSize="10" fontFamily="var(--font-mono)"
                  fill="var(--ink-4)" textAnchor="middle">{b.year}</text>
          </g>
        );
      })}
      {/* Capital line */}
      <path d={linePath} stroke="var(--accent)" strokeWidth="1.75" fill="none" />
      {buckets.map((b, i) => (
        <circle key={i} cx={pad.l + xStep * (i + 0.5)} cy={ay2(b.raised)}
                r="3" fill="var(--accent)" stroke="var(--bg)" strokeWidth="1.5" />
      ))}
      {/* Right axis labels (capital) */}
      {[0, 0.5, 1].map(t => (
        <text key={t} x={pad.l + iw + 8} y={pad.t + ih - t * ih + 3} fontSize="10"
              fontFamily="var(--font-mono)" fill="var(--accent)" textAnchor="start">
          {window.fmtMoney(maxRaised * t)}
        </text>
      ))}
      {/* Left axis labels (count) */}
      {[0, 0.5, 1].map(t => (
        <text key={t} x={pad.l - 8} y={pad.t + ih - t * ih + 3} fontSize="10"
              fontFamily="var(--font-mono)" fill="var(--ink-4)" textAnchor="end">
          {Math.round(maxCount * t)}
        </text>
      ))}
    </svg>
  );
}

// ── Velocity ────────────────────────────────────────────────────────────────

function VelocityView({ bySponsorCount, navigate }) {
  if (!bySponsorCount) return <div className="card"><div style={{ padding: 40 }}><div className="skel" style={{ height: 280 }}></div></div></div>;

  const maxDeals = bySponsorCount[0]?.deals_count || 1;
  const top = bySponsorCount.slice(0, 30);

  return (
    <>
      <div className="research-summary">
        <div className="research-summary-row">
          <div className="research-summary-num">{bySponsorCount[0]?.deals_count}</div>
          <div>
            <div className="research-summary-l">{bySponsorCount[0]?.canonical_name} · most prolific in our index</div>
            <div className="research-summary-sub">
              {window.fmtMoney(bySponsorCount[0]?.total_raised)} raised across {bySponsorCount[0]?.deals_count} attributed SPVs
            </div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num">
            {(bySponsorCount.slice(0, 50).reduce((a, b) => a + b.deals_count, 0) / 50).toFixed(0)}
          </div>
          <div>
            <div className="research-summary-l">average SPVs per top-50 sponsor</div>
            <div className="research-summary-sub">Real sponsors operate at scale — usually one master entity, dozens of SPVs underneath.</div>
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>Sponsor velocity · most active by SPV count</h3>
          <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
            BARS = ATTRIBUTED SPVs · NUMBERS = LIFETIME CAPITAL
          </span>
        </div>
        <div className="research-bars">
          {top.map((s, i) => {
            const pct = s.deals_count / maxDeals;
            return (
              <div className="research-bar" key={s.sponsor_id} onClick={() => navigate({ name: 'sponsor', id: s.sponsor_id })}>
                <span className="research-bar-rank">{String(i + 1).padStart(2, '0')}</span>
                <div className="research-bar-name">{s.canonical_name}</div>
                <div className="research-bar-track">
                  <div className="research-bar-fill alt" style={{ width: `${pct * 100}%` }}></div>
                </div>
                <div className="research-bar-amt">{s.deals_count} SPVs</div>
                <div className="research-bar-deals">{window.fmtMoney(s.total_raised)}</div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

// ── Size distribution ──────────────────────────────────────────────────────

function SizeView({ recent, topDeals, navigate }) {
  const buckets = useM_A(() => {
    if (!recent) return null;
    const tiers = [
      { l: '< $10M',         lo: 0,           hi: 10_000_000,    count: 0, raised: 0 },
      { l: '$10M – $50M',    lo: 10_000_000,  hi: 50_000_000,    count: 0, raised: 0 },
      { l: '$50M – $100M',   lo: 50_000_000,  hi: 100_000_000,   count: 0, raised: 0 },
      { l: '$100M – $500M',  lo: 100_000_000, hi: 500_000_000,   count: 0, raised: 0 },
      { l: '$500M – $1B',    lo: 500_000_000, hi: 1_000_000_000, count: 0, raised: 0 },
      { l: '$1B+',           lo: 1_000_000_000, hi: Infinity,    count: 0, raised: 0 },
    ];
    let zeros = 0;
    for (const r of recent) {
      const v = r.total_raised || 0;
      if (v === 0) { zeros++; continue; }
      for (const t of tiers) {
        if (v >= t.lo && v < t.hi) { t.count++; t.raised += v; break; }
      }
    }
    return { tiers, zeros, total: recent.length };
  }, [recent]);

  if (!buckets || !topDeals) return <div className="card"><div style={{ padding: 40 }}><div className="skel" style={{ height: 280 }}></div></div></div>;

  const totalCount = buckets.tiers.reduce((a, b) => a + b.count, 0);
  const totalRaised = buckets.tiers.reduce((a, b) => a + b.raised, 0);
  const maxC = Math.max(...buckets.tiers.map(t => t.count));

  return (
    <>
      <div className="research-summary">
        <div className="research-summary-row">
          <div className="research-summary-num">
            {((buckets.zeros / buckets.total) * 100).toFixed(0)}%
          </div>
          <div>
            <div className="research-summary-l">of recent filings report <em>no</em> dollar amount</div>
            <div className="research-summary-sub">Filed in advance of first sale, or fundraising abandoned. Count: {buckets.zeros} of {buckets.total}.</div>
          </div>
        </div>
        <div className="research-summary-row">
          <div className="research-summary-num">
            {(((buckets.tiers[3].raised + buckets.tiers[4].raised + buckets.tiers[5].raised) / totalRaised) * 100).toFixed(0)}%
          </div>
          <div>
            <div className="research-summary-l">of capital sits in $100M+ raises</div>
            <div className="research-summary-sub">
              {(((buckets.tiers[3].count + buckets.tiers[4].count + buckets.tiers[5].count) / totalCount) * 100).toFixed(1)}% of deals,
              {' '}{((buckets.tiers[3].raised + buckets.tiers[4].raised + buckets.tiers[5].raised) / totalRaised * 100).toFixed(0)}% of dollars
            </div>
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>Distribution by raise size</h3>
          <span style={{ fontSize: 11, color: 'var(--ink-4)', fontFamily: 'var(--font-mono)' }}>
            DEAL COUNT vs CAPITAL SHARE
          </span>
        </div>
        <div style={{ padding: '20px 22px 18px', display: 'flex', flexDirection: 'column', gap: 14 }}>
          {buckets.tiers.map(t => {
            const cPct = (t.count / totalCount) * 100;
            const rPct = (t.raised / totalRaised) * 100;
            return (
              <div className="size-row" key={t.l}>
                <div className="size-label">{t.l}</div>
                <div className="size-bar-wrap">
                  <div className="size-bar count" style={{ width: `${(t.count / maxC) * 100}%` }}>
                    <span className="size-bar-l">{t.count} deals · {cPct.toFixed(1)}%</span>
                  </div>
                </div>
                <div className="size-bar-wrap">
                  <div className="size-bar raised" style={{ width: `${rPct}%` }}>
                    <span className="size-bar-l">{window.fmtMoney(t.raised)} · {rPct.toFixed(1)}%</span>
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      <div className="card">
        <div className="card-header">
          <h3>The biggest live raises</h3>
          <a href="#/deals" className="btn ghost" onClick={(e) => { e.preventDefault(); navigate({ name: 'deals' }); }}>
            All deals <window.I.arrow />
          </a>
        </div>
        <div className="research-bars">
          {topDeals.slice(0, 12).map((d, i) => {
            const pct = d.total_raised / topDeals[0].total_raised;
            return (
              <div className="research-bar" key={d.deal_id} onClick={() => navigate({ name: 'deal', id: d.deal_id })}>
                <span className="research-bar-rank">{String(i + 1).padStart(2, '0')}</span>
                <div className="research-bar-name">{d.deal_name}</div>
                <div className="research-bar-track">
                  <div className="research-bar-fill" style={{ width: `${pct * 100}%` }}></div>
                </div>
                <div className="research-bar-amt">{window.fmtMoney(d.total_raised)}</div>
                <div className="research-bar-deals">
                  {new Date(d.first_filing_date).getFullYear()}–{new Date(d.latest_filing_date).getFullYear().toString().slice(2)}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

window.ActivityPage = ActivityPage;
window.ResearchPage = ResearchPage;
