// Analysis detail — mirrors apps/web/src/app/(dashboard)/analyses/[id]/page.tsx.
// Shows: header w/ decision badge + prices, executive summary, bull vs bear,
// key metrics, risk factors, reports download, approve/reject (if BUY + pending).

window.AnalysisDetail = function AnalysisDetail({ analysisId, goto, session, isElevated }) {
  const { useState, useMemo } = React;
  // Frontend-only authorization gate (mirrors dashboard / analyses pages)
  const canTrigger = isElevated === true
    ? true
    : (window.EQ_IS_AUTHORIZED ? window.EQ_IS_AUTHORIZED(session) : false);
  const [cancelBusy, setCancelBusy] = useState(false);
  const [cancelError, setCancelError] = useState(null);
  const detail = window.useApi(
    () => analysisId ? window.EQ_API.getAnalysis(analysisId) : Promise.resolve(null),
    [analysisId]
  );
  const reports = window.useApi(
    () => analysisId ? window.EQ_API.getAnalysisReports(analysisId) : Promise.resolve({ data: [] }),
    [analysisId]
  );
  const approvals = window.useApi(
    () => window.EQ_API.getApprovals('pending'),
    []
  );
  const [actionBusy, setActionBusy] = useState(false);
  const [actionError, setActionError] = useState(null);
  const [rerunBusy, setRerunBusy] = useState(false);
  const [rerunMsg, setRerunMsg] = useState(null);
  // Per-report refresh state: { [file_type]: { busy, url, error } }
  const [refreshState, setRefreshState] = useState({});
  // Email modal state
  const [emailOpen, setEmailOpen] = useState(false);
  const [emailRecipients, setEmailRecipients] = useState([]);
  const [emailBusy, setEmailBusy] = useState(false);
  const [emailMsg, setEmailMsg] = useState(null);
  const users = window.useApi(() => window.EQ_API.getUsers(), []);


  if (!analysisId) {
    return (
      <div>
        <window.TopBar title="Analysis" right={<button onClick={() => goto('analyses')} style={linkBtn}>← Back to analyses</button>}/>
        <div style={{ padding: 32 }}><window.EmptyState title="No analysis selected"/></div>
      </div>
    );
  }

  const a = detail.data;
  const pendingApproval = useMemo(() => {
    if (!a || !approvals.data?.data) return null;
    return approvals.data.data.find(ap => ap.analysis_id === a.id) || null;
  }, [a, approvals.data]);

  const approve = async () => {
    if (!pendingApproval) return;
    setActionBusy(true); setActionError(null);
    try { await window.EQ_API.approveApproval(pendingApproval.id); approvals.reload(); }
    catch (e) { setActionError(e.message); }
    finally { setActionBusy(false); }
  };
  const reject = async () => {
    if (!pendingApproval) return;
    setActionBusy(true); setActionError(null);
    try { await window.EQ_API.rejectApproval(pendingApproval.id); approvals.reload(); }
    catch (e) { setActionError(e.message); }
    finally { setActionBusy(false); }
  };

  const rerunAnalysis = async () => {
    if (!a?.ticker || rerunBusy) return;
    setRerunBusy(true); setRerunMsg(null);
    try {
      const res = await window.EQ_API.triggerAnalysis(a.ticker);
      setRerunMsg({ ok: true, text: 'New analysis queued — ID ' + res.id });
      setTimeout(() => goto('analyses'), 1800);
    } catch (e) {
      setRerunMsg({ ok: false, text: e.message || 'Failed to trigger analysis' });
      setRerunBusy(false);
    }
  };

  const refreshReport = async (fileType) => {
    if (!a?.id || refreshState[fileType]?.busy) return;
    setRefreshState(s => ({ ...s, [fileType]: { busy: true, url: null, error: null } }));
    try {
      const res = await window.EQ_API.generateReport(a.id, fileType);
      setRefreshState(s => ({ ...s, [fileType]: { busy: false, url: res.download_url, error: null } }));
      // Trigger download automatically
      if (res.download_url) {
        const link = document.createElement('a');
        link.href = res.download_url;
        link.target = '_blank';
        link.rel = 'noopener';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      }
      reports.reload();
    } catch (e) {
      setRefreshState(s => ({ ...s, [fileType]: { busy: false, url: null, error: e.message || 'Regeneration failed' } }));
    }
  };

  const sendEmail = async () => {
    if (!a?.id || !emailRecipients.length) return;
    setEmailBusy(true); setEmailMsg(null);
    try {
      await window.EQ_API.sendAnalysisEmail(a.id, emailRecipients);
      setEmailMsg({ ok: true, text: `Email sent to ${emailRecipients.join(', ')}` });
      setTimeout(() => { setEmailOpen(false); setEmailMsg(null); setEmailRecipients([]); }, 2500);
    } catch (e) {
      setEmailMsg({ ok: false, text: e.message || 'Failed to send email' });
    } finally {
      setEmailBusy(false);
    }
  };

  const isMobile = window.innerWidth < 768;
  const px = isMobile ? 12 : 32;

  // ── PDF viewer (memo PDF) — rendered both inline (desktop split) and as
  // a fall-back below on mobile. Header styling mirrors Card / CardWithAction
  // for visual consistency with the Executive Summary card. mode='inline'
  // fills 100% of its parent (height comes from the split row's stretch);
  // mode='block' uses a fixed 820px height (legacy bottom-of-page layout).
  const renderPdfViewer = (mode) => {
    const memoPdfEntry = reports.data?.data?.find(r => (r.file_type || r.type) === 'memo_pdf');
    const refreshedUrl = refreshState['memo_pdf']?.url;
    const pdfUrl = refreshedUrl || (memoPdfEntry ? (memoPdfEntry.download_url || memoPdfEntry.signed_url || memoPdfEntry.url) : null);
    const pdfGenerating = refreshState['memo_pdf']?.busy;
    const pdfError = refreshState['memo_pdf']?.error;
    const canGenerate = a?.status === 'completed';
    if (!pdfUrl && !canGenerate) return null;
    const inline = mode === 'inline';

    // Card shell — same colors/border/radius as window.Card / CardWithAction
    const shellStyle = {
      background: 'var(--paper)',
      border: '1px solid var(--ink-150)',
      borderRadius: 12,
      overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
      width: '100%',
      ...(inline ? { flex: 1, minHeight: 480 } : { height: 820 }),
    };
    // Header — matches CardWithAction exactly (padding, font, casing, etc.)
    const headerStyle = {
      padding: '10px 18px',
      borderBottom: '1px solid var(--ink-150)',
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      flexShrink: 0,
    };
    const titleStyle = {
      fontSize: 12, fontWeight: 700, color: 'var(--ink-900)',
      textTransform: 'uppercase', letterSpacing: '0.06em',
    };
    // Body fills the rest of the card; iframe extends edge-to-edge (no padding)
    const bodyStyle = {
      flex: 1, display: 'flex', flexDirection: 'column',
      minHeight: 0, background: 'var(--ink-50)',
    };

    return (
      <section style={shellStyle}>
        <div style={headerStyle}>
          <span style={titleStyle}>Investment Memo</span>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            {pdfUrl && (
              <a href={pdfUrl} target="_blank" rel="noopener" style={{
                fontSize: 11.5, color: 'var(--ink-600)', textDecoration: 'none',
              }}>Open full PDF ↗</a>
            )}
            {canGenerate && (
              <button
                onClick={() => refreshReport('memo_pdf')}
                disabled={!!pdfGenerating}
                style={{ ...secondaryBtn, fontSize: 11.5, padding: '5px 10px', opacity: pdfGenerating ? 0.6 : 1 }}
              >
                {pdfGenerating ? 'Generating…' : pdfUrl ? 'Regenerate' : 'Generate PDF'}
              </button>
            )}
          </div>
        </div>
        <div style={bodyStyle}>
          {pdfError && (
            <div style={{ padding: '10px 18px' }}>
              <window.ErrorBanner error={{ message: pdfError }}/>
            </div>
          )}
          {pdfUrl ? (
            <iframe
              src={pdfUrl}
              style={{ flex: 1, width: '100%', border: 'none', display: 'block', minHeight: 0 }}
              title="Investment Memo PDF"
            />
          ) : canGenerate && !pdfGenerating ? (
            <div style={{ flex: 1, padding: '32px 24px', textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <div style={{ fontSize: 13, color: 'var(--ink-500)' }}>No memo PDF yet — click "Generate PDF" to create one from the saved pipeline state.</div>
            </div>
          ) : pdfGenerating ? (
            <div style={{ flex: 1, padding: '32px 24px', textAlign: 'center', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <div style={{ fontSize: 13, color: 'var(--ink-500)' }}>Generating PDF memo — this takes ~60 seconds…</div>
            </div>
          ) : null}
        </div>
      </section>
    );
  };

  return (
    <div>
      <window.TopBar
        title={a ? `${a.ticker} ${a.company_name && a.company_name !== a.ticker ? '· ' + a.company_name : ''}` : 'Analysis'}
        subtitle={a ? `Triggered ${window.formatRelative(a.created_at)} by ${a.triggered_by || '—'}` : detail.loading ? 'Loading…' : null}
        right={
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            {a?.decision && <window.DecisionBadge decision={a.decision} size="lg"/>}
            {/* Cancel — visible only when analysis is currently running or queued */}
            {a && (a.status === 'running' || a.status === 'queued') && (
              <button
                onClick={async () => {
                  if (!canTrigger) return;
                  if (!window.confirm(`Cancel analysis for ${a.ticker}? This stops the pipeline and marks the row as cancelled.`)) return;
                  setCancelBusy(true); setCancelError(null);
                  try { await window.EQ_API.cancelAnalysis(a.id); detail.reload(); }
                  catch (e) { setCancelError(e.message || 'Cancel failed'); }
                  finally { setCancelBusy(false); }
                }}
                disabled={!canTrigger || cancelBusy}
                title={!canTrigger ? 'Restricted — contact your administrator to cancel analyses.' : 'Cancel this analysis'}
                style={{
                  padding: '7px 14px', fontSize: 12.5, fontWeight: 500,
                  background: !canTrigger ? 'var(--ink-100)' : 'var(--pass-soft)',
                  color: !canTrigger ? 'var(--ink-400)' : 'var(--pass)',
                  border: '1px solid ' + (!canTrigger ? 'var(--ink-200)' : 'var(--pass)'),
                  borderRadius: 7,
                  cursor: !canTrigger || cancelBusy ? 'not-allowed' : 'pointer',
                  opacity: !canTrigger ? 0.6 : 1,
                  fontFamily: 'inherit',
                }}
              >{cancelBusy ? 'Cancelling…' : 'Cancel'}</button>
            )}
            {a && (
              <button
                onClick={rerunAnalysis}
                disabled={rerunBusy}
                title={'Re-run analysis for ' + a.ticker}
                style={{ ...secondaryBtn, display: 'inline-flex', alignItems: 'center', gap: 6, opacity: rerunBusy ? 0.6 : 1 }}
              >
                {/* refresh icon */}
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none">
                  <path d="M4 12a8 8 0 0114.93-4H15m3.93 4A8 8 0 019 19.93V17" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
                {!isMobile && (rerunBusy ? 'Queuing…' : 'Re-run')}
              </button>
            )}
            <button onClick={() => goto('analyses')} style={linkBtn}>← Back</button>
          </div>
        }
      />

      <div style={{ padding: `20px ${px}px`, display: 'flex', flexDirection: 'column', gap: 16 }}>
        {detail.error && <window.ErrorBanner error={detail.error} onRetry={detail.reload}/>}
        {cancelError && <window.ErrorBanner error={{ message: 'Cancel failed: ' + cancelError }}/>}
        {detail.loading && <div style={{ color: 'var(--ink-500)', fontSize: 12.5 }}>Loading…</div>}

        {a && <>
          {/* Top stat row */}
          <section style={{ display: 'grid', gridTemplateColumns: isMobile ? 'repeat(2, 1fr)' : 'repeat(5, 1fr)', gap: 12 }}>
            <window.StatCard label="Status"        value={<window.StatusBadge status={a.status}/>} />
            <window.StatCard label="Price at analysis" value={window.formatPrice(a.price_at_analysis)}/>
            <window.StatCard label="Price target"      value={window.formatPrice(a.price_target)}/>
            <window.StatCard label="Upside"             value={window.formatPct(a.upside_pct)}/>
            <window.StatCard label="Duration"
              value={a.duration_seconds != null ? (a.duration_seconds / 60).toFixed(1) + 'm' : '—'}
              hint={a.token_cost != null ? '$' + Number(a.token_cost).toFixed(2) + ' tokens' : null}/>
          </section>

          {rerunMsg && (
            <div style={{
              padding: '10px 16px', borderRadius: 8, fontSize: 12.5, fontWeight: 500,
              background: rerunMsg.ok ? 'var(--buy-soft)' : 'var(--pass-soft)',
              color: rerunMsg.ok ? 'var(--buy)' : 'var(--pass)',
              border: '1px solid ' + (rerunMsg.ok ? 'var(--buy-soft)' : 'var(--pass-soft)'),
            }}>
              {rerunMsg.ok ? '✓ ' : '✗ '}{rerunMsg.text}
            </div>
          )}

          {actionError && <window.ErrorBanner error={{ message: actionError }}/>}

          {/* Approve/reject — only when there's a pending approval for this analysis */}
          {pendingApproval && (
            <section style={{
              background: 'var(--paper)', border: '1px solid var(--onni-primary)',
              borderRadius: 12, padding: '14px 18px',
              display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16
            }}>
              <div>
                <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--ink-900)' }}>Pending approval</div>
                <div style={{ fontSize: 12, color: 'var(--ink-500)', marginTop: 2 }}>Requires sign-off before this BUY signal is released.</div>
              </div>
              <div style={{ display: 'flex', gap: 8 }}>
                <button disabled={actionBusy} onClick={reject} style={secondaryBtn}>Reject</button>
                <button disabled={actionBusy} onClick={approve} style={primaryBtn}>Approve</button>
              </div>
            </section>
          )}

          {/* Executive summary  +  PDF viewer side-by-side (desktop only).
              On mobile this collapses to just the exec summary card; the
              PDF viewer remains at the bottom of the page (see below). */}
          {a.executive_summary && (() => {
            const execCard = (
              <Card title="Executive Summary">
                {a.decision && (
                  <div style={{
                    display: 'inline-flex', alignItems: 'center', gap: 12,
                    padding: '7px 12px', borderRadius: 7, marginBottom: 14,
                    background: a.decision === 'BUY' ? 'var(--buy-soft)' : 'var(--pass-soft)',
                    border: '1px solid ' + (a.decision === 'BUY' ? 'var(--buy)' : 'var(--pass)'),
                  }}>
                    <span style={{
                      fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 700,
                      letterSpacing: '0.12em', color: a.decision === 'BUY' ? 'var(--buy)' : 'var(--pass)',
                    }}>
                      {a.decision === 'BUY' ? '↑' : '↓'} {a.decision}
                    </span>
                    {a.price_target != null && (
                      <span style={{ fontSize: 11.5, color: 'var(--ink-500)', borderLeft: '1px solid var(--ink-200)', paddingLeft: 12 }}>
                        Target <strong style={{ color: 'var(--ink-800)' }}>${Number(a.price_target).toFixed(2)}</strong>
                      </span>
                    )}
                    {a.upside_pct != null && (
                      <span style={{ fontSize: 11.5, color: 'var(--ink-500)', borderLeft: '1px solid var(--ink-200)', paddingLeft: 12 }}>
                        Upside <strong style={{ color: a.upside_pct > 0 ? 'var(--buy)' : 'var(--pass)' }}>
                          {a.upside_pct > 0 ? '+' : ''}{Number(a.upside_pct).toFixed(1)}%
                        </strong>
                      </span>
                    )}
                  </div>
                )}
                <ProseBlock text={a.executive_summary}/>
              </Card>
            );
            if (isMobile) return execCard;

            // Desktop split: exec summary 60% left, PDF viewer 40% right.
            return (
              <div style={{ display: 'flex', alignItems: 'stretch', minHeight: 520, gap: 12 }}>
                <div style={{ width: '60%', minWidth: 0, display: 'flex' }}>
                  <div style={{ width: '100%' }}>{execCard}</div>
                </div>
                <div style={{ flex: 1, minWidth: 0, display: 'flex' }}>
                  {renderPdfViewer('inline')}
                </div>
              </div>
            );
          })()}

          {/* Bull / Bear */}
          {(a.bull_summary || a.bear_summary) && (
            <section style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 12 }}>
              <Card title="Bull case" accent="var(--buy)"><ProseBlock text={a.bull_summary || '—'} accentColor="var(--buy)"/></Card>
              <Card title="Bear case" accent="var(--pass)"><ProseBlock text={a.bear_summary || '—'} accentColor="var(--pass)"/></Card>
            </section>
          )}

          {/* Key metrics */}
          {a.key_metrics && Object.keys(a.key_metrics).length > 0 && (
            <Card title="Key metrics">
              <div style={{ display: 'grid', gridTemplateColumns: isMobile ? 'repeat(2, 1fr)' : 'repeat(4, 1fr)', gap: 16 }}>
                {Object.entries(a.key_metrics).map(([k, v]) => (
                  <window.KV key={k}
                    label={k.replace(/_/g, ' ')}
                    value={typeof v === 'number' ? v.toLocaleString() : (v ?? '—')}/>
                ))}
              </div>
            </Card>
          )}

          {/* Risk factors */}
          {Array.isArray(a.risk_factors) && a.risk_factors.length > 0 && (
            <Card title="Risk factors">
              <ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 10 }}>
                {a.risk_factors.map((r, i) => {
                  const sev = deriveSeverity(r);
                  const sevColor = sev === 'high' ? 'var(--pass)' : sev === 'medium' ? 'var(--warn)' : 'var(--ink-400)';
                  const sevBg = sev === 'high' ? 'var(--pass-soft)' : sev === 'medium' ? 'var(--warn-soft)' : 'var(--ink-50)';
                  const sevBorder = sev === 'high' ? 'var(--pass)' : sev === 'medium' ? 'var(--warn)' : 'var(--ink-300)';
                  return (
                    <li key={i} style={{
                      padding: '10px 14px', borderRadius: 8, fontSize: 12.5,
                      color: 'var(--ink-800)', background: sevBg,
                      borderLeft: '3px solid ' + sevBorder,
                    }}>
                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10 }}>
                        <div style={{ flex: 1 }}>
                          {typeof r === 'string'
                            ? <div style={{ fontSize: 13, color: 'var(--ink-800)', lineHeight: 1.6 }}>{r}</div>
                            : r.name && !r.mitigant
                              ? <div style={{ fontSize: 13, color: 'var(--ink-800)', lineHeight: 1.6 }}>{r.name}</div>
                              : <>
                                  {r.name && <div style={{ fontWeight: 600, color: 'var(--ink-900)', marginBottom: 3, fontSize: 13 }}>{r.name}</div>}
                                  {(r.description || r.mitigant) && (
                                    <div style={{ fontSize: 12.5, color: 'var(--ink-700)', lineHeight: 1.6 }}>
                                      {r.mitigant && <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 600, color: 'var(--ink-400)', textTransform: 'uppercase', letterSpacing: '0.06em', marginRight: 6 }}>Mitigant:</span>}
                                      {r.description || r.mitigant}
                                    </div>
                                  )}
                                </>
                          }
                        </div>
                        {/* Always show severity badge — derived from keywords if not explicit */}
                        <span style={{
                          fontFamily: 'var(--font-mono)', fontSize: 10, fontWeight: 700,
                          letterSpacing: '0.06em', textTransform: 'uppercase',
                          flexShrink: 0, padding: '3px 7px', borderRadius: 4,
                          color: sevColor,
                          background: 'rgba(255,255,255,0.65)',
                          border: '1px solid ' + sevBorder,
                        }}>
                          {sev}
                        </span>
                      </div>
                    </li>
                  );
                })}
              </ul>
            </Card>
          )}

          {/* Reports */}
          <CardWithAction
            title="Reports"
            action={a?.status === 'completed' && (
              <button onClick={() => { setEmailOpen(true); setEmailMsg(null); }} style={{
                ...secondaryBtn, fontSize: 11.5, padding: '5px 10px',
                display: 'inline-flex', alignItems: 'center', gap: 5
              }}>
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none">
                  <path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
                </svg>
                Send Email
              </button>
            )}
          >
            {reports.loading && <div style={{ color: 'var(--ink-500)', fontSize: 12.5 }}>Loading…</div>}
            {reports.error && <window.ErrorBanner error={reports.error} onRetry={reports.reload}/>}
            {!reports.loading && !reports.error && (
              !(reports.data?.data?.length)
                ? <div style={{ fontSize: 12.5, color: 'var(--ink-500)' }}>No reports generated yet.</div>
                : (() => {
                    // Index reports by type for deterministic layout
                    const byType = {};
                    (reports.data.data || []).forEach(r => { byType[r.file_type || r.type] = r; });
                    // 2×2 grid: [PPTX, Word] on row 1 / [PDF Deck, PDF Memo] on row 2
                    const LAYOUT = [
                      ['deck_pptx', 'memo_docx'],
                      ['deck_pdf',  'memo_pdf'],
                    ];
                    const renderBtn = (fileType) => {
                      const r = byType[fileType];
                      if (!r) return <div key={fileType}/>;
                      const { label, accent, Icon } = reportMeta(r);
                      const rState = refreshState[fileType] || {};
                      const href = rState.url || r.download_url || r.signed_url || r.url || '#';
                      const canRefresh = a?.status === 'completed' && fileType !== 'price_chart';
                      return (
                        <div key={fileType} style={{ flex: 1, display: 'flex', borderRadius: 8, overflow: 'hidden', border: '1px solid ' + (href === '#' ? 'var(--ink-200)' : accent + '44') }}>
                          {/* Download */}
                          <a href={href} target="_blank" rel="noopener" style={{
                            flex: 1, padding: '9px 14px', fontSize: 12.5, fontWeight: 500,
                            color: href === '#' ? 'var(--ink-400)' : accent,
                            background: 'var(--paper)', textDecoration: 'none',
                            display: 'inline-flex', alignItems: 'center', gap: 8,
                            pointerEvents: href === '#' ? 'none' : 'auto',
                            opacity: href === '#' ? 0.5 : 1,
                          }}>
                            <Icon />
                            {label}
                          </a>
                          {/* Refresh / regenerate */}
                          {canRefresh && (
                            <button
                              onClick={() => refreshReport(fileType)}
                              disabled={rState.busy}
                              title="Regenerate with fresh data (~60s)"
                              style={{
                                padding: '9px 10px', fontSize: 11, background: accent + '11',
                                color: accent, border: 'none', borderLeft: '1px solid ' + accent + '33',
                                cursor: rState.busy ? 'default' : 'pointer',
                                display: 'inline-flex', alignItems: 'center', gap: 4,
                                opacity: rState.busy ? 0.6 : 1, fontFamily: 'inherit'
                              }}
                            >
                              {rState.busy
                                ? <><SpinnerIcon/> Generating…</>
                                : <><svg width="11" height="11" viewBox="0 0 24 24" fill="none"><path d="M4 12a8 8 0 0114.93-4H15m3.93 4A8 8 0 019 19.93V17" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg> Refresh</>
                              }
                            </button>
                          )}
                          {rState.error && (
                            <span style={{ padding: '9px 10px', fontSize: 11, color: 'var(--pass)', borderLeft: '1px solid var(--ink-200)' }} title={rState.error}>⚠</span>
                          )}
                        </div>
                      );
                    };
                    return (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                        {LAYOUT.map((row, ri) => (
                          <div key={ri} style={{ display: 'flex', gap: 10 }}>
                            {row.map(ft => renderBtn(ft))}
                          </div>
                        ))}
                      </div>
                    );
                  })()
            )}
          </CardWithAction>

          {/* PDF Viewer — Investment Memo (mobile only; desktop renders it
              alongside the executive summary in the split row above). */}
          {isMobile && renderPdfViewer('block')}

          {/* Email modal */}
          {emailOpen && (
            <div style={{
              position: 'fixed', inset: 0, zIndex: 50,
              background: 'rgba(10,20,40,0.45)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }} onClick={(e) => { if (e.target === e.currentTarget) setEmailOpen(false); }}>
              <div style={{
                background: 'var(--paper)', borderRadius: 14, padding: '24px 28px',
                width: 420, maxWidth: '90vw', boxShadow: '0 20px 60px rgba(10,20,40,0.18)',
                border: '1px solid var(--ink-150)',
              }}>
                <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink-900)', marginBottom: 4 }}>
                  Send Analysis Email
                </div>
                <div style={{ fontSize: 12, color: 'var(--ink-500)', marginBottom: 20 }}>
                  PDF deck + PDF memo will be attached. Sent via Postmark.
                </div>

                <div style={{ marginBottom: 16 }}>
                  <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-500)', textTransform: 'uppercase', letterSpacing: '0.06em', marginBottom: 8 }}>Recipients</div>
                  {users.loading && <div style={{ fontSize: 12, color: 'var(--ink-400)' }}>Loading users…</div>}
                  {!users.loading && (users.data?.data || []).map(u => {
                    const email = u.email;
                    const checked = emailRecipients.includes(email);
                    return (
                      <label key={email} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 10px', borderRadius: 8, cursor: 'pointer', background: checked ? 'var(--ink-50)' : 'transparent' }}>
                        <input type="checkbox" checked={checked}
                          onChange={e => setEmailRecipients(prev => e.target.checked ? [...prev, email] : prev.filter(x => x !== email))}
                          style={{ accentColor: 'var(--onni-primary)', width: 14, height: 14 }}
                        />
                        <span style={{ fontSize: 12.5, color: 'var(--ink-800)' }}>{u.display_name || email}</span>
                        <span style={{ fontSize: 11, color: 'var(--ink-400)', marginLeft: 'auto' }}>{u.display_name ? email : ''}</span>
                      </label>
                    );
                  })}
                </div>

                {emailMsg && (
                  <div style={{
                    padding: '8px 12px', borderRadius: 7, fontSize: 12.5, marginBottom: 14,
                    background: emailMsg.ok ? 'var(--buy-soft)' : 'var(--pass-soft)',
                    color: emailMsg.ok ? 'var(--buy)' : 'var(--pass)',
                    border: '1px solid ' + (emailMsg.ok ? 'var(--buy-soft)' : 'var(--pass-soft)'),
                  }}>
                    {emailMsg.ok ? '✓ ' : '✗ '}{emailMsg.text}
                  </div>
                )}

                <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
                  <button onClick={() => setEmailOpen(false)} style={secondaryBtn}>Cancel</button>
                  <button
                    onClick={sendEmail}
                    disabled={emailBusy || !emailRecipients.length}
                    style={{ ...primaryBtn, opacity: emailBusy || !emailRecipients.length ? 0.5 : 1, display: 'inline-flex', alignItems: 'center', gap: 6 }}
                  >
                    {emailBusy ? <><SpinnerIcon/> Sending…</> : 'Send Email'}
                  </button>
                </div>
              </div>
            </div>
          )}

          {/* Error if pipeline failed */}
          {a.status === 'failed' && a.error_message && (
            <Card title="Error" accent="var(--pass)">
              <pre style={{ margin: 0, fontSize: 12, color: 'var(--ink-700)', whiteSpace: 'pre-wrap', fontFamily: 'var(--font-mono)' }}>{a.error_message}</pre>
            </Card>
          )}
        </>}
      </div>
    </div>
  );
};

