// 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;
const CommentFileCorpusChat = window.CommentFileCorpusChat;
const CHAT_STARTERS = [
  'How do the 53 substantive letters differ from the short retail submissions?',
  'What arguments do supporters make for issuer choice?',
  'What alternatives to semiannual reporting did commenters propose?',
  'How do investor, issuer, banking, and audit commenters differ?',
];
const CHAT_CITATION_TARGETS = Object.fromEntries(DATA.commenters.map((commenter) => [commenter.ref, commenter.ref]));
const EDITORIAL_EXEC_SUMMARY = [
  "The June 5 snapshot contains 818 letters: 773 Object, 13 Support, and 15 Support with caveats. The file also includes 53 substantive letters, 760 retail letters, and five SEC meeting memos.",
  "Most retail letters argue that semiannual reporting would give investors less timely information and make fraud harder to spot. The most common themes are transparency, information gaps between retail and institutional investors, and accountability. Thirty-six letters instead ask for monthly or live reporting.",
  "The substantive letters disagree about the evidence. Cai, Demers, and Nguyen estimate that 70% to 99% of the information in a quarterly earnings announcement spills over to peer firms. Bourveau points to widespread voluntary disclosure in the pre-mandate streetcar industry, and Bainbridge supports using the proposal as a natural experiment.",
  "Practitioners focus on how the change would work. Rosenfeld identifies a disclosure-liability gap for companies that keep issuing quarterly updates outside Form 10-Q. Wells has worked under both UK semiannual reporting and a Nasdaq quarterly schedule. Sarafian expects modest savings because close, earnings-call, board, and lender work would continue. Steinberg recommends a new Form 8-K trigger for the loss of a material contract. Foley & Lardner supports the proposal, while Better Markets and SIFMA both requested more time to comment.",
  "Cai, Rand, Rosenfeld, and Bourveau expect smaller or development-stage issuers to use the option, while Parker and Wold would require a clean reporting history. Other letters ask for more auditor assurance on Form 10-S. Banking and audit commenters also split: National Bankshares supports issuer choice, while Rodriguez Valladares objects on banking-risk grounds; Montalti expects fewer interim reviews to improve year-end audit work, while other auditor-affiliated letters want assurance for voluntary quarterly disclosures.",
];

// --- 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 AnalysisMasthead() {
  return (
    <header className="analysis-masthead">
      <a className="analysis-skip" href="#analysis-main">Skip to analysis</a>
      <div className="analysis-masthead__inner">
        <a className="analysis-masthead__brand" href="../">
          <span>Independent analysis</span>
          Comment File
        </a>
        <div className="analysis-masthead__edition">SEC · S7-2026-15 · Semiannual reporting</div>
        <div className="analysis-masthead__actions">
          <a href="../">All analyses</a>
          <button type="button" onClick={() => window.print()}>Print / PDF</button>
        </div>
      </div>
    </header>
  );
}

