// SEC Semiannual Reporting (S7-2026-15) — comment letter analysis app.
//
// Data: window.SEC_DATA (commenters, positions, themes, exec summary)
//       window.SEC_THEMES (canonical themes with chosen quotes)
//       window.SEC_QUOTES (per-letter quote source for substantive letters)
//       window.SEC_LETTER_URLS (ref -> SEC letter URL)

const { useState, useMemo, useEffect } = React;

const DATA = window.SEC_DATA;
const THEMES_DETAIL = window.SEC_THEMES;
const QUOTES_DETAIL = window.SEC_QUOTES;
const LETTER_URLS = window.SEC_LETTER_URLS;

// --- helpers ---
const POSITION_ORDER = ['object', 'support-caveats', 'support', 'neutral', 'meeting-memo'];
const POSITION_LABELS = {
  'object':           'Object',
  'support':          'Support',
  'support-caveats':  'Support with caveats',
  'neutral':          'Neutral',
  'meeting-memo':     'Meeting memo',
};

// Human-readable date from "May 22, 2026" or ISO "2026-05-22". The site's
// data already uses long-form English dates for letter rows, but the docket
// metadata (commentPeriodClose, asOf) is in ISO format.
function humanDate(s) {
  if (!s) return '';
  // Already-formatted "Month D, YYYY" — pass through
  if (/^[A-Z][a-z]+\s+\d{1,2},?\s+\d{4}$/.test(s)) return s;
  // ISO "YYYY-MM-DD" — convert
  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return s;
  const months = ['January','February','March','April','May','June',
                  'July','August','September','October','November','December'];
  return `${months[+m[2] - 1]} ${+m[3]}, ${m[1]}`;
}

function PositionPill({ position, positionId }) {
  return (
    <span className={`pos-pill pos-${positionId}`}>{position}</span>
  );
}

function TierLabel({ tier }) {
  const label = { substantive: 'Substantive', retail: 'Retail', 'meeting-memo': 'Memo' }[tier] || tier;
  return <span className={`letter-tier tier-${tier}`}>{label}</span>;
}

// Compact color legend for the position-split bars used in breakdown sections
// and theme cards. Rendered as an inline strip so it can appear under section
// headers where users encounter the bars.
function PositionLegend({ compact = false }) {
  const items = [
    { id: 'object',          label: 'Object' },
    { id: 'support-caveats', label: 'Support with caveats' },
    { id: 'support',         label: 'Support' },
    { id: 'neutral',         label: 'Neutral' },
    { id: 'meeting-memo',    label: 'Meeting memo' },
  ];
  return (
    <div className={`position-legend ${compact ? 'position-legend--compact' : ''}`}>
      {items.map(i => (
        <span key={i.id} className="position-legend-item">
          <span className={`position-legend-swatch pos-${i.id}`}></span>
          {i.label}
        </span>
      ))}
    </div>
  );
}

// --- Components ---

function Cover() {
  return (
    <div className="cover">
      <div className="container">
        <div className="cover-eyebrow">SEC File No. S7-2026-15 &nbsp;·&nbsp; C. Aubrey Smith Center, UT McCombs</div>
        <h1 className="cover-title">SEC Semiannual Reporting: Comment Letter Analysis</h1>
        <p className="cover-sub">
          An analysis of the {DATA.total} comment letters submitted in response to the SEC's Proposed Amendments to Permit Optional Semiannual Reporting (File No. S7-2026-15) during the first two weeks of the public comment period. The comment period runs through {humanDate(DATA.commentPeriodClose)}.
        </p>
        <StatBar />
        <div className="cover-meta">
          <div className="cover-meta-item">
            <strong>{DATA.total}</strong>
            Total comment letters
          </div>
          <div className="cover-meta-item">
            <strong>{DATA.tiers.find(t => t.id === 'substantive').count}</strong>
            Substantive-tier (per-letter notes)
          </div>
          <div className="cover-meta-item">
            <strong>{DATA.tiers.find(t => t.id === 'retail').count}</strong>
            Retail-tier (CSV summary)
          </div>
          <div className="cover-meta-item">
            <strong>{DATA.themes.length}</strong>
            Canonical themes
          </div>
        </div>
      </div>
    </div>
  );
}

