/* global React, ReactDOM */
const { useState, useEffect, useMemo, useRef } = React;

// ---------------- Hash routing (full-page letter view) ----------------
function parseHash() {
  const h = (typeof window !== 'undefined' && window.location.hash) || '';
  const m = h.match(/^#letter\/(.+)$/);
  if (m) {
    try { return { type: 'letter', name: decodeURIComponent(m[1]) }; }
    catch (e) { return { type: 'home' }; }
  }
  return { type: 'home' };
}
function useHashRoute() {
  const [route, setRoute] = useState(() => parseHash());
  useEffect(() => {
    const handler = () => setRoute(parseHash());
    window.addEventListener('hashchange', handler);
    return () => window.removeEventListener('hashchange', handler);
  }, []);
  return route;
}

const D = window.PCAOB_DATA;
const Q = window.PCAOB_QUOTES || {};
const URLS = window.PCAOB_LETTER_URLS || {};
const THEMES = window.PCAOB_THEMES || {};
const CommentFileCorpusChat = window.CommentFileCorpusChat;
const CHAT_STARTERS = [
  'Where do investors and audit firms disagree about PCAOB inspections?',
  'What do commenters want the PCAOB to do about AI?',
  'Which changes to inspection reporting have the broadest support?',
  'What priorities did small and mid-sized firms emphasize?',
];
const CHAT_CITATION_TARGETS = Object.fromEntries(D.COMMENTERS.map((commenter) => [commenter.ref, commenter.name]));
const EDITORIAL_EXEC_SUMMARY = [
  "Commenters broadly favored a more modern inspection program, but investors disagreed about how far the Board should shift toward QC-system inspections. Better Markets and AFR Education Fund oppose a QC-only model as inconsistent with SOX. CII would support the shift if firm QC systems are publicly disclosed. MIAG, ICGN, and ICI would keep engagement testing until firms show that their systems work over time. Technology and AI also drew broad attention. The file contains repeated concerns about audit staffing as well.",
  "Inspection reporting draws broad support, but commenters disagree about its form. Some asked for raw data; others preferred contextualized, risk-based, or graded reporting. Maksymov et al.'s survey of 342 audit committee members found partner-specific engagement disclosures more useful than firm-level metrics.",
  "Accounting firms asked for a public enforcement manual modeled on the SEC's. Commenters offered two approaches to international alignment: an ISA baseline or a US GAAS baseline adjusted for PCAOB rules. Others asked for severity grades and a totality-of-evidence approach in Part I.A reporting.",
  "Technology proposals include separate AI guidance for firms and PCAOB inspectors. Cerex Advisory described AI document integrity as a distinct risk with no current professional framework. Senator Warren argued that SEC enforcement cuts have left PCAOB enforcement under-resourced.",
];
const EDITORIAL_PRIORITIES = {
  intro: "These six recommendations come from requests that recur across the letters.",
  recs: [
    { num: 1, title: 'Make inspection reports more useful.', body: 'Commenters asked for findings that investors and audit committees can act on, along with clearer explanations of what the findings mean.' },
    { num: 2, title: 'Address technology, AI, and QC 1000 in the standards agenda.', body: 'The letters repeatedly ask the Board to clarify QC 1000 implementation and respond to the use of technology and AI in audits and oversight.' },
    { num: 3, title: 'Bring stakeholders into the process earlier.', body: 'Commenters want the Board to seek views from different groups throughout strategic planning and standard setting.' },
    { num: 4, title: 'Explain choices about international alignment.', body: 'The Board should state where alignment with international auditing standards would improve investor protection and audit quality.' },
    { num: 5, title: 'Treat audit staffing as a strategic issue.', body: 'Audit committee chairs, accounting associations, and a multinational issuer raised concerns about the talent pipeline. The Audit Committee Council and Center for Audit Quality also linked inspection stress to attrition. The Board should examine how inspection design affects recruitment, retention, and practitioner morale.' },
    { num: 6, title: 'Create a faster route for time-sensitive guidance.', body: 'The Center for Audit Quality and the Texas Society of CPAs proposed an EITF-style process for technology, digital assets, AI, and other issues that move faster than the normal standard-setting cycle.' },
  ],
  outro: "This analysis will be updated when the PCAOB publishes its draft 2026–2030 Strategic Plan and standard-setting agenda.",
};
const GROUP_COLORS = {
  'investors':           'var(--g-investors)',
  'accounting':          'var(--g-accounting)',
  'audit-committee':     'var(--g-audit-committee)',
  'preparer':            'var(--g-preparer)',
  'technology-provider': 'var(--g-technology-provider)',
  'advisory-firm':       'var(--g-advisory-firm)',
  'regulators':          'var(--g-regulators)',
  'academic':            'var(--g-academic)',
  'other':               'var(--g-other)',
};
// Group display order — kept in sync with the bar chart (sorted by total letters descending).
const GROUP_ORDER = ['accounting', 'investors', 'audit-committee', 'preparer', 'technology-provider', 'advisory-firm', 'regulators', 'academic', 'other'];

const commenterByName = Object.fromEntries(D.COMMENTERS.map(c => [c.name, c]));

const MATRIX_SHORTS = {
  q1: 'Priorities',
  q2: 'QC 1000',
  q3: 'Reporting',
  q4: 'Standards',
  q5: 'Int\u2019l',
  q6: 'Tech & AI',
  q7: 'Transparency',
};

// Compact column-header labels for the coverage matrix. The longest group label
// ("Regulators & Standard-Setters") rotates past the panel's right edge and gets
// clipped; the full label is preserved as the head's hover title and everywhere else.
const GROUP_HEAD_SHORTS = {
  regulators: 'Regulators',
};

// Direct letter URLs from spreadsheet; fall back to PCAOB.org search if missing.
function letterUrlFor(name) {
  if (URLS[name]) return URLS[name];
  const q = encodeURIComponent(`site:pcaobus.org "PCAOB No. 2026-001" "${name}"`);
  return `https://www.google.com/search?q=${q}`;
}
function hasDirectLetter(name) { return !!URLS[name]; }

const QUOTE_PREVIEW = 3;

// ---------------- Top nav ----------------
function TopBar() {
  const [active, setActive] = useState('overview');
  const sections = [
    { id: 'overview',  label: 'Overview' },
    { id: 'ask-comment-file', label: 'Ask' },
    { id: 'dashboard', label: 'Dashboard' },
    { id: 'q1',        label: 'Q1' },
    { id: 'q2',        label: 'Q2' },
    { id: 'q3',        label: 'Q3' },
    { id: 'q4',        label: 'Q4' },
    { id: 'q5',        label: 'Q5' },
    { id: 'q6',        label: 'Q6' },
    { id: 'q7',        label: 'Q7' },
    { id: 'other-comments', label: 'Other Comments' },
    { id: 'looking-ahead',  label: 'Priorities' },
    { id: 'appendix',  label: 'Appendix' },
  ];

  useEffect(() => {
    const ids = sections.map(s => s.id);
    let rafId = null;
    function update() {
      rafId = null;
      const trigger = 120; // px from top of viewport
      let current = ids[0];
      for (const id of ids) {
        const el = document.getElementById(id);
        if (!el) continue;
        const top = el.getBoundingClientRect().top;
        if (top - trigger <= 0) current = id;
        else break;
      }
      setActive(prev => prev === current ? prev : current);
    }
    function onScroll() {
      if (rafId == null) rafId = requestAnimationFrame(update);
    }
    update();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      if (rafId != null) cancelAnimationFrame(rafId);
    };
  }, []);

  return (
    <nav className="topbar">
      <div className="topbar__inner">
        <a className="topbar__brand" href="../">
          <span className="b1">Independent analysis</span>
          Comment File
        </a>
        <div className="topbar__nav">
          <a href="../">All analyses</a>
          {sections.map(s => (
            <a key={s.id} href={`#${s.id}`} className={active === s.id ? 'is-active' : ''}>{s.label}</a>
          ))}
        </div>
        <button className="topbar__cta" onClick={() => window.print()}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M6 9V3h12v6M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
          Print / PDF
        </button>
      </div>
    </nav>
  );
}

