/* reports.jsx — branded, shareable, printable visibility reports
 *
 * Two views:
 *   1) List of generated reports (one per scan in history, plus on-demand generation)
 *   2) A "tear sheet" detail view: branded, timestamped, print/PDF-ready, with shareable URL
 *
 * Source of truth: localStorage history (recorded by time-series.jsx on every real scan).
 * No new persistence here — we just project that data into a presentation layer.
 */

const { useState: useStateRep, useEffect: useEffectRep, useMemo: useMemoRep, useRef: useRefRep } = React;

const REPORT_MODELS = [
  { id: 'anthropic',  name: 'Claude',     short: 'CL',  hue: 28  },
  { id: 'openai',     name: 'OpenAI',     short: 'GPT', hue: 158 },
  { id: 'google',     name: 'Gemini',     short: 'GE',  hue: 252 },
  { id: 'perplexity', name: 'Perplexity', short: 'PP',  hue: 200 },
  { id: 'xai',        name: 'Grok',       short: 'GR',  hue: 340 },
  { id: 'deepseek',   name: 'DeepSeek',   short: 'DS',  hue: 90  },
];

function fmtDate(ts) {
  const d = new Date(ts);
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
function fmtDateLong(ts) {
  const d = new Date(ts);
  return d.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
}
function fmtTime(ts) {
  const d = new Date(ts);
  return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
}
function relTime(ts) {
  const diff = Date.now() - ts;
  if (diff < 60_000) return 'just now';
  if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
  if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
  return `${Math.floor(diff / 86_400_000)}d ago`;
}

// Pull every recorded scan across all sites, projected into report rows.
function gatherReports(sites) {
  const out = [];
  (sites || []).forEach((site) => {
    const hist = (window.getHistoryForSite && window.getHistoryForSite(site.id)) || [];
    hist.forEach((entry, idx) => {
      out.push({
        id: `${site.id}-${entry.at}`,
        siteId: site.id,
        siteName: site.name,
        siteDomain: site.domain,
        site,
        at: entry.at,
        score: entry.score,
        prevScore: idx > 0 ? hist[idx - 1].score : null,
        perModel: entry.perModel || {},
        configured: entry.configured || [],
        totalCalls: entry.totalCalls,
        index: idx,
        total: hist.length,
        history: hist,
      });
    });
  });
  return out.sort((a, b) => b.at - a.at);
}

// Synthesize a "draft" report from current discovery + scan state, even before
// any history exists. This is what "Generate new report" produces.
function draftReportFor(site, scanResult, fixes) {
  if (!site) return null;
  const at = scanResult?.runAt || Date.now();
  return {
    id: `draft-${site.id}-${at}`,
    isDraft: !scanResult,
    siteId: site.id,
    siteName: site.name,
    siteDomain: site.domain,
    site,
    at,
    score: scanResult?.score ?? null,
    prevScore: null,
    perModel: scanResult?.perModel || {},
    configured: scanResult?.configured || [],
    totalCalls: scanResult?.totalCalls,
    totalMentions: scanResult?.totalMentions,
    rank: scanResult?.rank,
    competitors: scanResult?.competitors || [],
    fixes: fixes || [],
    history: (window.getHistoryForSite && window.getHistoryForSite(site.id)) || [],
  };
}

// ────────────────────────────────────────────────────────────────────────────
// LIST VIEW
// ────────────────────────────────────────────────────────────────────────────
function Reports({ profile, sites, activeSite, scanState, realFixes, onOpenSettings, onBack, initialReportId }) {
  const [openId, setOpenId] = useStateRep(initialReportId || null);
  const [siteFilter, setSiteFilter] = useStateRep('all');
  const [tick, setTick] = useStateRep(0);

  useEffectRep(() => {
    const onCh = () => setTick(t => t + 1);
    window.addEventListener('aiso:scan-recorded', onCh);
    window.addEventListener('focus', onCh);
    return () => {
      window.removeEventListener('aiso:scan-recorded', onCh);
      window.removeEventListener('focus', onCh);
    };
  }, []);

  // Read share-link param on first mount
  useEffectRep(() => {
    const m = (location.hash.match(/report=([^&]+)/) || [])[1];
    if (m) setOpenId(decodeURIComponent(m));
  }, []);

  const allReports = useMemoRep(() => gatherReports(sites), [sites, tick]);
  const filtered = siteFilter === 'all' ? allReports : allReports.filter(r => r.siteId === siteFilter);

  const draft = useMemoRep(() => {
    if (!activeSite) return null;
    return draftReportFor(activeSite, scanState?.result, realFixes);
  }, [activeSite, scanState?.result, realFixes]);

  // If user opens by id, prefer history match; fall back to draft.
  const openReport = useMemoRep(() => {
    if (!openId) return null;
    return allReports.find(r => r.id === openId) || (draft?.id === openId ? draft : null);
  }, [openId, allReports, draft]);

  if (openReport) {
    return (
      <ReportSheet
        report={openReport}
        profile={profile}
        onBack={() => { setOpenId(null); if (location.hash.includes('report=')) history.replaceState(null, '', location.pathname); }}
      />
    );
  }

  return (
    <section className="container fade-in rp-section">
      <header className="rp-head">
        <div>
          <div className="eyebrow"><span className="pulse" /> Reports · branded, timestamped, shareable</div>
          <h1 className="rp-h1">
            Every scan, packaged for <span className="grad">the people who pay</span>.
          </h1>
          <p className="rp-sub">
            One-click PDF or share link — the deliverable agencies bill for, and the receipt teams
            need to prove visibility moved. Pulled live from your own scan history.
          </p>
        </div>
        <div className="rp-head-actions">
          {draft && (
            <button className="btn btn-primary" onClick={() => setOpenId(draft.id)}>
              <ReportIcon /> Generate report for {activeSite?.name || 'site'}
            </button>
          )}
          <button className="btn" onClick={onBack}>Back to report</button>
        </div>
      </header>

      {/* Filters */}
      {sites && sites.length > 1 && (
        <div className="rp-filters">
          <span className="rp-flabel">Site</span>
          <button className={`rp-fpill ${siteFilter === 'all' ? 'on' : ''}`} onClick={() => setSiteFilter('all')}>
            All <span className="rp-fcount">{allReports.length}</span>
          </button>
          {sites.map(s => {
            const count = allReports.filter(r => r.siteId === s.id).length;
            return (
              <button key={s.id} className={`rp-fpill ${siteFilter === s.id ? 'on' : ''}`} onClick={() => setSiteFilter(s.id)}>
                {s.name} <span className="rp-fcount">{count}</span>
              </button>
            );
          })}
        </div>
      )}

      {filtered.length === 0 ? (
        <div className="rp-empty">
          <div className="rp-empty-orb">
            <svg width="84" height="84" viewBox="0 0 84 84" fill="none">
              <circle cx="42" cy="42" r="40" stroke="var(--line)" strokeDasharray="4 6" />
              <circle cx="42" cy="42" r="28" fill="none" stroke="oklch(78% 0.16 var(--accent-h) / 0.4)" />
              <text x="42" y="48" textAnchor="middle" fontFamily="var(--serif)" fontSize="22" fill="var(--fg-1)" fontStyle="italic">—</text>
            </svg>
          </div>
          <div>
            <h3>No reports yet.</h3>
            <p>Every real scan you run gets archived here as a branded, shareable report.{' '}
              {draft ? <>Generate one now from your current scan, or run a fresh scan from the main report.</> : null}
            </p>
            {draft && (
              <button className="btn btn-primary" onClick={() => setOpenId(draft.id)} style={{ marginTop: 14 }}>
                <ReportIcon /> Generate from current scan
              </button>
            )}
          </div>
        </div>
      ) : (
        <div className="rp-grid">
          {/* Draft "generate now" card always at top if active site has data */}
          {draft && (
            <button className="rp-card rp-card-draft" onClick={() => setOpenId(draft.id)}>
              <div className="rp-card-head">
                <div className="rp-card-eyebrow">
                  <span className="db-dot live-pulse" />
                  Live · current scan
                </div>
                <span className="rp-card-time">just now</span>
              </div>
              <h3 className="rp-card-title">{draft.siteName} <span className="rp-card-dim">/ this week</span></h3>
              <div className="rp-card-row">
                <div className="rp-card-score">
                  <span className="rp-card-snum">{draft.score ?? '—'}</span>
                  <span className="rp-card-spct">{draft.score != null ? '%' : ''}</span>
                </div>
                <div className="rp-card-meta">
                  <div><span className="rp-mk">mentions</span> {draft.totalMentions ?? '—'}/{draft.totalCalls ?? '—'}</div>
                  <div><span className="rp-mk">rank</span> {draft.rank ? `#${draft.rank}` : '—'}</div>
                  <div><span className="rp-mk">fixes</span> {(draft.fixes || []).length}</div>
                </div>
              </div>
              <div className="rp-card-foot">
                <span className="rp-cta">Generate report <Arrow /></span>
                <span className="rp-card-stamp">{fmtDate(Date.now())}</span>
              </div>
            </button>
          )}

          {filtered.map((r) => (
            <ReportCard key={r.id} report={r} onOpen={() => setOpenId(r.id)} />
          ))}
        </div>
      )}
    </section>
  );
}

function ReportCard({ report, onOpen }) {
  const delta = report.prevScore != null ? report.score - report.prevScore : null;
  const ms = (report.configured || []).length;
  return (
    <button className="rp-card" onClick={onOpen}>
      <div className="rp-card-head">
        <div className="rp-card-eyebrow">
          <span className="rp-card-bullet" />
          {report.siteDomain || report.siteName}
        </div>
        <span className="rp-card-time">{relTime(report.at)}</span>
      </div>
      <h3 className="rp-card-title">{report.siteName}</h3>
      <div className="rp-card-row">
        <div className="rp-card-score">
          <span className="rp-card-snum">{report.score}</span>
          <span className="rp-card-spct">%</span>
        </div>
        <div className="rp-card-meta">
          <div><span className="rp-mk">models</span> {ms}</div>
          <div>
            <span className="rp-mk">change</span>
            {delta == null ? '—' : (
              <span className={`rp-delta ${delta > 0 ? 'up' : delta < 0 ? 'down' : ''}`}>
                {delta > 0 ? '+' : ''}{delta}
              </span>
            )}
          </div>
          <div><span className="rp-mk">prompts</span> {report.totalCalls ?? '—'}</div>
        </div>
      </div>
      {/* Mini ridge — last 12 history entries */}
      <ReportSparkline history={report.history} highlightAt={report.at} />
      <div className="rp-card-foot">
        <span className="rp-cta">Open <Arrow /></span>
        <span className="rp-card-stamp">{fmtDate(report.at)} · {fmtTime(report.at)}</span>
      </div>
    </button>
  );
}

function ReportSparkline({ history, highlightAt }) {
  if (!history || history.length === 0) return <div className="rp-spark-empty" />;
  const tail = history.slice(-12);
  const w = 280, h = 36, pad = 4;
  const min = Math.min(...tail.map(p => p.score), 0);
  const max = Math.max(...tail.map(p => p.score), 100);
  const range = Math.max(1, max - min);
  const pts = tail.map((p, i) => {
    const x = pad + (i / Math.max(1, tail.length - 1)) * (w - pad * 2);
    const y = h - pad - ((p.score - min) / range) * (h - pad * 2);
    return [x, y, p];
  });
  const d = pts.map(([x, y], i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`).join(' ');
  return (
    <svg className="rp-spark" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none">
      <path d={d} fill="none" stroke="oklch(78% 0.16 var(--accent-h) / 0.7)" strokeWidth="1.4" />
      {pts.map(([x, y, p], i) => (
        <circle
          key={i}
          cx={x} cy={y} r={p.at === highlightAt ? 3 : 1.6}
          fill={p.at === highlightAt ? 'oklch(78% 0.16 var(--accent-h))' : 'oklch(78% 0.16 var(--accent-h) / 0.5)'}
        />
      ))}
    </svg>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// DETAIL — the branded tear sheet
// ────────────────────────────────────────────────────────────────────────────
function ReportSheet({ report, profile, onBack }) {
  const [copied, setCopied] = useStateRep(false);
  const [savingPdf, setSavingPdf] = useStateRep(false);
  const shareUrl = useMemoRep(() => `${location.origin}${location.pathname}#report=${encodeURIComponent(report.id)}`, [report.id]);

  const onPrint = () => {
    setSavingPdf(true);
    // Let the spinner paint before window.print() (which blocks while the dialog is open).
    setTimeout(() => { try { window.print(); } finally { setSavingPdf(false); } }, 80);
  };
  const onShare = async () => {
    try {
      await navigator.clipboard.writeText(shareUrl);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch {
      prompt('Copy this link:', shareUrl);
    }
  };

  const score = report.score ?? 0;
  const delta = report.prevScore != null ? score - report.prevScore : null;
  const ms = (report.configured || []).length || REPORT_MODELS.length;
  const fixes = report.fixes || [];
  const completedFixes = fixes.filter(f => f.shipping || f.deployed);
  const remainingFixes = fixes.filter(f => !f.shipping && !f.deployed);
  const competitors = (report.competitors || []).slice(0, 6);

  // Synthesize "best lift" — top deployed fix(es) with projected gain
  const bestLifts = completedFixes.slice(0, 3);

  return (
    <section className="container fade-in rp-sheet-wrap">
      {/* sheet toolbar — hidden in print */}
      <div className="rp-toolbar no-print">
        <button className="btn btn-ghost" onClick={onBack}>← All reports</button>
        <div className="rp-toolbar-actions">
          <button className="btn" onClick={onShare}>
            {copied ? <><Tick /> Link copied</> : <><LinkIcon /> Copy share link</>}
          </button>
          <button className="btn btn-primary" onClick={onPrint} disabled={savingPdf}>
            {savingPdf ? <>Preparing PDF<span className="dots" /></> : <><PrintIcon /> Save as PDF</>}
          </button>
        </div>
      </div>

      <article className="rp-sheet">
        {/* sheet header */}
        <header className="rp-sheet-head">
          <div className="rp-mast">
            <span className="rp-mast-dot" />
            <span className="rp-mast-word">AISO</span>
            <span className="rp-mast-tag">observatory · monthly report</span>
          </div>
          <div className="rp-stamp">
            <div className="rp-stamp-k">Issued</div>
            <div className="rp-stamp-v">{fmtDateLong(report.at)} · {fmtTime(report.at)}</div>
          </div>
        </header>

        <div className="rp-sheet-title">
          <div className="rp-sheet-brand">
            <div className="rp-sheet-favi">{(report.siteName || '·')[0]}</div>
            <div>
              <div className="rp-eyebrow">Visibility report · {ms} models · {report.totalCalls ?? '—'} prompts</div>
              <h1 className="rp-sheet-h1">{report.siteName}</h1>
              <div className="rp-sheet-dom">{report.siteDomain}</div>
            </div>
          </div>
          <div className="rp-sheet-score">
            <div className="rp-score-orb">
              <ScoreRing value={score} />
              <div className="rp-score-num">{score}<span>%</span></div>
            </div>
            <div className="rp-score-cap">
              <div className="rp-score-label">Visibility score</div>
              {delta != null ? (
                <div className={`rp-score-delta ${delta > 0 ? 'up' : delta < 0 ? 'down' : ''}`}>
                  {delta > 0 ? '↑' : delta < 0 ? '↓' : '·'} {Math.abs(delta)} since last
                </div>
              ) : (
                <div className="rp-score-delta">baseline</div>
              )}
            </div>
          </div>
        </div>

        {/* TL;DR strip */}
        <div className="rp-tldr">
          <div className="rp-tldr-cell">
            <div className="rp-tldr-k">Mentioned</div>
            <div className="rp-tldr-v">{report.totalMentions != null ? report.totalMentions : (Math.round(score * (report.totalCalls || 0) / 100) || '—')}<span className="rp-tldr-of">/{report.totalCalls ?? '—'}</span></div>
            <div className="rp-tldr-sub">prompts across {ms} models</div>
          </div>
          <div className="rp-tldr-cell">
            <div className="rp-tldr-k">Rank</div>
            <div className="rp-tldr-v">{report.rank ? `#${report.rank}` : '—'}</div>
            <div className="rp-tldr-sub">in your category</div>
          </div>
          <div className="rp-tldr-cell">
            <div className="rp-tldr-k">Fixes shipped</div>
            <div className="rp-tldr-v">{completedFixes.length}<span className="rp-tldr-of">/{fixes.length || '—'}</span></div>
            <div className="rp-tldr-sub">{remainingFixes.length} remaining</div>
          </div>
          <div className="rp-tldr-cell">
            <div className="rp-tldr-k">Period</div>
            <div className="rp-tldr-v" style={{ fontSize: '22px' }}>{report.history && report.history.length > 1 ? `${report.history.length} scans` : '1 scan'}</div>
            <div className="rp-tldr-sub">on file</div>
          </div>
        </div>

        {/* Per-model breakdown */}
        <section className="rp-block">
          <h2 className="rp-h2">How each model sees you</h2>
          <p className="rp-lede">Mention rate across the wired AI assistants for {report.siteName}-style prompts.</p>
          <div className="rp-models">
            {REPORT_MODELS.map(m => {
              const v = report.perModel?.[m.id];
              const color = `oklch(80% 0.14 ${m.hue})`;
              const has = v != null;
              return (
                <div key={m.id} className={`rp-model ${has ? '' : 'rp-model-na'}`}>
                  <div className="rp-model-top">
                    <span className="rp-model-name" style={{ color }}>{m.name}</span>
                    <span className="rp-model-short">{m.short}</span>
                  </div>
                  <div className="rp-model-val">
                    {has ? <>{v}<small>%</small></> : <span className="rp-model-dash">—</span>}
                  </div>
                  <div className="rp-model-bar">
                    <i style={{ width: `${has ? v : 0}%`, background: color, boxShadow: `0 0 8px ${color}` }} />
                  </div>
                </div>
              );
            })}
          </div>
        </section>

        {/* Trend chart */}
        {report.history && report.history.length >= 2 && (
          <section className="rp-block">
            <h2 className="rp-h2">Trajectory</h2>
            <p className="rp-lede">Visibility over the last {report.history.length} scans. The dot marks this report.</p>
            <ReportTrend history={report.history} highlightAt={report.at} />
          </section>
        )}

        {/* Competitors */}
        {competitors.length > 0 && (
          <section className="rp-block">
            <h2 className="rp-h2">Who AI recommends</h2>
            <p className="rp-lede">Brands extracted from {report.totalCalls ?? 'the'} model responses — ranked by mention count.</p>
            <div className="rp-comp-table">
              {[...competitors, { name: report.siteName, us: true, count: report.totalMentions || 0, rank: report.rank }]
                .sort((a, b) => (b.count || 0) - (a.count || 0))
                .map((c, i) => (
                  <div key={c.name + i} className={`rp-comp-row ${c.us ? 'us' : ''}`}>
                    <div className="rp-comp-rank">{i + 1}</div>
                    <div className="rp-comp-name">
                      {c.name}
                      {c.us && <span className="rp-you-tag">YOU</span>}
                    </div>
                    <div className="rp-comp-bar">
                      <i style={{ width: `${Math.min(100, ((c.count || 0) / Math.max(1, competitors[0]?.count || 1)) * 100)}%` }} />
                    </div>
                    <div className="rp-comp-count">{c.count ?? '—'} mention{c.count === 1 ? '' : 's'}</div>
                  </div>
                ))}
            </div>
          </section>
        )}

        {/* Fixes ledger */}
        {fixes.length > 0 && (
          <section className="rp-block">
            <h2 className="rp-h2">Fixes — completed & remaining</h2>
            <p className="rp-lede">Highest-impact technical changes detected on {report.siteDomain}, ranked by projected lift.</p>
            <div className="rp-fixes">
              {completedFixes.length > 0 && (
                <div className="rp-fix-col">
                  <div className="rp-fix-coltitle"><span className="rp-fix-coldot ok" /> Completed · {completedFixes.length}</div>
                  {completedFixes.slice(0, 5).map((f, i) => (
                    <FixLedgerRow key={f.id || i} fix={f} done />
                  ))}
                </div>
              )}
              {remainingFixes.length > 0 && (
                <div className="rp-fix-col">
                  <div className="rp-fix-coltitle"><span className="rp-fix-coldot wait" /> Remaining · {remainingFixes.length}</div>
                  {remainingFixes.slice(0, 5).map((f, i) => (
                    <FixLedgerRow key={f.id || i} fix={f} />
                  ))}
                </div>
              )}
            </div>
          </section>
        )}

        {/* Before/after */}
        {bestLifts.length > 0 && bestLifts.some(f => f.aiContent?.beforeAfter?.length) && (
          <section className="rp-block">
            <h2 className="rp-h2">Before & after — what changed</h2>
            <p className="rp-lede">A real prompt, and how the answer moved.</p>
            <div className="rp-ba-list">
              {bestLifts.flatMap(f => (f.aiContent?.beforeAfter || []).slice(0, 1).map((ba, i) => (
                <div key={f.id + i} className="rp-ba">
                  <div className="rp-ba-prompt">
                    <span className="rp-ba-asker">Claude · prompt</span>
                    "{ba.prompt}"
                  </div>
                  <div className="rp-ba-pair">
                    <div className="rp-ba-side rp-ba-before">
                      <div className="rp-ba-head">Before</div>
                      <div className="rp-ba-body">{ba.before}</div>
                    </div>
                    <div className="rp-ba-side rp-ba-after">
                      <div className="rp-ba-head">After · +{f.gain || 0} visibility</div>
                      <div className="rp-ba-body">{ba.after}</div>
                    </div>
                  </div>
                </div>
              ))).slice(0, 2)}
            </div>
          </section>
        )}

        {/* footer */}
        <footer className="rp-sheet-foot">
          <div className="rp-foot-left">
            <span className="rp-mast-dot" />
            <span>AISO Observatory — generated for <strong>{profile?.name || profile?.email || 'your team'}</strong></span>
          </div>
          <div className="rp-foot-right">
            Method: 142 vertical-aware prompts × {ms} models. Mention rate is the share of responses
            naming the brand. Rank is computed from competitor extraction.
          </div>
        </footer>
      </article>
    </section>
  );
}

function ScoreRing({ value }) {
  const r = 48;
  const c = 2 * Math.PI * r;
  const off = c - (Math.max(0, Math.min(100, value)) / 100) * c;
  return (
    <svg viewBox="0 0 120 120" width="120" height="120">
      <circle cx="60" cy="60" r={r} fill="none" stroke="oklch(28% 0.018 80)" strokeWidth="6" />
      <circle
        cx="60" cy="60" r={r}
        fill="none"
        stroke="url(#rpring)"
        strokeWidth="6"
        strokeLinecap="round"
        strokeDasharray={c}
        strokeDashoffset={off}
        transform="rotate(-90 60 60)"
        style={{ transition: 'stroke-dashoffset 1.2s cubic-bezier(.2,.7,.2,1)' }}
      />
      <defs>
        <linearGradient id="rpring" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%" stopColor="oklch(78% 0.16 var(--accent-h))" />
          <stop offset="100%" stopColor="oklch(76% 0.16 var(--accent-h2))" />
        </linearGradient>
      </defs>
    </svg>
  );
}

function ReportTrend({ history, highlightAt }) {
  const w = 880, h = 180, padX = 28, padY = 22;
  const pts = history.map((p, i) => ({ ...p, i }));
  const min = Math.min(...pts.map(p => p.score)) - 4;
  const max = Math.max(...pts.map(p => p.score)) + 4;
  const range = Math.max(8, max - min);
  const xy = (p) => {
    const x = padX + (p.i / Math.max(1, pts.length - 1)) * (w - padX * 2);
    const y = h - padY - ((p.score - min) / range) * (h - padY * 2);
    return [x, y];
  };
  const path = pts.map((p, i) => {
    const [x, y] = xy(p);
    return `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${y.toFixed(1)}`;
  }).join(' ');
  const area = `${path} L ${(w - padX).toFixed(1)} ${h - padY} L ${padX} ${h - padY} Z`;

  return (
    <div className="rp-trend">
      <svg viewBox={`0 0 ${w} ${h}`} className="rp-trend-svg">
        {/* gridlines */}
        {[0, 0.25, 0.5, 0.75, 1].map((p, i) => (
          <line key={i} x1={padX} x2={w - padX} y1={padY + p * (h - padY * 2)} y2={padY + p * (h - padY * 2)}
            stroke="var(--line)" strokeDasharray="2 4" />
        ))}
        <path d={area} fill="url(#rptrendfill)" opacity="0.4" />
        <path d={path} fill="none" stroke="oklch(78% 0.16 var(--accent-h))" strokeWidth="2" strokeLinejoin="round" />
        {pts.map((p) => {
          const [x, y] = xy(p);
          const hi = p.at === highlightAt;
          return (
            <g key={p.at}>
              <circle cx={x} cy={y} r={hi ? 5 : 2.5}
                fill={hi ? 'oklch(78% 0.16 var(--accent-h))' : 'oklch(78% 0.16 var(--accent-h) / 0.6)'}
                stroke={hi ? 'var(--bg-0)' : 'transparent'} strokeWidth="2" />
              {hi && (
                <text x={x} y={y - 12} textAnchor="middle" fontFamily="var(--mono)" fontSize="10"
                  fill="var(--accent)" letterSpacing="0.06em">{p.score}</text>
              )}
            </g>
          );
        })}
        <defs>
          <linearGradient id="rptrendfill" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="oklch(78% 0.16 var(--accent-h) / 0.4)" />
            <stop offset="100%" stopColor="oklch(78% 0.16 var(--accent-h) / 0)" />
          </linearGradient>
        </defs>
      </svg>
      <div className="rp-trend-axis">
        <span>{fmtDate(pts[0].at)}</span>
        <span>{fmtDate(pts[pts.length - 1].at)}</span>
      </div>
    </div>
  );
}

function FixLedgerRow({ fix, done }) {
  return (
    <div className={`rp-fix-row ${done ? 'done' : ''}`}>
      <div className="rp-fix-mark">{done ? '✓' : '·'}</div>
      <div className="rp-fix-name">
        <div className="rp-fix-title">{fix.title}</div>
        <div className="rp-fix-cat">{fix.category}</div>
      </div>
      <div className="rp-fix-gain">+{fix.gain || 0}</div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// Inline icons
// ────────────────────────────────────────────────────────────────────────────
function ReportIcon() { return <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3 1.5h6l3 3V12.5a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V2.5a1 1 0 0 1 1-1z M9 1.5v3h3 M4.5 7.5h5 M4.5 10h3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/></svg>; }
function LinkIcon() { return <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M6 8c1 1 2.5 1 3.5 0l2-2c1-1 1-2.5 0-3.5s-2.5-1-3.5 0l-1 1 M8 6c-1-1-2.5-1-3.5 0l-2 2c-1 1-1 2.5 0 3.5s2.5 1 3.5 0l1-1" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/></svg>; }
function PrintIcon() { return <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M3.5 4V1.5h7V4 M3.5 11H2a1 1 0 0 1-1-1V5.5a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1V10a1 1 0 0 1-1 1h-1.5 M3.5 8h7v4.5h-7V8z" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round"/></svg>; }

Object.assign(window, { Reports });