// ─────────── local helpers ───────────
const linkBtn = { background: 'transparent', border: 'none', color: 'var(--ink-600)', fontSize: 12, cursor: 'pointer', fontFamily: 'inherit' };
const primaryBtn = { padding: '7px 14px', fontSize: 12.5, fontWeight: 500, background: 'var(--onni-primary)', color: 'white', borderRadius: 7, border: 'none', cursor: 'pointer', fontFamily: 'inherit' };
const secondaryBtn = { padding: '7px 14px', fontSize: 12.5, fontWeight: 500, background: 'var(--ink-100)', color: 'var(--ink-700)', borderRadius: 7, border: '1px solid var(--ink-200)', cursor: 'pointer', fontFamily: 'inherit' };

// ─────────── report type helpers ───────────
const _REPORT_META = {
  deck_pptx: {
    label: 'PPTX Deck',
    accent: '#d97706',
    Icon: () => (
      // PowerPoint-style icon (orange)
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
        <rect x="3" y="4" width="18" height="16" rx="2" stroke="#d97706" strokeWidth="1.6"/>
        <path d="M8 9h4.5a2 2 0 010 4H8V9z" stroke="#d97706" strokeWidth="1.5" strokeLinejoin="round"/>
        <path d="M8 13v4" stroke="#d97706" strokeWidth="1.5" strokeLinecap="round"/>
      </svg>
    ),
  },
  deck_pdf: {
    label: 'PDF Deck',
    accent: '#dc2626',
    Icon: () => (
      // PDF icon (red)
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
        <path d="M6 2h9l5 5v15a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2z" stroke="#dc2626" strokeWidth="1.6"/>
        <path d="M14 2v5h5" stroke="#dc2626" strokeWidth="1.4" strokeLinejoin="round"/>
        <text x="6" y="18" fontSize="6" fontWeight="700" fill="#dc2626" fontFamily="sans-serif">PDF</text>
      </svg>
    ),
  },
  memo_docx: {
    label: 'Word Memo',
    accent: '#2563eb',
    Icon: () => (
      // Word-style icon (blue)
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
        <rect x="3" y="2" width="18" height="20" rx="2" stroke="#2563eb" strokeWidth="1.6"/>
        <path d="M7 8h10M7 12h10M7 16h6" stroke="#2563eb" strokeWidth="1.4" strokeLinecap="round"/>
      </svg>
    ),
  },
  memo_pdf: {
    label: 'PDF Memo',
    accent: '#dc2626',
    Icon: () => (
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
        <path d="M6 2h9l5 5v15a2 2 0 01-2 2H6a2 2 0 01-2-2V4a2 2 0 012-2z" stroke="#dc2626" strokeWidth="1.6"/>
        <path d="M14 2v5h5" stroke="#dc2626" strokeWidth="1.4" strokeLinejoin="round"/>
        <text x="6" y="18" fontSize="6" fontWeight="700" fill="#dc2626" fontFamily="sans-serif">PDF</text>
      </svg>
    ),
  },
};