function Cover() {
  return (
    <div className="cover">
      <div className="container">
        <div className="cover-eyebrow">SEC File No. S7-2026-15 &nbsp;·&nbsp; Nicholas Hallman<span className="draft-badge">DRAFT</span></div>
        <h1 className="cover-title">SEC Semiannual Reporting: Comment Letter Analysis</h1>
        <p className="cover-sub">
          Ask questions across all {DATA.total} comment letters, then open the cited records and inspect the analysis. The SEC proposal would permit optional semiannual reporting (File No. S7-2026-15); this snapshot includes submissions posted through {humanDate(DATA.asOf)}.
        </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 (written up in depth)
          </div>
          <div className="cover-meta-item cover-meta-item--mobile-optional">
            <strong>{DATA.tiers.find(t => t.id === 'retail').count}</strong>
            Retail-tier (brief summary)
          </div>
          <div className="cover-meta-item cover-meta-item--mobile-optional">
            <strong>{DATA.themes.length}</strong>
            Published 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">Docket summary</div>
        <h2 className="section-h">Docket findings</h2>
        <div className="exec">
          {EDITORIAL_EXEC_SUMMARY.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>Each item receives one of five position labels based on the request it makes:</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> records an SEC staff meeting with outside parties</li>
            </ul>
          </div>

          <div className="method-card">
            <h3>Tier (how much editorial work?)</h3>
            <p>Tier measures what a letter <em>contributes</em>. A <strong>substantive</strong> letter comes from a named organization, applies relevant professional or governance experience, or adds evidence, research, or a concrete policy alternative to the common arguments. Length by itself does not qualify a letter, and a bare credential attached to a generic opinion stays retail.</p>
            <ul className="method-list">
              <li><strong>Substantive:</strong> presented with key quotes, themes, and the letter's distinctive contribution. Examples include a CFO, auditor, securities lawyer, professor, or analyst applying professional experience; a letter using UK or EU evidence; or a specific policy proposal.</li>
              <li><strong>Retail:</strong> recorded with a position, themes, and one-sentence summary. This tier includes long, well-argued letters that restate the common transparency or information-asymmetry case.</li>
              <li><strong>Meeting memo:</strong> SEC staff record of a meeting, tracked separately from comment letters.</li>
            </ul>
            <p>Empty, no-position submissions (e.g., a one-word "thanks") are excluded.</p>
          </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> a letter that substantially duplicates others in the file. The file contains {DATA.byLetterType.find(g => g.id === 'template').count} such letters across several near-duplicate families.</li>
            </ul>
          </div>

          <div className="method-card">
            <h3>Themes</h3>
            <p>Every letter is tagged with the themes it raises. The site shows themes that at least <strong>15 letters</strong> (about 1.8% of the file) touched, with editorial overrides for arguments central to the policy debate that fall below the cutoff: <em>short-termism</em> (the proposal's own rationale), its paired <em>short-termism rebuttal</em>, and <em>issuer burden</em>. Tags that describe who's writing or bare stance (rather than what they argue) are kept out of this list.</p>
            <p>The threshold rises with the size of the file. A two-letter cutoff surfaced 72 themes in early waves, and a six-letter cutoff produced 10 themes at 310 letters. At 818 letters, the cutoff rises to 15, and near-duplicate tags from earlier classification waves are merged. For example, <em>insider-advantage</em> now sits under <em>insider-trading</em>.</p>
          </div>
        </div>

        <div className="method-limits">
          <h3>Known limitations</h3>
          <ul>
            <li><strong>Pre-deadline snapshot.</strong> Of {DATA.total} letters as of {humanDate(DATA.asOf)}, the overwhelming majority are retail submissions, and the count has surged as the comment-period close on {humanDate(DATA.commentPeriodClose)} nears. Some institutional voices have begun to appear (e.g., SIFMA and Better Markets filed comment-period-extension requests), but several major industry-association substantive letters (CAQ, AICPA, ICI, Chamber CCMC, Big Four firms) may still be incoming before the close.</li>
            <li><strong>How position is coded.</strong> Position records what the letter asks the Commission to do: Object, Support, or Support&nbsp;with&nbsp;caveats. Persuasiveness and constituency are separate fields. When the request is ambiguous, the bottom-line recommendation controls.</li>
            <li><strong>How themes are coded.</strong> Theme assignment requires editorial judgment, and borderline letters can reasonably be handled differently. The frequency threshold limits the effect of those close calls on the published theme list.</li>
            <li><strong>How templates are identified.</strong> The {DATA.byLetterType.find(g => g.id === 'template').count} template labels mark substantial duplication within the file. A systematic similarity check across all {DATA.total} 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 by letter type</h2>
        <p className="section-lede">
          The 95% Object figure combines different kinds of submissions. In the substantive tier, opposition falls to 58%. Short retail and template or campaign letters account for most of the total imbalance.
        </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">Positions by constituency (53 substantive letters)</h2>
        <p className="section-lede">
          The 53 substantive letters split across six constituencies. Investors and Lawyers mostly Object; Auditors & Accounting and Preparers & Audit Committees are split (leaning Support / Support-with-caveats); Academics and Other span support and opposition. "Other" covers technologists, RegTech founders, banking-risk analysts, financial advisers, advocacy groups, and similar 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">Recurring themes ({DATA.themes.length})</h2>
        <p className="section-lede">
          Theme tags record the arguments raised in each letter. A theme appears here when at least <strong>six letters</strong> use it. <em>Short-termism</em>, the main argument among supporters, also appears because it occurs in five letters. Each card shows the letter count and position split; open one to see excerpts and the full set of matching letters.
        </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 or campaign; substantially 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 is one of <strong>{DATA.commenters.filter(x => x.templateLetter).length}</strong> coordinated template filings; it substantially duplicates other letters in the comment file.
            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 short SEC web-form submission appears with its position, themes, and a one-line summary. Substantive letters receive the longer write-ups. 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 <strong>meeting memo</strong> is an SEC staff record of a meeting with stakeholders. Meeting memos are tracked separately from comment letters.
            </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 (DRAFT)</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 site includes all {DATA.total} letters; {DATA.tiers.find(t => t.id === 'substantive').count} substantive-tier letters receive extended write-ups.
        </p>
        <p>
          Published by Nicholas Hallman.
        </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">
      <AnalysisMasthead />
      <main id="analysis-main">
        <Cover />
        <CommentFileCorpusChat
          docketId={DATA.docketId}
          letterCount={DATA.total}
          starterPrompts={CHAT_STARTERS}
          citationTargets={CHAT_CITATION_TARGETS}
          citationDemo="#016 James Parker"
          placeholder="Ask about positions, alternatives, or groups of commenters…"
          ariaLabel="Ask a question about the semiannual reporting comment file"
          sourceName="SEC.gov"
          onSelectLetter={setSelectedLetter}
        />
        <ExecSummary />
        <Methodology />
        <ByLetterType />
        <ByConstituency />
        <ThemesGrid onSelectTheme={setSelectedTheme} />
        <LetterAppendix onSelectLetter={setSelectedLetter} />
      </main>
      <Footer />
      <LetterDrawer letterRef={selectedLetter} onClose={() => setSelectedLetter(null)} />
      <ThemeDrawer
        themeId={selectedTheme}
        onClose={() => setSelectedTheme(null)}
        onSelectLetter={setSelectedLetter}
      />
    </div>
  );
}

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