function StatBar() {
  const positions = DATA.positions.filter(p => p.count > 0);
  return (
    <div className="statbar">
      <div className="statbar-title">Position breakdown · all {DATA.total} letters</div>
      <div className="statbar-track" role="img" aria-label="Position breakdown">
        {positions.map(p => (
          <div
            key={p.id}
            className={`statbar-seg pos-${p.id}`}
            style={{ flexBasis: `${p.share}%` }}
            title={`${p.label}: ${p.count} (${p.share}%)`}
          >
            {p.share >= 6 && `${p.count}`}
          </div>
        ))}
      </div>
      <div className="statbar-legend">
        {positions.map(p => (
          <div key={p.id}>
            <span className={`legend-swatch pos-${p.id}`} style={{ background: `var(--pos-${p.id})` }}></span>
            <strong>{p.label}</strong>: {p.count} ({p.share}%)
          </div>
        ))}
      </div>
    </div>
  );
}

function ExecSummary() {
  return (
    <section>
      <div className="container">
        <div className="section-eyebrow">Executive Summary</div>
        <h2 className="section-h">What the comment file shows</h2>
        <div className="exec">
          {DATA.execSummary.map((p, i) => <p key={i}>{p}</p>)}
        </div>
      </div>
    </section>
  );
}

// Methodology / "how to read this site" — explains the three classification axes
// (position, tier, letter type), how themes were derived, and what's NOT yet in
// the file. Linked from drawer banners and from the section dividers so readers
// who jump straight to a section can find the vocabulary.
function Methodology() {
  return (
    <section id="methodology" className="methodology">
      <div className="container">
        <div className="section-eyebrow">How to read this analysis</div>
        <h2 className="section-h">Three classification axes</h2>

        <div className="method-grid">
          <div className="method-card">
            <h3>Position (do they agree?)</h3>
            <p>Every letter is read end-to-end and classified into one of five buckets based on its stance on the proposed rule:</p>
            <ul className="method-list">
              <li><span className="pos-pill pos-object">Object</span> opposes the proposal</li>
              <li><span className="pos-pill pos-support">Support</span> endorses as drafted</li>
              <li><span className="pos-pill pos-support-caveats">Support with caveats</span> agrees with the direction but pushes back on specifics (eligibility, form design, assurance, transition)</li>
              <li><span className="pos-pill pos-neutral">Neutral</span> doesn't take a position (off-topic, technical, fact-only)</li>
              <li><span className="pos-pill pos-meeting-memo">Meeting memo</span> is an SEC staff record of a meeting, not a comment from an outside party</li>
            </ul>
          </div>

          <div className="method-card">
            <h3>Tier (how much editorial work?)</h3>
            <p>Tier governs how much per-letter detail the analysis produces. Length alone doesn't determine tier — a 215-char letter can be substantive, a 4,700-char letter can be retail, depending on whose voice it is and what argument it makes.</p>
            <ul className="method-list">
              <li><strong>Substantive</strong> — gets a per-letter notes file in <code>notes/NN_slug.md</code> with quotes, themes, and a novelty section. Used for named industry voices, structured multi-point arguments, or unique policy proposals.</li>
              <li><strong>Retail</strong> — tracked in <code>bulk_classifications.csv</code> only: position + themes touched + one-sentence summary. No per-letter notes file.</li>
              <li><strong>Meeting memo</strong> — SEC staff record of a meeting; tracked but not analyzed as a letter.</li>
            </ul>
          </div>

          <div className="method-card">
            <h3>Letter type (how was it produced?)</h3>
            <p>A second classification used in the "by letter type" section. Priority: meeting-memo &gt; substantive &gt; template &gt; size-based.</p>
            <ul className="method-list">
              <li><strong>Substantive</strong> — see above.</li>
              <li><strong>Long retail (≥300 chars)</strong> — a multi-sentence retail filing.</li>
              <li><strong>Short retail (&lt;300 chars)</strong> — typically a one-or-two-sentence filing.</li>
              <li><strong>Template / campaign</strong> — letters that substantively duplicate other letters in the file. Detected manually during classification (currently 5 letters across 2 template families).</li>
            </ul>
          </div>

          <div className="method-card">
            <h3>Themes</h3>
            <p>Every letter is tagged with a list of themes during classification (<code>themes_touched</code> in the CSV). The site shows themes that crossed a <strong>≥6 letter</strong> threshold, with two editorial overrides for arguments central to the policy debate that fall just below: <em>short-termism</em> and <em>issuer burden</em>. Descriptor tags (who's writing, e.g. <code>cpa-perspective</code>) and the template-flag tags are excluded from the canonical list but stay in the per-letter data.</p>
            <p>Earlier waves used a ≥2-letter threshold which surfaced 72 themes — most were single-letter watch-items in disguise. The tighter threshold keeps the canonical list focused on cross-letter convergence.</p>
          </div>
        </div>

        <div className="method-limits">
          <h3>Known limitations</h3>
          <ul>
            <li><strong>First-two-weeks snapshot.</strong> Of 310 letters as of {humanDate(DATA.asOf)}, the bulk arrived in the first few days after the proposal landed. Industry-association letters (CAQ itself, AICPA, ICI, Chamber CCMC, Big Four firms, banking trade groups) typically arrive closer to the comment-period close on {humanDate(DATA.commentPeriodClose)} and are not yet represented.</li>
            <li><strong>Position is a soft label.</strong> Some letters are genuinely hard to classify (e.g., #015 Ramsey supports the proposal but argues from issuer-burden grounds the SEC might find weaker than the empirical literature). When in doubt, we err toward what the letter <em>asks for</em> rather than what it argues against.</li>
            <li><strong>Theme tagging is open-vocabulary.</strong> The tag set in <code>themes_touched</code> is editorial, not algorithmic. Two readers might tag a borderline letter slightly differently. The ≥6 threshold absorbs most of that noise but the tail does not.</li>
            <li><strong>Template detection is manual.</strong> The 5 letters tagged as templates were detected by reading the file. A more systematic similarity check across the 310 letters might find more.</li>
          </ul>
        </div>
      </div>
    </section>
  );
}

// Reusable horizontal stacked-bar row showing position split inside a category.
function PositionBreakdownRow({ label, count, positions, onClick }) {
  const order = ['object','support-caveats','support','neutral','meeting-memo'];
  const total = Object.values(positions).reduce((a, b) => a + b, 0);
  return (
    <div className="breakdown-row" onClick={onClick}>
      <div className="breakdown-label">
        <span className="breakdown-label-name">{label}</span>
        <span className="breakdown-label-count">{count}</span>
      </div>
      <div className="breakdown-bar">
        {order.map(p => positions[p] ? (
          <div
            key={p}
            className={`breakdown-bar-seg pos-${p}`}
            style={{ flexBasis: `${100 * positions[p] / total}%` }}
            title={`${p}: ${positions[p]}`}
          >
            {(100 * positions[p] / total) >= 8 && <span className="breakdown-seg-n">{positions[p]}</span>}
          </div>
        ) : null)}
      </div>
    </div>
  );
}

function ByLetterType({ onSelectGroup }) {
  const subCount = (DATA.byLetterType.find(g => g.id === 'substantive') || {}).count || 0;
  return (
    <section>
      <div className="container">
        <div className="section-eyebrow">Position breakdown by letter type</div>
        <h2 className="section-h">Position split varies sharply by letter type</h2>
        <p className="section-lede">
          The 89% Object headline number is a single average across very different kinds of submissions. Breaking the comment file out by letter type shows the substantive tier is much more divided — Object opposition falls to ~40% — while the short retail tier and the template/campaign letters drive the headline.
        </p>
        <PositionLegend />
        <div className="breakdown">
          {DATA.byLetterType.map(g => (
            <PositionBreakdownRow
              key={g.id}
              label={g.label}
              count={g.count}
              positions={g.positions}
            />
          ))}
        </div>
      </div>
    </section>
  );
}

function ByConstituency({ onSelectLetter }) {
  return (
    <section>
      <div className="container">
        <div className="section-eyebrow">Substantive letters by constituency</div>
        <h2 className="section-h">Who's saying what (substantive tier only — 30 letters)</h2>
        <p className="section-lede">
          The 30 substantive letters split across six constituencies. Auditors & accounting voices are unanimously Support / Support-with-caveats; Lawyers and Investors mostly Object; Preparers & Audit Committees are dominated by Support-with-caveats; Academics and Other are mixed. "Other" covers technologists, RegTech founders, banking-risk analysts, and similar topic-specific professional voices that don't fit the named buckets.
        </p>
        <PositionLegend />
        <div className="breakdown">
          {DATA.byConstituency.map(g => (
            <PositionBreakdownRow
              key={g.id}
              label={g.label}
              count={g.count}
              positions={g.positions}
            />
          ))}
        </div>
      </div>
    </section>
  );
}

function ThemesGrid({ onSelectTheme }) {
  // Show the top ~30 canonical themes
  const top = DATA.themes.slice(0, 30);
  return (
    <section>
      <div className="container">
        <div className="section-eyebrow">Themes</div>
        <h2 className="section-h">Canonical themes ({DATA.themes.length})</h2>
        <p className="section-lede">
          Themes are tagged per-letter during classification (every letter gets a list of <code>themes_touched</code>) and aggregated here. To keep the canonical list focused on cross-letter convergence rather than long-tail vocabulary, the threshold is <strong>≥6 letters</strong>, with editorial overrides to include <em>short-termism</em> (5 letters — the primary support-side argument) and <em>issuer burden</em> (4 letters — support-side cost case). Each card shows the letter count and a position-split bar. Click a card to see chosen quotes and every letter that touches the theme.
        </p>
        <PositionLegend />
        <div className="themes-grid">
          {top.map(t => <ThemeCard key={t.id} theme={t} onClick={() => onSelectTheme(t.id)} />)}
        </div>
      </div>
    </section>
  );
}

function ThemeCard({ theme, onClick }) {
  const positions = theme.positions || {};
  const total = Object.values(positions).reduce((a, b) => a + b, 0);
  const order = ['object', 'support-caveats', 'support', 'neutral', 'meeting-memo'];
  return (
    <div className="theme-card" onClick={onClick}>
      <div className="theme-card-label">{theme.label}</div>
      <div className="theme-card-count">{theme.count} letter{theme.count === 1 ? '' : 's'}</div>
      <div className="theme-card-bar">
        {order.map(p => positions[p] ? (
          <div
            key={p}
            className="theme-card-bar-seg"
            style={{
              flexBasis: `${100 * positions[p] / total}%`,
              background: `var(--pos-${p})`,
            }}
            title={`${p}: ${positions[p]}`}
          />
        ) : null)}
      </div>
    </div>
  );
}

function LetterAppendix({ onSelectLetter }) {
  const [tierFilter, setTierFilter] = useState('all'); // all | substantive | retail | meeting-memo
  const [positionFilter, setPositionFilter] = useState('all');
  const [hideTemplates, setHideTemplates] = useState(false);
  const [search, setSearch] = useState('');

  const templateCount = useMemo(() => DATA.commenters.filter(c => c.templateLetter).length, []);

  const filtered = useMemo(() => {
    return DATA.commenters.filter(c => {
      if (tierFilter !== 'all' && c.tier !== tierFilter) return false;
      if (positionFilter !== 'all' && c.positionId !== positionFilter) return false;
      if (hideTemplates && c.templateLetter) return false;
      if (search) {
        const q = search.toLowerCase();
        if (
          !c.name.toLowerCase().includes(q) &&
          !(c.summary || '').toLowerCase().includes(q) &&
          !c.themes.some(t => t.toLowerCase().includes(q))
        ) return false;
      }
      return true;
    });
  }, [tierFilter, positionFilter, hideTemplates, search]);

  return (
    <section>
      <div className="container">
        <div className="section-eyebrow">Letters</div>
        <h2 className="section-h">All comment letters ({DATA.total})</h2>
        <div className="letter-filters">
          <div className="filter-group">
            <button className={`filter-btn ${tierFilter === 'all' ? 'active' : ''}`} onClick={() => setTierFilter('all')}>All tiers</button>
            <button className={`filter-btn ${tierFilter === 'substantive' ? 'active' : ''}`} onClick={() => setTierFilter('substantive')}>Substantive only ({DATA.tiers.find(t => t.id === 'substantive').count})</button>
            <button className={`filter-btn ${tierFilter === 'retail' ? 'active' : ''}`} onClick={() => setTierFilter('retail')}>Retail only ({DATA.tiers.find(t => t.id === 'retail').count})</button>
            <button className={`filter-btn ${tierFilter === 'meeting-memo' ? 'active' : ''}`} onClick={() => setTierFilter('meeting-memo')}>Memos</button>
          </div>
          <div className="filter-group" style={{ marginLeft: '0.5rem' }}>
            <button className={`filter-btn ${positionFilter === 'all' ? 'active' : ''}`} onClick={() => setPositionFilter('all')}>All positions</button>
            <button className={`filter-btn ${positionFilter === 'object' ? 'active' : ''}`} onClick={() => setPositionFilter('object')}>Object</button>
            <button className={`filter-btn ${positionFilter === 'support' ? 'active' : ''}`} onClick={() => setPositionFilter('support')}>Support</button>
            <button className={`filter-btn ${positionFilter === 'support-caveats' ? 'active' : ''}`} onClick={() => setPositionFilter('support-caveats')}>Support w/ caveats</button>
            <button className={`filter-btn ${positionFilter === 'neutral' ? 'active' : ''}`} onClick={() => setPositionFilter('neutral')}>Neutral</button>
          </div>
          {templateCount > 0 && (
            <label className="filter-checkbox" title={`${templateCount} letters are part of coordinated template/campaign filings`}>
              <input
                type="checkbox"
                checked={hideTemplates}
                onChange={e => setHideTemplates(e.target.checked)}
              />
              Hide template letters ({templateCount})
            </label>
          )}
          <input
            type="text"
            className="search-input"
            placeholder="Search by name, summary, or theme…"
            value={search}
            onChange={e => setSearch(e.target.value)}
          />
          <div className="letter-count">{filtered.length} of {DATA.total}</div>
        </div>
        <div className="letter-table">
          {filtered.length === 0 && (
            <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--ink-4)', fontFamily: 'var(--sans)' }}>
              No letters match this filter combination.
            </div>
          )}
          {filtered.map(c => <LetterRow key={c.ref} commenter={c} onClick={() => onSelectLetter(c.ref)} />)}
        </div>
      </div>
    </section>
  );
}

function LetterRow({ commenter, onClick }) {
  return (
    <div className="letter-row" onClick={onClick}>
      <div className="letter-ref">#{String(commenter.ref).padStart(3, '0')}</div>
      <div className="letter-main">
        <div className="letter-name">
          {commenter.displayName || commenter.name}
          {commenter.templateLetter && <span className="template-badge" title="Part of a coordinated template/campaign — substantively duplicates other letters">TEMPLATE</span>}
        </div>
        <div className="letter-summary">{commenter.summary}</div>
        {commenter.tier === 'substantive' && commenter.themes && commenter.themes.length > 0 && (
          <div className="letter-themes">
            {commenter.themes.slice(0, 5).map(t => (
              <span key={t} className="theme-tag">{t}</span>
            ))}
            {commenter.themes.length > 5 && <span className="theme-tag">+{commenter.themes.length - 5}</span>}
          </div>
        )}
      </div>
      <div className="letter-pos-tier">
        <PositionPill position={commenter.position} positionId={commenter.positionId} />
      </div>
      <TierLabel tier={commenter.tier} />
    </div>
  );
}

function LetterDrawer({ letterRef, onClose }) {
  if (!letterRef) return null;
  const c = DATA.commenters.find(x => x.ref === letterRef);
  if (!c) return null;
  const url = LETTER_URLS[letterRef];
  const noteQuotes = c.noteQuotes || [];
  return (
    <div className="drawer-overlay" onClick={onClose}>
      <div className="drawer" onClick={e => e.stopPropagation()}>
        <button className="drawer-close" onClick={onClose}>✕</button>
        <h2>
          {c.displayName || c.name}
          {c.templateLetter && <span className="template-badge">TEMPLATE</span>}
        </h2>
        <div className="drawer-meta">
          <span style={{ fontFamily: 'var(--mono)', color: 'var(--ink-3)' }}>#{String(c.ref).padStart(3, '0')}</span>
          <PositionPill position={c.position} positionId={c.positionId} />
          <TierLabel tier={c.tier} />
          {c.date && <span style={{ color: 'var(--ink-3)' }}>{c.date}</span>}
          {c.organization && <span style={{ color: 'var(--ink-3)' }}>{c.organization}</span>}
        </div>
        {c.templateLetter && (
          <div className="template-banner">
            <strong>Template / campaign letter.</strong>{' '}
            This letter substantively duplicates other letters in the comment file —
            one of <strong>{DATA.commenters.filter(x => x.templateLetter).length}</strong> letters tagged as part of coordinated template filings.
            Tags: {c.templateTags.join(', ')}.
          </div>
        )}

        {c.tier === 'substantive' ? (
          <>
            {c.oneLineSummary && (
              <>
                <h3>One-line summary</h3>
                <p style={{ fontFamily: 'var(--serif)', fontSize: '1rem', color: 'var(--ink)' }}>{c.oneLineSummary}</p>
              </>
            )}
            {c.keyQuote && (
              <>
                <h3>Key one-liner</h3>
                <blockquote>
                  "{c.keyQuote}"
                </blockquote>
              </>
            )}
            {noteQuotes && noteQuotes.length > 0 && (
              <>
                <h3>Notable quotes</h3>
                {noteQuotes.map((q, i) => (
                  <blockquote key={i}>
                    "{q.text}"
                    <span className="quote-attr">— theme: {q.theme}</span>
                  </blockquote>
                ))}
              </>
            )}
            {c.novelty && (
              <>
                <h3>What's distinctive</h3>
                <div className="drawer-novelty">{c.novelty}</div>
              </>
            )}
          </>
        ) : c.tier === 'retail' ? (
          <>
            <h3>Summary</h3>
            <p style={{ fontFamily: 'var(--serif)', fontSize: '1rem' }}>{c.summary}</p>
            {c.themes && c.themes.length > 0 && (
              <>
                <h3>Themes touched</h3>
                <div className="letter-themes">
                  {c.themes.map(t => <span key={t} className="theme-tag">{t}</span>)}
                </div>
              </>
            )}
            <div className="drawer-retail-note">
              This is a <strong>retail-tier</strong> letter — a short submission filed via the SEC's web form. Classified by full-text manual review (position + themes touched + one-line summary) but not given a per-letter notes file. See <a href="#methodology">How to read this analysis</a> for the tier system.
            </div>
          </>
        ) : (
          <>
            <h3>Summary</h3>
            <p style={{ fontFamily: 'var(--serif)', fontSize: '1rem' }}>{c.summary}</p>
            <div className="drawer-retail-note">
              This is a <strong>meeting memo</strong> — an SEC staff record of a meeting with stakeholders, not a comment letter. Tracked separately in the analysis.
            </div>
          </>
        )}

        {url && (
          <a href={url} target="_blank" rel="noopener" className="drawer-link">
            View original on SEC.gov →
          </a>
        )}
      </div>
    </div>
  );
}

function ThemeDrawer({ themeId, onClose, onSelectLetter }) {
  if (!themeId) return null;
  const t = DATA.themes.find(x => x.id === themeId);
  const detail = THEMES_DETAIL[themeId];
  if (!t) return null;
  const refs = t.refs || [];
  return (
    <div className="drawer-overlay" onClick={onClose}>
      <div className="drawer" onClick={e => e.stopPropagation()}>
        <button className="drawer-close" onClick={onClose}>✕</button>
        <h2>{t.label}</h2>
        <div className="drawer-meta">
          <span style={{ fontFamily: 'var(--mono)', color: 'var(--ink-3)' }}>{t.id}</span>
          <span>{t.count} letters</span>
        </div>
        <div className="theme-card-bar" style={{ height: 12, marginBottom: '1.5rem' }}>
          {['object','support-caveats','support','neutral','meeting-memo'].map(p => t.positions[p] ? (
            <div key={p} className="theme-card-bar-seg" style={{ flexBasis: `${100 * t.positions[p] / t.count}%`, background: `var(--pos-${p})` }} />
          ) : null)}
        </div>
        <div className="drawer-meta">
          {Object.entries(t.positions).map(([p, n]) => (
            <span key={p}>
              <span className="legend-swatch" style={{ background: `var(--pos-${p})` }}></span>
              {POSITION_LABELS[p] || p}: {n}
            </span>
          ))}
        </div>

        {detail && detail.quotes && detail.quotes.length > 0 && (
          <>
            <h3>Highlighted quotes</h3>
            {detail.quotes.map((q, i) => (
              <blockquote key={i}>
                "{q.text}"
                <span className="quote-attr">
                  — {q.name} (#{q.ref}, {q.position})
                </span>
              </blockquote>
            ))}
          </>
        )}

        {refs.length > 0 && (
          <>
            <h3>All letters touching this theme ({refs.length})</h3>
            <div className="letter-table">
              {refs.map(ref => {
                const c = DATA.commenters.find(x => x.ref === ref);
                if (!c) return null;
                return (
                  <div
                    key={ref}
                    className="letter-row"
                    style={{ cursor: 'pointer' }}
                    onClick={() => { onClose(); setTimeout(() => onSelectLetter(ref), 50); }}
                  >
                    <div className="letter-ref">#{String(ref).padStart(3, '0')}</div>
                    <div className="letter-main">
                      <div className="letter-name">{c.displayName || c.name}</div>
                      <div className="letter-summary">{c.summary}</div>
                    </div>
                    <div className="letter-pos-tier">
                      <PositionPill position={c.position} positionId={c.positionId} />
                    </div>
                    <TierLabel tier={c.tier} />
                  </div>
                );
              })}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

function Footer() {
  return (
    <footer>
      <div className="container">
        <p>SEC Semiannual Reporting (S7-2026-15) · Comment Letter Analysis</p>
        <p>
          As of {humanDate(DATA.asOf)}, the comment period (closes {humanDate(DATA.commentPeriodClose)}) is still open. New letters
          arriving after this snapshot have not been incorporated.
          The analysis reflects a structured review of the substantive-tier letters and a classification pass across the full comment file.
        </p>
        <p>
          Prepared by the
          C. Aubrey Smith Center at the UT McCombs School of Business.
        </p>
      </div>
    </footer>
  );
}

function App() {
  const [selectedLetter, setSelectedLetter] = useState(null);
  const [selectedTheme, setSelectedTheme] = useState(null);

  // ESC to close drawers
  useEffect(() => {
    const handler = (e) => {
      if (e.key === 'Escape') { setSelectedLetter(null); setSelectedTheme(null); }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []);

  return (
    <div className="app">
      <Cover />
      <ExecSummary />
      <Methodology />
      <ByLetterType />
      <ByConstituency />
      <ThemesGrid onSelectTheme={setSelectedTheme} />
      <LetterAppendix onSelectLetter={setSelectedLetter} />
      <Footer />
      <LetterDrawer letterRef={selectedLetter} onClose={() => setSelectedLetter(null)} />
      <ThemeDrawer
        themeId={selectedTheme}
        onClose={() => setSelectedTheme(null)}
        onSelectLetter={setSelectedLetter}
      />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