function reportMeta(r) {
  const type = r.file_type || r.type || '';
  return _REPORT_META[type] || {
    label: r.filename || type || 'Report',
    accent: '#6b7280',
    Icon: () => (
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none">
        <path d="M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2" stroke="#6b7280" strokeWidth="1.6" strokeLinecap="round"/>
      </svg>
    ),
  };
}

function Card({ title, accent, children }) {
  return (
    <section style={{ background: 'var(--paper)', border: '1px solid var(--ink-150)', borderRadius: 12 }}>
      <div style={{
        padding: '12px 18px', borderBottom: '1px solid var(--ink-150)',
        fontSize: 12, fontWeight: 700, color: 'var(--ink-900)',
        textTransform: 'uppercase', letterSpacing: '0.06em',
        borderLeft: accent ? '3px solid ' + accent : undefined,
        borderTopLeftRadius: 12, borderTopRightRadius: 12,
      }}>{title}</div>
      <div style={{ padding: '16px 18px' }}>{children}</div>
    </section>
  );
}

function CardWithAction({ title, accent, action, children }) {
  return (
    <section style={{ background: 'var(--paper)', border: '1px solid var(--ink-150)', borderRadius: 12 }}>
      <div style={{
        padding: '10px 18px', borderBottom: '1px solid var(--ink-150)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        borderLeft: accent ? '3px solid ' + accent : undefined,
        borderTopLeftRadius: 12, borderTopRightRadius: 12,
      }}>
        <span style={{ fontSize: 12, fontWeight: 700, color: 'var(--ink-900)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>{title}</span>
        {action}
      </div>
      <div style={{ padding: '16px 18px' }}>{children}</div>
    </section>
  );
}

function SpinnerIcon() {
  return (
    <svg width="11" height="11" viewBox="0 0 24 24" fill="none" style={{ animation: 'spin 0.8s linear infinite' }}>
      <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2" strokeDasharray="31.4" strokeDashoffset="10"/>
    </svg>
  );
}

// Derive a severity level from a risk factor entry (object or plain string).
// Uses explicit r.severity when present; falls back to keyword matching.
function deriveSeverity(r) {
  const explicit = typeof r === 'object' && r !== null ? (r.severity || '').toLowerCase() : '';
  if (explicit === 'high' || explicit === 'medium' || explicit === 'low') return explicit;
  const text = (typeof r === 'string' ? r : [r.name, r.description, r.mitigant].filter(Boolean).join(' ')).toLowerCase();
  if (/\b(critical|severe|significant|material|major|substantial|systemic|high)\b/.test(text)) return 'high';
  if (/\b(minor|minimal|limited|low|small|negligible)\b/.test(text)) return 'low';
  return 'medium';
}

function renderInline(text, accentColor) {
  // Parse **bold** and financial numbers/percentages for accent coloring
  const regex = /\*\*(.+?)\*\*|(\b\d+\.?\d*%|\$[\d,]+\.?\d*|\b\d+\.?\d*x\b)/g;
  const parts = [];
  let last = 0, m;
  while ((m = regex.exec(text)) !== null) {
    if (m.index > last) parts.push(text.slice(last, m.index));
    if (m[1]) parts.push({ bold: true, text: m[1] });
    else parts.push({ num: true, text: m[2] });
    last = m.index + m[0].length;
  }
  if (last < text.length) parts.push(text.slice(last));
  return parts.map((p, i) => {
    if (typeof p === 'string') return React.createElement(React.Fragment, { key: i }, p);
    if (p.bold) return <strong key={i} style={{ fontWeight: 700, color: accentColor }}>{p.text}</strong>;
    return <span key={i} style={{ fontWeight: 600, color: accentColor }}>{p.text}</span>;
  });
}

function ProseBlock({ text, accentColor }) {
  if (!text) return <div style={{ color: 'var(--ink-500)', fontSize: 12.5 }}>—</div>;
  const barColor = accentColor || 'var(--ink-300)';

  // Parse raw LLM output into typed segments so we can render it cleanly.
  // Handles: markdown #/##/### headers, ALL-CAPS section headers,
  // "- / * / •" bullets, "1. / 1)" numbered items, and regular prose.
  const segments = [];
  text.split('\n').forEach((raw, i) => {
    const t = raw.trim();
    if (!t) return; // blank lines → natural gap via CSS

    // Markdown headers: #, ##, ### (strip hashes, preserve level for sizing)
    const mdHash = t.match(/^(#{1,3})\s+(.+)/);
    if (mdHash) {
      segments.push({ type: 'header', text: mdHash[2].replace(/\*\*/g, ''), key: i, level: mdHash[1].length });
      return;
    }
    // ALL-CAPS header: uppercase only, ≥3 chars, starts with a letter
    if (t.length >= 3 && t === t.toUpperCase() && /^[A-Z]/.test(t) && !/^[\d\-*•]/.test(t) && /[A-Z]{2}/.test(t)) {
      segments.push({ type: 'header', text: t, key: i, level: 2 });
      return;
    }
    // Numbered item: "1. " or "1) "
    if (/^\d+[.)]\s+/.test(t)) {
      segments.push({ type: 'bullet', text: t.replace(/^\d+[.)]\s+/, ''), key: i });
      return;
    }
    // Bullet: "- " "* " "• "
    if (/^[-*•]\s+/.test(t)) {
      segments.push({ type: 'bullet', text: t.replace(/^[-*•]\s+/, ''), key: i });
      return;
    }
    segments.push({ type: 'text', text: t, key: i });
  });

  // If the raw text has no ALL-CAPS section headers (typical of narrative
  // exec summaries), still visually separate paragraphs with a top border so
  // the card looks structured like a header-delimited one.
  const hasHeaders = segments.some(s => s.type === 'header');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
      {segments.map((s, idx) => {
        if (s.type === 'header') {
          const isH1 = s.level === 1;
          const isH3 = s.level === 3;
          return (
            <div key={s.key} style={{ paddingTop: idx > 0 ? (isH1 ? 18 : 14) : 0, marginTop: idx > 0 ? (isH1 ? 10 : 8) : 0, paddingBottom: isH3 ? 3 : 5 }}>
              <span style={{
                display: 'inline-block',
                fontSize: isH1 ? 13 : isH3 ? 10 : 11,
                fontWeight: isH3 ? 600 : 700,
                textTransform: isH3 ? 'none' : 'uppercase',
                letterSpacing: isH3 ? '0' : '0.08em',
                color: isH1 ? 'var(--ink-900)' : barColor,
                borderLeft: `${isH1 ? 4 : 3}px solid ${barColor}`,
                paddingLeft: 8, lineHeight: 1.5,
              }}>{s.text}</span>
            </div>
          );
        }
        if (s.type === 'bullet') return (
          <div key={s.key} style={{ display: 'flex', gap: 10, paddingLeft: 2, alignItems: 'flex-start' }}>
            <span style={{
              width: 2, minWidth: 2, height: '1em', marginTop: '0.35em',
              background: barColor, borderRadius: 1, flexShrink: 0, display: 'inline-block',
            }} />
            <span style={{ fontSize: 13, color: 'var(--ink-800)', lineHeight: 1.65 }}>
              {renderInline(s.text, barColor)}
            </span>
          </div>
        );
        const needsRule = !hasHeaders && idx > 0;
        return (
          <div key={s.key} style={{
            fontSize: 13, lineHeight: 1.65, color: 'var(--ink-800)',
            paddingTop: needsRule ? 10 : 0,
            marginTop: needsRule ? 6 : 0,
            borderTop: needsRule ? '1px solid var(--ink-100)' : 'none',
          }}>{renderInline(s.text, barColor)}</div>
        );
      })}
    </div>
  );
}