// ---------------- Cover ----------------
function Cover() {
  return (
    <header id="overview" className="cover">
      <div className="cover__grid"></div>
      <div className="cover__inner">
        <div>
          <div className="cover__eyebrow">PCAOB · No. 2026-001 · Strategic Priorities</div>
          <h1>Comment Letter <em>Analysis</em><br/>2026–2030 Strategic Plan</h1>
          <p className="cover__sub">
            Ask questions across all 69 letters, then open the cited records. The analysis that follows organizes the file by question and stakeholder group, with excerpts from the filings.
          </p>
          <dl className="cover__meta">
            <dt>Published</dt><dd>May 2026</dd>
            <dt>Published by</dt><dd>Nicholas Hallman</dd>
            <dt>Comment period</dt><dd>Mar 31 — May 15, 2026</dd>
            <dt>Coverage</dt><dd>69 of 69 letters as of 2026-06-02</dd>
          </dl>
        </div>
        <div className="cover__hero-stat">
          <div className="cover__hero-num">69</div>
          <div className="cover__hero-label">Comment Letters in File</div>
          <div className="cover__hero-sub">
            From investors, accounting firms, preparers and audit committees, audit-technology and advisory firms, academics, regulators, and the public.
          </div>
        </div>
      </div>
    </header>
  );
}

// ---------------- Executive summary ----------------
function ExecSummary() {
  return (
    <section className="section section--paper" id="exec-summary">
      <div className="section__inner">
        <div className="section__label">Executive Summary</div>
        <h2 className="section__title">Where commenters agree</h2>
        <div className="exec">
          <div>
            <p className="section__lede" style={{margin: 0}}>
              The PCAOB received 69 letters from nine stakeholder groups. The file includes accounting firms,
              investors, audit committees, preparers, technology and advisory firms, academics, regulators,
              standard setters, and members of the public. Across the file, commenters asked the Board to modernize
              inspections and make its reporting more useful. AI and audit technology appeared in the most letters.
            </p>
          </div>
          <ul className="exec__bullets">
            {EDITORIAL_EXEC_SUMMARY.map((s, i) => <li key={i}>{s}</li>)}
          </ul>
        </div>
      </div>
    </section>
  );
}

// ---------------- Dashboard ----------------
function Dashboard() {
  const totalCovered = D.QUESTIONS.length;
  const themeMax = Math.max(...D.THEMES.map(t => t.weight));

  // matrix: rows = questions, cols = stakeholder groups,
  // value = share of each group's letters that substantively address this question (0-100).
  const groupTotals = useMemo(() => {
    const t = {};
    GROUP_ORDER.forEach(g => {
      const s = D.STAKEHOLDERS.find(x => x.id === g);
      t[g] = s ? s.count : 0;
    });
    return t;
  }, []);
  const matrix = useMemo(() => {
    return D.QUESTIONS.map(q => {
      const row = { id: q.id, num: q.num, short: q.short, cells: {} };
      GROUP_ORDER.forEach(g => {
        const n = (q.groups[g] && q.groups[g].commenters) ? q.groups[g].commenters.length : 0;
        const total = groupTotals[g] || 0;
        row.cells[g] = { count: n, pct: total ? Math.round((n / total) * 100) : 0 };
      });
      return row;
    });
  }, [groupTotals]);
  const matrixMax = 100; // percentages are already normalized 0-100

  return (
    <section className="section section--cream" id="dashboard">
      <div className="section__inner">
        <div className="section__label">File overview</div>
        <h2 className="section__title">Who answered, and what they covered</h2>
        <p className="section__lede">
          Nine stakeholder groups responded, and 36 commenters addressed all seven prompts. Technology and AI drew the broadest response. Inspection design, transparency, and standard setting also appeared often.
        </p>

        <div className="dash">
          <div className="stat">
            <div className="stat__label">Letters</div>
            <div className="stat__num">69</div>
            <div className="stat__sub">Included in this analysis</div>
          </div>
          <div className="stat">
            <div className="stat__label">Groups</div>
            <div className="stat__num">9</div>
            <div className="stat__sub">Stakeholder categories represented</div>
          </div>
          <div className="stat">
            <div className="stat__label">Prompts</div>
            <div className="stat__num">7</div>
            <div className="stat__sub">Numbered RFC questions analyzed</div>
          </div>
          <div className="stat">
            <div className="stat__label">Comprehensive</div>
            <div className="stat__num">36</div>
            <div className="stat__sub">Commenters addressed all 7 questions</div>
          </div>
        </div>

        <div className="dash-grid">
          {/* Left column: stakeholder breakdown + themes */}
          <div style={{display: 'flex', flexDirection: 'column', gap: 28, minWidth: 0}}>
          {/* Stakeholder breakdown */}
          <div className="panel">
            <h3 className="panel__title">Who responded</h3>
            <div className="panel__caption">Letters by stakeholder group · % of total</div>
            <div className="bars">
              {[...D.STAKEHOLDERS].sort((a,b) => b.count - a.count).map(s => {
                const max = Math.max(...D.STAKEHOLDERS.map(x => x.count));
                const w = (s.count / max) * 100;
                return (
                  <div key={s.id} className="bar-row">
                    <div className="bar-row__label">
                      <span className="bar-row__dot" style={{background: GROUP_COLORS[s.id]}}></span>
                      {s.label}
                    </div>
                    <div className="bar-row__track">
                      <div className="bar-row__fill" style={{width: `${w}%`, background: GROUP_COLORS[s.id]}}>
                        {s.share}%
                      </div>
                    </div>
                    <div className="bar-row__count">{s.count}</div>
                  </div>
                );
              })}
            </div>
            <p style={{fontSize: 13, color: 'var(--ink-4)', marginTop: 18, lineHeight: 1.45, marginBottom: 0}}>
              Accounting firms and associations submitted the most letters (22); Advisory Firms is the smallest group (3).
            </p>
          </div>

          {/* Themes */}
          <div className="panel">
            <h3 className="panel__title">Cross-cutting themes</h3>
            <div className="panel__caption">Topics surfaced across multiple letters</div>
            <div className="themes">
              {D.THEMES.map(t => (
                <button
                  key={t.label}
                  type="button"
                  className={`theme theme--btn ${t.weight >= 13 ? 'theme--xl' : ''}`}
                  onClick={() => window.openThemeDrawer && window.openThemeDrawer(t.id)}>
                  {t.label}
                  <span className="theme__count">{t.weight}</span>
                  <svg className="theme__caret" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="square"><path d="M9 18l6-6-6-6"/></svg>
                </button>
              ))}
            </div>
            <p style={{fontSize: 12, color: 'var(--ink-4)', marginTop: 14, fontFamily: 'var(--mono)', letterSpacing: '0.1em', textTransform: 'uppercase'}}>Click any theme to see relevant quotes from the letter file</p>
          </div>
          </div>

          {/* Coverage matrix */}
          <div className="panel">
            <h3 className="panel__title">Question coverage by group</h3>
            <div className="panel__caption">Share of each group's letters addressing each prompt</div>
            <div className="matrix">
              <div></div>
              {GROUP_ORDER.map(g => {
                const s = D.STAKEHOLDERS.find(x => x.id === g);
                return <div key={g} className="matrix__head" title={s.label}><span>{GROUP_HEAD_SHORTS[g] || s.label}</span></div>;
              })}
              {matrix.map(row => (
                <React.Fragment key={row.id}>
                  <div className="matrix__row-head">
                    <span className="matrix__row-q">Q{row.num}</span>
                    <span className="matrix__row-text">{MATRIX_SHORTS[row.id]}</span>
                  </div>
                  {GROUP_ORDER.map(g => {
                    const v = row.cells[g] || { count: 0, pct: 0 };
                    const intensity = v.count === 0 ? 0 : 0.25 + (v.pct / 100) * 0.75;
                    return (
                      <div key={g} className="matrix__cell">
                        <div className="matrix__chip" style={{
                          background: v.count === 0 ? 'var(--paper-2)' : GROUP_COLORS[g],
                          opacity: v.count === 0 ? 0.5 : intensity,
                          color: v.count === 0 ? 'var(--ink-4)' : '#fff',
                        }} title={`${v.count} of ${groupTotals[g]} letters in this group (${v.pct}%)`}>{v.count === 0 ? '—' : `${v.pct}%`}</div>
                      </div>
                    );
                  })}
                </React.Fragment>
              ))}
            </div>
            <p style={{fontSize: 13, color: 'var(--ink-4)', marginTop: 18, lineHeight: 1.45, marginBottom: 0}}>
              Each cell shows the share of that group's letters that substantively address each question; cell shading scales with the percentage. Hover for the count.
              Inspection priorities (Q1) and inspection-program changes (Q2) drew engagement from every group.
            </p>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------------- Commenter chip ----------------
function CChip({ name, focusQ }) {
  const c = commenterByName[name];
  if (!c) return <span className="cchip">{name}</span>;
  return (
    <button
      type="button"
      className="cchip"
      title={`${c.name} — ${c.sub} · Click for quotes & letter`}
      onClick={() => window.openCommenterDrawer && window.openCommenterDrawer(c.name, focusQ)}>
      <span className="cchip__dot" style={{background: GROUP_COLORS[c.group]}}></span>
      <span className="cchip__ref">#{c.ref}</span>
      {c.name}
    </button>
  );
}

// ---------------- Question section ----------------
function QuestionSection({ q, idx }) {
  // Default: no groups shown. 'all' = every group; <group-id> = just that group.
  const [filter, setFilter] = useState(null);
  // group counts available
  const groupCounts = GROUP_ORDER.map(g => ({
    id: g,
    label: D.STAKEHOLDERS.find(s => s.id === g).label,
    count: (q.groups[g] && q.groups[g].commenters) ? q.groups[g].commenters.length : 0,
  })).filter(g => g.count > 0);
  const totalCommenters = groupCounts.reduce((s,g) => s + g.count, 0);

  return (
    <section className="q" id={q.id} data-screen-label={`Q${q.num} ${q.short}`}>
      <div className="section__inner">
        <div className="q__head">
          <div className="q__num">{String(q.num).padStart(2, '0')}</div>
          <div className="q__title-block">
            <div className="q__kicker">Question {q.num} of 7</div>
            <h2 className="q__title">{q.short}</h2>
            <div className="q__prompt">
              <span className="q__prompt-label">PCAOB asked</span>
              {q.prompt}
            </div>
            <p className="q__topline">{q.topline}</p>
          </div>
        </div>

        <div className="filter" role="tablist" aria-label="Filter by stakeholder group">
          <span className="filter__label">View by group</span>
          <button
            className={`filter__chip ${filter === 'all' ? 'is-active' : ''}`}
            onClick={() => setFilter(filter === 'all' ? null : 'all')}>
            All groups <span className="filter__chip__count">{totalCommenters}</span>
          </button>
          {groupCounts.map(g => (
            <button
              key={g.id}
              className={`filter__chip ${filter === g.id ? 'is-active' : ''}`}
              onClick={() => setFilter(filter === g.id ? null : g.id)}
              style={filter === g.id ? {background: GROUP_COLORS[g.id], borderColor: GROUP_COLORS[g.id]} : {}}>
              <span className="filter__chip__dot" style={{background: GROUP_COLORS[g.id]}}></span>
              {g.label}
              <span className="filter__chip__count">{g.count}</span>
            </button>
          ))}
        </div>

        {filter === null ? (
          <div className="groups-empty">
            <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
            <span>Choose a stakeholder group above, or select <strong>All groups</strong> to compare the full set.</span>
          </div>
        ) : (
          <div className="groups">
            {GROUP_ORDER.map(gid => {
              const g = q.groups[gid];
              if (!g) return null;
              if (filter !== 'all' && filter !== gid) return null;
              const s = D.STAKEHOLDERS.find(x => x.id === gid);
              return (
                <article
                  key={gid}
                  className="gcard"
                  style={{borderTopColor: GROUP_COLORS[gid]}}>
                  <header className="gcard__head">
                    <h3 className="gcard__name">{s.label}</h3>
                    <span className="gcard__count">{g.commenters.length} {g.commenters.length === 1 ? 'commenter' : 'commenters'}</span>
                  </header>
                  <p className="gcard__intro">{g.intro}</p>
                  <div className="gcard__body">
                    {g.paras.map((p, i) => <p key={i}>{p}</p>)}
                  </div>
                  <div className="gcard__commenters">
                    <div className="gcard__commenters-label">Cited in this section</div>
                    <div className="gcard__commenters-list">
                      {g.commenters.map(c => <CChip key={c} name={c} focusQ={q.id} />)}
                    </div>
                  </div>
                </article>
              );
            })}
          </div>
        )}

        {q.convergence && (
          <div className="convergence">
            <div className="convergence__label">Convergence & divergence</div>
            <p className="convergence__body">{q.convergence}</p>
          </div>
        )}

        <figure className="pullq">
          <blockquote className="pullq__text">{q.quote.text}</blockquote>
          <div className="pullq__attr">{q.quote.attr}</div>
        </figure>
      </div>
    </section>
  );
}

// ---------------- Other comments ----------------
function OtherComments() {
  return (
    <section className="section section--paper" id="other-comments">
      <div className="section__inner">
        <div className="section__label">Beyond the seven prompts</div>
        <h2 className="section__title">Other comments raised outside the PCAOB's questions.</h2>
        <p className="section__lede">{D.OTHER_COMMENTS.intro}</p>
        <div className="other-grid">
          {D.OTHER_COMMENTS.sections.map((s, i) => (
            <article key={i} className="other-card">
              <h3>{s.title}</h3>
              <p>{s.body}</p>
              <div className="gcard__commenters" style={{borderTop: 'none', paddingTop: 0, marginTop: 0}}>
                <div className="gcard__commenters-label">Raised by</div>
                <div className="gcard__commenters-list">
                  {s.commenters.map(c => <CChip key={c} name={c} focusQ="other" />)}
                </div>
              </div>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

// ---------------- Looking ahead ----------------
function LookingAhead() {
  return (
    <section className="section section--navy" id="looking-ahead">
      <div className="section__inner">
        <div className="section__label">Priorities from the letters</div>
        <h2 className="section__title">Six recommendations for the strategic plan</h2>
        <p className="section__lede">{EDITORIAL_PRIORITIES.intro}</p>
        <div className="la-grid">
          {EDITORIAL_PRIORITIES.recs.map(r => (
            <article key={r.num} className="la-card">
              <div className="la-card__num">{String(r.num).padStart(2, '0')}</div>
              <h3 className="la-card__title">{r.title}</h3>
              <p className="la-card__body">{r.body}</p>
            </article>
          ))}
        </div>
        <p className="la-outro">{EDITORIAL_PRIORITIES.outro}</p>
      </div>
    </section>
  );
}

// ---------------- Appendix ----------------
function Appendix() {
  const [q, setQ] = useState('');
  const [grp, setGrp] = useState('all');

  const filtered = useMemo(() => {
    return D.COMMENTERS.filter(c => {
      if (grp !== 'all' && c.group !== grp) return false;
      if (q && !(c.name.toLowerCase().includes(q.toLowerCase()) || c.sub.toLowerCase().includes(q.toLowerCase()))) return false;
      return true;
    });
  }, [q, grp]);

  function downloadCSV() {
    const rows = [['Ref', 'Commenter', 'Stakeholder Group', 'Sub-Category', 'Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6', 'Q7']];
    D.COMMENTERS.forEach(c => {
      const cov = D.COVERAGE[c.name] || [];
      const groupLabel = D.STAKEHOLDERS.find(s => s.id === c.group).label;
      rows.push([
        c.ref, c.name, groupLabel, c.sub,
        ...['q1','q2','q3','q4','q5','q6','q7'].map(qid => cov.includes(qid) ? '✓' : ''),
      ]);
    });
    const csv = rows.map(r => r.map(v => {
      const s = String(v);
      return s.includes(',') || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
    }).join(',')).join('\n');
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = 'pcaob-strategic-priorities-commenters.csv';
    a.click();
    URL.revokeObjectURL(url);
  }

  return (
    <section className="section section--white" id="appendix">
      <div className="section__inner">
        <div className="section__label">Appendix</div>
        <h2 className="section__title">All 69 commenters.</h2>
        <p className="section__lede">
          Filter by stakeholder group or search by name. The coverage columns show which of the seven RFC
          prompts each commenter substantively addressed.
        </p>

        <div className="appendix-toolbar">
          <input
            className="appendix-search"
            type="search"
            placeholder="Search commenters or sub-categories…"
            value={q}
            onChange={e => setQ(e.target.value)}
          />
          <button className={`filter__chip ${grp === 'all' ? 'is-active' : ''}`} onClick={() => setGrp('all')}>
            All
            <span className="filter__chip__count">{D.COMMENTERS.length}</span>
          </button>
          {GROUP_ORDER.map(gid => {
            const s = D.STAKEHOLDERS.find(x => x.id === gid);
            const active = grp === gid;
            return (
              <button
                key={gid}
                className={`filter__chip ${active ? 'is-active' : ''}`}
                onClick={() => setGrp(active ? 'all' : gid)}
                style={active ? {background: GROUP_COLORS[gid], borderColor: GROUP_COLORS[gid]} : {}}>
                <span className="filter__chip__dot" style={{background: GROUP_COLORS[gid]}}></span>
                {s.label}
                <span className="filter__chip__count">{s.count}</span>
              </button>
            );
          })}
          <button className="topbar__cta" style={{marginLeft: 'auto'}} onClick={downloadCSV}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M12 3v14m0 0l-5-5m5 5l5-5M5 21h14"/></svg>
            Download CSV
          </button>
        </div>

        <table className="atable">
          <thead>
            <tr>
              <th>Ref</th>
              <th>Commenter</th>
              <th>Group</th>
              <th>Sub-category</th>
              <th>Question Coverage</th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(c => {
              const groupLabel = D.STAKEHOLDERS.find(s => s.id === c.group).label;
              const cov = D.COVERAGE[c.name] || [];
              return (
                <tr key={c.ref}>
                  <td className="ref">#{String(c.ref).padStart(2, '0')}</td>
                  <td className="name">
                    <button type="button" className="cchip" style={{padding:'4px 10px', background:'transparent', border:'none'}} onClick={() => window.openCommenterDrawer && window.openCommenterDrawer(c.name)}>
                      {c.name}
                    </button>
                  </td>
                  <td>
                    <span className="group-pill" style={{background: `${GROUP_COLORS[c.group]}22`, color: GROUP_COLORS[c.group]}}>
                      <span className="bar-row__dot" style={{background: GROUP_COLORS[c.group]}}></span>
                      {groupLabel}
                    </span>
                  </td>
                  <td style={{color: 'var(--ink-3)'}}>
                    {c.sub}
                    {c.formerPCAOB && <span className="pcaob-tag" title={`Former PCAOB: ${c.formerPCAOB}`}>★ Former PCAOB</span>}
                  </td>
                  <td>
                    <div className="coverage-cells">
                      {['q1','q2','q3','q4','q5','q6','q7'].map(qid => (
                        <span key={qid} className={`cov-cell ${cov.includes(qid) ? 'on' : 'off'}`} title={`Q${qid.slice(1)}`}>
                          {qid.slice(1)}
                        </span>
                      ))}
                    </div>
                  </td>
                </tr>
              );
            })}
            {filtered.length === 0 && (
              <tr><td colSpan="5" style={{textAlign: 'center', color: 'var(--ink-4)', padding: 32}}>No commenters match this filter.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </section>
  );
}

// ---------------- Footer ----------------
function Footer() {
  return (
    <footer className="footer">
      <div className="footer__inner">
        <div>
          <h4>Comment Letter Analysis · PCAOB No. 2026-001</h4>
          <p style={{margin: 0, maxWidth: '60ch'}}>
            Published by
            {' '}<strong style={{color: 'var(--cream)'}}>Nicholas Hallman</strong>. This is a draft analysis of public comment letters
            submitted in response to the PCAOB's Request for Public Comment on its Strategic Priorities
            informing the 2026–2030 Strategic Plan.
          </p>
        </div>
        <div style={{textAlign: 'right'}}>
          <h4>Source materials</h4>
          <p style={{margin: 0}}>
            <a href="https://pcaobus.org/news-events/news-releases/news-release-detail/pcaob-requests-public-comment-on-strategic-priorities" target="_blank" rel="noreferrer">PCAOB Request for Public Comment</a><br/>
            Comment period: Mar 31 – May 15, 2026
          </p>
        </div>
      </div>
    </footer>
  );
}

// ---------------- Quotes list with cap + show-all ----------------
function QuotesList({ quotes }) {
  const [expanded, setExpanded] = useState(false);
  const showAll = expanded || quotes.length <= QUOTE_PREVIEW;
  const visible = showAll ? quotes : quotes.slice(0, QUOTE_PREVIEW);
  const extra = quotes.length - QUOTE_PREVIEW;
  return (
    <>
      <div className="drawer__quotes-label">
        {quotes.length === 1 ? 'Direct quote from the letter' : 'Direct quotes from the letter'}
        {!showAll && <span className="drawer__quotes-count"> · showing top {QUOTE_PREVIEW} of {quotes.length}</span>}
      </div>
      <ol className="drawer__quotes">
        {visible.map((q, i) => <li key={i}>“{q}”</li>)}
      </ol>
      {quotes.length > QUOTE_PREVIEW && (
        <button type="button" className="drawer__show-more" onClick={() => setExpanded(e => !e)}>
          {expanded ? `− Show fewer` : `+ Show ${extra} more ${extra === 1 ? 'quote' : 'quotes'}`}
        </button>
      )}
    </>
  );
}

// ---------------- Commenter drawer ----------------
function Drawer({ state, onClose }) {
  const bodyRef = useRef(null);
  const open = !!state;

  useEffect(() => {
    if (open) document.body.classList.add('drawer-open');
    else document.body.classList.remove('drawer-open');
  }, [open]);

  useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    if (open) window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  // Scroll-to-focus when opened with a specific question
  useEffect(() => {
    if (!open || !state.focusQ) return;
    const t = setTimeout(() => {
      const el = bodyRef.current?.querySelector(`[data-q="${state.focusQ}"]`);
      if (el) el.scrollIntoView({ block: 'start', behavior: 'instant' });
    }, 380); // wait for slide-in to settle
    return () => clearTimeout(t);
  }, [open, state]);

  const c = state && commenterByName[state.name];
  const qdata = state && Q[state.name];
  const groupLabel = c && D.STAKEHOLDERS.find(s => s.id === c.group).label;

  return (
    <>
      <div className={`drawer-backdrop ${open ? 'is-open' : ''}`} onClick={onClose}></div>
      <aside className={`drawer drawer--fullscreen ${open ? 'is-open' : ''}`} role="dialog" aria-modal="true" aria-label="Commenter detail">
        {c && (
          <>
            <header className="drawer__header">
              <button className="drawer__close" onClick={onClose} aria-label="Close">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M18 6L6 18M6 6l12 12"/></svg>
              </button>
              <div className="drawer__ref">
                <span className="drawer__ref-num">#{String(c.ref).padStart(2,'0')}</span>
                Commenter
              </div>
              <h3 className="drawer__name">{c.name}</h3>
              <div className="drawer__meta">
                <span className="drawer__meta-pill">
                  <span style={{width:8, height:8, background: GROUP_COLORS[c.group], display:'inline-block', borderRadius: 2}}></span>
                  {groupLabel}
                </span>
                {c.sub && <span className="drawer__meta-pill">{c.sub}</span>}
                {c.formerPCAOB && <span className="drawer__meta-pill drawer__meta-pill--pcaob" title={`Former PCAOB: ${c.formerPCAOB}`}>★ Former PCAOB</span>}
                {qdata?.date && <span className="drawer__meta-pill">Submitted {qdata.date}</span>}
              </div>
              <a className="drawer__link" href={letterUrlFor(c.name)} target="_blank" rel="noreferrer">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M14 3h7v7m0-7L10 14M19 14v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6"/></svg>
                {hasDirectLetter(c.name) ? 'Read the letter (PDF) on PCAOB.org' : 'Find letter on PCAOB.org'}
              </a>
            </header>
            <div className="drawer__body" ref={bodyRef}>
              {qdata?.caqSummary && (
                <div className="drawer__caq">
                  <div className="drawer__caq-label">Summary of this letter</div>
                  <p className="drawer__caq-body">{qdata.caqSummary}</p>
                </div>
              )}
              {D.QUESTIONS.map(q => {
                const entry = qdata?.questions?.[q.id] || { summary: '', quotes: [] };
                const addressed = entry.summary || entry.quotes.length;
                const highlight = state.focusQ === q.id;
                return (
                  <section key={q.id} className={`drawer__q ${highlight ? 'is-highlight' : ''}`} data-q={q.id}>
                    <div className="drawer__q-head">
                      <span className="drawer__q-num">Q{q.num}</span>
                      <h4 className="drawer__q-title">{q.short}</h4>
                    </div>
                    {addressed ? (
                      <>
                        {entry.summary && <p className="drawer__summary">{entry.summary}</p>}
                        {entry.quotes.length > 0 && <QuotesList quotes={entry.quotes} />}
                      </>
                    ) : (
                      <div className="drawer__nomention">— Not addressed in this letter</div>
                    )}
                  </section>
                );
              })}
              {qdata?.other && (qdata.other.summary || qdata.other.quotes.length) ? (
                <section className="drawer__q" data-q="other">
                  <div className="drawer__q-head">
                    <span className="drawer__q-num" style={{color:'var(--navy-700)', borderColor:'var(--navy-700)'}}>OTH</span>
                    <h4 className="drawer__q-title">Other comments</h4>
                  </div>
                  {qdata.other.summary && <p className="drawer__summary">{qdata.other.summary}</p>}
                  {qdata.other.quotes.length > 0 && <QuotesList quotes={qdata.other.quotes} />}
                </section>
              ) : null}
            </div>
          </>
        )}
      </aside>
    </>
  );
}

// ---------------- Theme drawer ----------------
function ThemeDrawer({ state, onClose }) {
  const open = !!state;
  useEffect(() => {
    if (open) document.body.classList.add('drawer-open');
    return () => document.body.classList.remove('drawer-open');
  }, [open]);
  useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    if (open) window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  const theme = state && THEMES[state.themeId];

  return (
    <>
      <div className={`drawer-backdrop ${open ? 'is-open' : ''}`} onClick={onClose}></div>
      <aside className={`drawer ${open ? 'is-open' : ''}`} role="dialog" aria-modal="true" aria-label="Theme detail">
        {theme && (
          <>
            <header className="drawer__header">
              <button className="drawer__close" onClick={onClose} aria-label="Close">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M18 6L6 18M6 6l12 12"/></svg>
              </button>
              <div className="drawer__ref">
                <span className="drawer__ref-num" style={{background: 'var(--gold-soft)'}}>THEME</span>
                Cross-cutting topic
              </div>
              <h3 className="drawer__name">{theme.label}</h3>
            </header>
            <div className="drawer__body">
              <p style={{fontFamily: 'var(--serif)', fontSize: 16, color: 'var(--ink-3)', fontStyle: 'italic', margin: '0 0 24px', lineHeight: 1.5}}>
                A handful of the most direct quotes from the letter file on this theme. Click a commenter's name to open their full letter view.
              </p>
              <ul className="theme-quotes">
                {theme.quotes.map((q, i) => {
                  const c = commenterByName[q.name];
                  return (
                    <li key={i}>
                      <p className="theme-quotes__text">“{q.text}”</p>
                      <button
                        type="button"
                        className="theme-quotes__attr"
                        onClick={() => {
                          onClose();
                          setTimeout(() => window.openCommenterDrawer && window.openCommenterDrawer(q.name, q.qid === 'other' ? null : q.qid), 200);
                        }}>
                        <span className="theme-quotes__attr-dot" style={{background: c ? GROUP_COLORS[c.group] : 'var(--ink-4)'}}></span>
                        {q.name}
                        <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M9 18l6-6-6-6"/></svg>
                      </button>
                    </li>
                  );
                })}
              </ul>
            </div>
          </>
        )}
      </aside>
    </>
  );
}

// ---------------- App ----------------
// ---------------- Full-page letter view ----------------
function LetterPage({ name }) {
  const c = commenterByName[name];
  const qdata = Q[name];
  const groupLabel = c && D.STAKEHOLDERS.find(s => s.id === c.group)?.label;

  useEffect(() => {
    document.body.classList.add('letter-page-mode');
    return () => document.body.classList.remove('letter-page-mode');
  }, []);

  if (!c) {
    return (
      <main className="letter-page">
        <div className="letter-page__inner">
          <a className="letter-page__back" href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }}>← Back to overview</a>
          <h1 className="letter-page__notfound">Letter not found</h1>
          <p>No commenter matches "{name}". <a href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }}>Return to the overview.</a></p>
        </div>
      </main>
    );
  }

  return (
    <main className="letter-page">
      <div className="letter-page__inner">
        <a className="letter-page__back" href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }}>← Back to overview</a>
        <header className="letter-page__header">
          <div className="letter-page__ref">
            <span className="letter-page__ref-num">#{String(c.ref).padStart(2,'0')}</span>
            Commenter
          </div>
          <h1 className="letter-page__name">{c.name}</h1>
          <div className="letter-page__meta">
            <span className="drawer__meta-pill">
              <span style={{width:8, height:8, background: GROUP_COLORS[c.group], display:'inline-block', borderRadius: 2}}></span>
              {groupLabel}
            </span>
            {c.sub && <span className="drawer__meta-pill">{c.sub}</span>}
            {c.formerPCAOB && <span className="drawer__meta-pill drawer__meta-pill--pcaob" title={`Former PCAOB: ${c.formerPCAOB}`}>★ Former PCAOB</span>}
            {qdata?.date && <span className="drawer__meta-pill">Submitted {qdata.date}</span>}
          </div>
          <a className="letter-page__link" href={letterUrlFor(c.name)} target="_blank" rel="noreferrer">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="square"><path d="M14 3h7v7m0-7L10 14M19 14v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6"/></svg>
            {hasDirectLetter(c.name) ? 'Read the letter (PDF) on PCAOB.org' : 'Find letter on PCAOB.org'}
          </a>
        </header>

        <article className="letter-page__body">
          {qdata?.caqSummary && (
            <section className="letter-page__caq">
              <div className="letter-page__caq-label">Summary of this letter</div>
              <p className="letter-page__caq-body">{qdata.caqSummary}</p>
            </section>
          )}
          {D.QUESTIONS.map(q => {
            const entry = qdata?.questions?.[q.id] || { summary: '', quotes: [] };
            const addressed = entry.summary || entry.quotes.length;
            return (
              <section key={q.id} className="letter-page__q">
                <div className="letter-page__q-head">
                  <span className="letter-page__q-num">Q{q.num}</span>
                  <h2 className="letter-page__q-title">{q.short}</h2>
                </div>
                {addressed ? (
                  <>
                    {entry.summary && <p className="letter-page__summary">{entry.summary}</p>}
                    {entry.quotes.length > 0 && (
                      <ol className="letter-page__quotes">
                        {entry.quotes.map((quote, i) => <li key={i}>{quote}</li>)}
                      </ol>
                    )}
                  </>
                ) : (
                  <p className="letter-page__not-addressed">This question was not substantively addressed in the letter.</p>
                )}
              </section>
            );
          })}
        </article>

        <footer className="letter-page__footer">
          <a className="letter-page__back" href="#" onClick={(e) => { e.preventDefault(); window.location.hash = ''; }}>← Back to overview</a>
        </footer>
      </div>
    </main>
  );
}

function App() {
  const route = useHashRoute();
  const [drawer, setDrawer] = useState(null);
  const [themeDrawer, setThemeDrawer] = useState(null);
  useEffect(() => {
    window.openCommenterDrawer = (name, focusQ) => setDrawer({ name, focusQ });
    window.openThemeDrawer = (themeId) => setThemeDrawer({ themeId });
    return () => { delete window.openCommenterDrawer; delete window.openThemeDrawer; };
  }, []);

  if (route.type === 'letter') {
    return <><TopBar /><LetterPage name={route.name} /></>;
  }

  return (
    <>
      <TopBar />
      <main id="analysis-main">
        <Cover />
        <CommentFileCorpusChat
          docketId="strategic-priorities-2026"
          letterCount={D.COMMENTERS.length}
          starterPrompts={CHAT_STARTERS}
          citationTargets={CHAT_CITATION_TARGETS}
          citationDemo="#15 Center for Audit Quality"
          placeholder="Ask about a priority, stakeholder group, or commenter…"
          ariaLabel="Ask a question about the PCAOB strategic priorities comment file"
          sourceName="PCAOB.org"
          onSelectLetter={(name) => setDrawer({ name, focusQ: null })}
        />
        <ExecSummary />
        <Dashboard />
        {D.QUESTIONS.map((q, i) => <QuestionSection key={q.id} q={q} idx={i} />)}
        <OtherComments />
        <LookingAhead />
        <Appendix />
      </main>
      <Footer />
      <Drawer state={drawer} onClose={() => setDrawer(null)} />
      <ThemeDrawer state={themeDrawer} onClose={() => setThemeDrawer(null)} />
    </>
  );
}

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