// report-export.jsx — Report export pipeline + run history
// ─────────────────────────────────────────────────────────────────
// Adds real export to the Reports screen:
//   • PDF      — print-styled new window + window.print()
//   • CSV      — per-block flatten + concatenated download
//   • XLSX     — SheetJS multi-sheet workbook (graceful TSV fallback)
//   • JSON     — full structured dump
//   • Email    — queued-for-delivery toast + run-history entry
//
// Provides:
//   window.ReportExport.toCSV(rendered)       → string
//   window.ReportExport.toJSON(rendered)      → string
//   window.ReportExport.toXLSX(rendered)      → Blob | null
//   window.ReportExport.toPdfHtml(rendered)   → string (full HTML doc)
//   window.ReportExport.exportPDF(report)
//   window.ReportExport.exportCSV(report)
//   window.ReportExport.exportXLSX(report)
//   window.ReportExport.exportJSON(report)
//   window.ReportExport.email(report, opts)
//   window.ReportExport.runHistory()          → array
//   window.ReportExport.logRun(entry)
//
// React surfaces:
//   <ReportExportBar report={r}/>              — button row
//   <ReportRunHistory reportId={id}/>          — list of past exports
// ─────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined' || !window.React) return;
  const _S = React.useState, _M = React.useMemo, _E = React.useEffect;

  // ─── helpers ────────────────────────────────────────────────────
  const HISTORY_KEY = 'astro.reportRunHistory.v1';

  function readHistory() {
    try {
      const raw = localStorage.getItem(HISTORY_KEY);
      return raw ? JSON.parse(raw) : [];
    } catch { return []; }
  }
  function writeHistory(list) {
    try { localStorage.setItem(HISTORY_KEY, JSON.stringify(list.slice(0, 200))); } catch {}
  }
  function logRun(entry) {
    const list = readHistory();
    list.unshift({
      id: 'run-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
      ts: new Date().toISOString(),
      ...entry,
    });
    writeHistory(list);
    window.dispatchEvent(new CustomEvent('astro-report-run', { detail: entry }));
  }

  function toast(msg, tone) {
    if (typeof window.toast === 'function') return window.toast(msg, tone || 'ok');
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg, tone: tone || 'ok' } }));
  }

  function safeFilename(name) {
    return (name || 'report').replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 80);
  }
  function ts() {
    const d = new Date();
    return d.toISOString().slice(0, 10) + '_' + d.toTimeString().slice(0, 5).replace(':', '');
  }

  function downloadBlob(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 50);
  }
  function downloadString(str, filename, mime) {
    downloadBlob(new Blob([str], { type: mime || 'text/plain' }), filename);
  }

  function fmtUsd(n) {
    if (n == null || isNaN(n)) return '';
    return Math.round(n * 100) / 100;
  }
  function csvEscape(v) {
    if (v == null) return '';
    const s = String(v);
    if (/[",\n]/.test(s)) return '"' + s.replace(/"/g, '""') + '"';
    return s;
  }
  function tableToCSV(headers, rows) {
    const lines = [headers.map(csvEscape).join(',')];
    rows.forEach(r => lines.push(r.map(csvEscape).join(',')));
    return lines.join('\n');
  }

  // ─── Per-block flatten — converts a rendered block to a (sheet) table ─
  // Each fn returns { headers, rows, summary?: [[k,v],...] }
  const FLATTEN = {
    summary: (b) => {
      const cells = (b.data && b.data.cells) || [];
      return {
        headers: ['Metric', 'Value', 'Detail'],
        rows: cells.map(c => [c.label, c.value, c.sub]),
      };
    },
    earnings: (b) => {
      const rows = (b.data && b.data.rows) || [];
      const total = (b.data && b.data.total) || 1;
      return {
        headers: [(b.params && b.params.dim) || 'Source', 'Amount (USD)', 'Share %'],
        rows: rows.map(r => [r.key, fmtUsd(r.value), ((r.value / total) * 100).toFixed(1)]),
        summary: [['Total', fmtUsd((b.data && b.data.total) || 0)]],
      };
    },
    topworks: (b) => {
      const rows = (b.data && b.data.rows) || [];
      return {
        headers: ['Rank', 'Title', 'Writers', 'ISWC', 'Revenue (USD)'],
        rows: rows.map((w, i) => [
          i + 1, w.title || '', (w.writers || []).map(x => x.name || x).join(' / '),
          w.iswc || '', fmtUsd(w.revenue),
        ]),
      };
    },
    statement: (b) => {
      const lines = (b.data && b.data.rows) || [];
      const stmt = (b.data && b.data.statement) || {};
      return {
        headers: ['Title', 'Units', 'Rate', 'Amount (USD)'],
        rows: lines.map(l => [
          l.title || l.workTitle || l.recordingTitle || '(unmatched)',
          l.units || l.qty || '',
          l.rate ? l.rate.toFixed(4) : '',
          fmtUsd(l.amountUsd || l.amount || l.gross),
        ]),
        summary: [
          ['Statement', stmt.id || ''],
          ['Source', stmt.sourceName || stmt.sourceId || ''],
          ['Period', stmt.period || ''],
        ],
      };
    },
    registrations: (b) => {
      const c = (b.data && b.data.counts) || {};
      const total = (b.data && b.data.total) || 0;
      return {
        headers: ['Status', 'Count', 'Share %'],
        rows: [
          ['Accepted', c.accepted || 0, total ? (((c.accepted || 0) / total) * 100).toFixed(1) : ''],
          ['Rejected', c.rejected || 0, total ? (((c.rejected || 0) / total) * 100).toFixed(1) : ''],
          ['Pending',  c.pending  || 0, total ? (((c.pending  || 0) / total) * 100).toFixed(1) : ''],
        ],
        summary: [['Total transactions', total]],
      };
    },
    anomalies: (b) => {
      const issues = (b.data && b.data.issues) || [];
      return {
        headers: ['#', 'Title', 'Severity', 'Entity'],
        rows: issues.map((i, idx) => [
          idx + 1,
          i.title || i.label || i.kind || '',
          (i.severity || 'low'),
          i.entity || i.party || '',
        ]),
        summary: [['Active issues', issues.length], ['Leak signals', (b.data && b.data.leakCount) || 0]],
      };
    },
    royaltyLift: (b) => {
      const ss = (b.data && b.data.strategies) || [];
      return {
        headers: ['#', 'Strategy', 'Lift (USD)', 'Effort', 'Confidence %'],
        rows: ss.map((s, i) => [
          i + 1, s.name || s.title || '', fmtUsd(s.liftUsd || s.lift || 0),
          s.effort || 'med', Math.round((s.confidence || 0.7) * 100),
        ]),
        summary: [['Projected annual lift', fmtUsd((b.data && b.data.total) || 0)]],
      };
    },
    patterns: (b) => {
      const ps = (b.data && b.data.patterns) || [];
      return {
        headers: ['#', 'Severity', 'Title', 'Kicker', 'Lens', 'Confidence %'],
        rows: ps.map((p, i) => [
          i + 1, p.severity || '', p.title || '', p.kicker || '', p.lens || '',
          Math.round((p.conf || 0) * 100),
        ]),
      };
    },
    subpub: (b) => {
      const ds = (b.data && b.data.deals) || [];
      return {
        headers: ['Partner', 'Territory', 'Fee %', 'Collected (USD)', 'Recouped (USD)', 'Status'],
        rows: ds.map(d => [
          d.partner || d.subpub || '', d.territory || '',
          d.feePct != null ? d.feePct : '',
          fmtUsd(d.collected || d.collectedUsd || 0),
          fmtUsd(d.recouped || 0),
          d.status || 'live',
        ]),
      };
    },
    text: (b) => {
      const c = (b.data && b.data.content) || '';
      return { headers: ['Commentary'], rows: c.split(/\n\n+/).map(p => [p.trim()]) };
    },
    paragraph: (b) => {
      const s = (b.data && b.data.summary) || '';
      return { headers: ['Auto-summary'], rows: [[s]] };
    },
  };

  // ─── CSV export — every block stacked with section markers ────────
  function toCSV(rendered) {
    const out = [];
    const meta = rendered.report || {};
    out.push('# Pluralis ASTRO Report Export');
    out.push('# Name: ' + (meta.name || 'Untitled'));
    out.push('# Generated: ' + (rendered.generatedAt || new Date().toISOString()));
    out.push('# Blocks: ' + (rendered.blocks || []).length);
    out.push('');
    (rendered.blocks || []).forEach((b, i) => {
      const flat = FLATTEN[b.kind];
      const label = (b.label || b.kind);
      out.push('# ─── ' + (i + 1).toString().padStart(2, '0') + ' · ' + label + ' ─────────');
      if (b.error) {
        out.push('# error: ' + b.error);
      } else if (!flat) {
        out.push('# (no flattener for ' + b.kind + ')');
      } else {
        const t = flat(b);
        out.push(tableToCSV(t.headers, t.rows));
        if (t.summary && t.summary.length) {
          out.push('');
          out.push('# summary');
          t.summary.forEach(([k, v]) => out.push(csvEscape(k) + ',' + csvEscape(v)));
        }
      }
      out.push('');
    });
    return out.join('\n');
  }

  // ─── JSON export ──────────────────────────────────────────────────
  function toJSON(rendered) {
    return JSON.stringify({
      report: rendered.report,
      generatedAt: rendered.generatedAt,
      blocks: (rendered.blocks || []).map(b => ({
        kind: b.kind, label: b.label, params: b.params, data: b.data, error: b.error || null,
      })),
    }, null, 2);
  }

  // ─── XLSX export (SheetJS if loaded, TSV fallback otherwise) ──────
  // We don't bundle SheetJS; we detect window.XLSX. If absent, we ship a
  // multi-tab TSV (single .tsv file with sheet markers) as graceful degrade.
  function ensureXLSX() {
    if (window.XLSX && window.XLSX.utils) return Promise.resolve(window.XLSX);
    if (window.__xlsx_loading) return window.__xlsx_loading;
    window.__xlsx_loading = new Promise((resolve, reject) => {
      const s = document.createElement('script');
      s.src = 'https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js';
      s.onload = () => resolve(window.XLSX);
      s.onerror = () => reject(new Error('xlsx load failed'));
      document.head.appendChild(s);
    });
    return window.__xlsx_loading;
  }

  async function toXLSX(rendered) {
    let XLSX;
    try { XLSX = await ensureXLSX(); }
    catch { return null; }

    const wb = XLSX.utils.book_new();
    const meta = rendered.report || {};

    // Cover sheet
    const cover = [
      ['Pluralis ASTRO Report'],
      ['Name', meta.name || 'Untitled'],
      ['Generated', rendered.generatedAt || new Date().toISOString()],
      ['Schedule', meta.schedule || 'On-demand'],
      ['Recipients', meta.recipients || ''],
      ['Blocks', (rendered.blocks || []).length],
      [],
      ['#', 'Block', 'Type'],
    ];
    (rendered.blocks || []).forEach((b, i) => cover.push([i + 1, b.label || '', b.kind]));
    const wsCover = XLSX.utils.aoa_to_sheet(cover);
    wsCover['!cols'] = [{ wch: 4 }, { wch: 32 }, { wch: 16 }];
    XLSX.utils.book_append_sheet(wb, wsCover, 'Cover');

    // Per-block sheets
    const used = new Set(['Cover']);
    (rendered.blocks || []).forEach((b, i) => {
      const flat = FLATTEN[b.kind];
      if (!flat) return;
      const t = flat(b);
      const aoa = [t.headers, ...t.rows];
      if (t.summary && t.summary.length) {
        aoa.push([], ['Summary']);
        t.summary.forEach(s => aoa.push(s));
      }
      let name = ((i + 1).toString().padStart(2, '0') + ' ' + (b.label || b.kind)).slice(0, 28).replace(/[\\\/\?\*\[\]:]/g, '');
      let dedupe = 0;
      while (used.has(name)) name = name.slice(0, 26) + '_' + (++dedupe);
      used.add(name);
      const ws = XLSX.utils.aoa_to_sheet(aoa);
      ws['!cols'] = t.headers.map(h => ({ wch: Math.min(40, Math.max(12, String(h).length + 4)) }));
      XLSX.utils.book_append_sheet(wb, ws, name);
    });

    const buf = XLSX.write(wb, { type: 'array', bookType: 'xlsx' });
    return new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  }

  // ─── PDF export (print-styled HTML, opens for browser print dialog) ─
  function escapeHtml(s) {
    return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({
      '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
    }[c]));
  }
  function fmtUsdHtml(n) {
    if (n == null || isNaN(n)) return '—';
    const a = Math.abs(n);
    if (a >= 1e6) return '$' + (n / 1e6).toFixed(2) + 'M';
    if (a >= 1e3) return '$' + (n / 1e3).toFixed(1) + 'k';
    return '$' + Math.round(n).toLocaleString();
  }

  function blockToHTML(b, idx) {
    if (b.error) return '<div class="err">Error: ' + escapeHtml(b.error) + '</div>';
    const flat = FLATTEN[b.kind];
    if (!flat) return '<div class="err">No renderer for ' + escapeHtml(b.kind) + '</div>';

    // Special-case nicer rendering for a couple of types
    if (b.kind === 'summary') {
      const cells = (b.data && b.data.cells) || [];
      return `<div class="kpis">${cells.map(c =>
        `<div class="kpi"><div class="kpiLabel">${escapeHtml(c.label)}</div>
         <div class="kpiVal">${escapeHtml(c.value)}</div>
         <div class="kpiSub">${escapeHtml(c.sub || '')}</div></div>`).join('')}</div>`;
    }
    if (b.kind === 'paragraph') {
      const s = (b.data && b.data.summary) || '';
      return '<div class="autosum"><div class="kpiLabel">AUTO-SUMMARY</div>' + escapeHtml(s) + '</div>';
    }
    if (b.kind === 'text') {
      const c = (b.data && b.data.content) || '';
      return '<div class="commentary">' + escapeHtml(c).replace(/\n/g, '<br/>') + '</div>';
    }

    // Generic table
    const t = flat(b);
    let html = '<table class="data"><thead><tr>' +
      t.headers.map(h => '<th>' + escapeHtml(h) + '</th>').join('') +
      '</tr></thead><tbody>';
    t.rows.forEach(r => {
      html += '<tr>' + r.map((c, i) => `<td class="${i === 0 ? 'first' : (typeof c === 'number' ? 'num' : 'txt')}">${escapeHtml(c)}</td>`).join('') + '</tr>';
    });
    html += '</tbody></table>';
    if (t.summary && t.summary.length) {
      html += '<div class="summary">' + t.summary.map(([k, v]) =>
        `<span><strong>${escapeHtml(k)}:</strong> ${escapeHtml(v)}</span>`).join('') + '</div>';
    }
    return html;
  }

  function toPdfHtml(rendered) {
    const meta = rendered.report || {};
    const blocks = rendered.blocks || [];
    return `<!doctype html>
<html><head><meta charset="utf-8"><title>${escapeHtml(meta.name || 'Report')}</title>
<style>
  @page { size: letter; margin: 0.5in 0.6in; }
  * { box-sizing: border-box; }
  body { font-family: 'Inter', -apple-system, system-ui, sans-serif; color: #111; margin: 0; padding: 24px 30px; font-size: 11pt; line-height: 1.45; }
  .top { border-bottom: 2px solid #111; padding-bottom: 14px; margin-bottom: 22px; }
  .eyebrow { font-family: ui-monospace, monospace; font-size: 8pt; letter-spacing: 0.12em; color: #666; text-transform: uppercase; margin-bottom: 4px; }
  h1 { font-size: 22pt; font-weight: 600; letter-spacing: -0.02em; margin: 0; }
  .meta { display: flex; justify-content: space-between; margin-top: 6px; color: #666; font-size: 9pt; }
  .block { margin-bottom: 24px; page-break-inside: avoid; }
  .blockTitle { font-family: ui-monospace, monospace; font-size: 8pt; letter-spacing: 0.12em; text-transform: uppercase;
                border-bottom: 1px solid #ccc; padding-bottom: 4px; margin-bottom: 10px; color: #111; }
  .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; }
  .kpi { padding: 10px 14px; border-right: 1px solid #ccc; }
  .kpi:last-child { border-right: 0; }
  .kpiLabel { font-family: ui-monospace, monospace; font-size: 7pt; letter-spacing: 0.1em; color: #888; text-transform: uppercase; }
  .kpiVal { font-size: 14pt; font-weight: 600; letter-spacing: -0.01em; margin-top: 3px; }
  .kpiSub { font-size: 8pt; color: #888; margin-top: 2px; }
  table.data { width: 100%; border-collapse: collapse; font-size: 9pt; }
  table.data th, table.data td { padding: 5px 7px; border-bottom: 1px solid #eee; text-align: left; }
  table.data th { font-family: ui-monospace, monospace; font-size: 7pt; letter-spacing: 0.1em; text-transform: uppercase; color: #666; border-bottom: 1px solid #999; }
  table.data td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: ui-monospace, monospace; }
  table.data td.first { font-weight: 500; }
  .summary { margin-top: 8px; padding: 8px 10px; background: #f5f5f5; font-size: 9pt; display: flex; gap: 16px; flex-wrap: wrap; }
  .commentary { padding: 12px 14px; background: #f5f5f5; font-size: 10pt; line-height: 1.6; white-space: pre-wrap; }
  .autosum { padding: 12px 14px; border-left: 3px solid #1a4ed8; background: #1a4ed808; font-size: 10pt; line-height: 1.6; }
  .err { padding: 10px; color: #a32a18; font-size: 9pt; background: #a32a1810; }
  footer { border-top: 1px solid #ccc; padding-top: 8px; margin-top: 30px; display: flex; justify-content: space-between; font-size: 8pt; color: #666; }
  .actions { position: fixed; top: 8px; right: 8px; background: #111; color: #fff; padding: 6px 12px; font-family: ui-monospace, monospace; font-size: 9pt; letter-spacing: 0.08em; }
  @media print { .actions { display: none; } }
</style>
</head><body>
<div class="actions">USE ⌘-P (CTRL-P) TO SAVE AS PDF</div>
<div class="top">
  <div class="eyebrow">Pluralis · ASTRO Report</div>
  <h1>${escapeHtml(meta.name || 'Untitled Report')}</h1>
  <div class="meta">
    <span>Generated ${escapeHtml(new Date(rendered.generatedAt || Date.now()).toLocaleString())}</span>
    <span>${blocks.length} block${blocks.length === 1 ? '' : 's'} · ${escapeHtml(meta.schedule || 'On-demand')}</span>
  </div>
</div>
${blocks.map((b, i) => `
  <div class="block">
    <div class="blockTitle">${(i + 1).toString().padStart(2, '0')} · ${escapeHtml(b.label || b.kind)}</div>
    ${blockToHTML(b, i)}
  </div>
`).join('')}
<footer>
  <span>Pluralis · ASTRO · Confidential</span>
  <span>${escapeHtml(meta.id || '')}</span>
</footer>
<script>setTimeout(() => window.print(), 400);</script>
</body></html>`;
  }

  // ─── Top-level export functions (run + emit) ──────────────────────
  async function ensureRendered(report) {
    if (window.ReportEngine && window.ReportEngine.run) {
      return await window.ReportEngine.run(report);
    }
    return { report, blocks: [], generatedAt: new Date().toISOString() };
  }

  async function exportPDF(report) {
    const rendered = await ensureRendered(report);
    const html = toPdfHtml(rendered);
    const w = window.open('', '_blank');
    if (!w) { toast('Popup blocked — allow popups to save PDF', 'err'); return; }
    w.document.open(); w.document.write(html); w.document.close();
    logRun({ reportId: report.id, name: report.name, format: 'pdf', size: '~' + Math.ceil(html.length / 1024) + 'k', destination: 'browser-print' });
    toast('PDF preview opened — use the browser print dialog');
  }

  async function exportCSV(report) {
    const rendered = await ensureRendered(report);
    const csv = toCSV(rendered);
    const filename = safeFilename(report.name) + '_' + ts() + '.csv';
    downloadString(csv, filename, 'text/csv;charset=utf-8');
    logRun({ reportId: report.id, name: report.name, format: 'csv', size: csv.length + 'b', destination: 'download' });
    toast('CSV exported · ' + filename);
  }

  async function exportJSON(report) {
    const rendered = await ensureRendered(report);
    const json = toJSON(rendered);
    const filename = safeFilename(report.name) + '_' + ts() + '.json';
    downloadString(json, filename, 'application/json');
    logRun({ reportId: report.id, name: report.name, format: 'json', size: json.length + 'b', destination: 'download' });
    toast('JSON exported · ' + filename);
  }

  async function exportXLSX(report) {
    const rendered = await ensureRendered(report);
    const blob = await toXLSX(rendered);
    if (!blob) {
      // graceful degrade — TSV
      const csv = toCSV(rendered).replace(/,/g, '\t');
      const filename = safeFilename(report.name) + '_' + ts() + '.tsv';
      downloadString(csv, filename, 'text/tab-separated-values');
      logRun({ reportId: report.id, name: report.name, format: 'tsv', destination: 'download', note: 'XLSX library unavailable, fell back to TSV' });
      toast('XLSX library unavailable — saved as .tsv', 'warn');
      return;
    }
    const filename = safeFilename(report.name) + '_' + ts() + '.xlsx';
    downloadBlob(blob, filename);
    logRun({ reportId: report.id, name: report.name, format: 'xlsx', size: Math.round(blob.size / 1024) + 'k', destination: 'download' });
    toast('XLSX exported · ' + filename);
  }

  async function email(report, opts) {
    const rendered = await ensureRendered(report);
    const recipients = (opts && opts.recipients) || report.recipients || '';
    const format = (opts && opts.format) || 'pdf';
    if (!recipients.trim()) { toast('No recipients — set recipients on the report', 'err'); return; }
    logRun({ reportId: report.id, name: report.name, format, destination: 'email', recipients });
    toast('Queued for delivery to ' + recipients.split(',').length + ' recipient' + (recipients.split(',').length === 1 ? '' : 's'));
  }

  // ─── React UI: ReportExportBar ────────────────────────────────────
  function ReportExportBar({ report, compact, onAfter }) {
    const [busy, setBusy] = _S(null);
    const fire = async (kind, fn) => {
      setBusy(kind);
      try { await fn(report); } finally { setBusy(null); if (onAfter) onAfter(kind); }
    };
    const btn = (kind, label, fn, primary) => (
      <button
        onClick={() => fire(kind, fn)}
        disabled={busy === kind}
        className="ff-mono upper"
        style={{
          fontSize: compact ? 10 : 11,
          padding: compact ? '5px 10px' : '8px 14px',
          letterSpacing: '0.08em',
          background: primary ? 'var(--ink)' : 'transparent',
          color: primary ? 'var(--bg)' : 'var(--ink)',
          border: '1px solid ' + (primary ? 'var(--ink)' : 'var(--rule)'),
          cursor: busy === kind ? 'wait' : 'pointer',
          opacity: busy && busy !== kind ? 0.45 : 1,
        }}>
        {busy === kind ? '…' : label}
      </button>
    );
    return (
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
        {btn('pdf',   '↓ PDF',   exportPDF, true)}
        {btn('csv',   '↓ CSV',   exportCSV)}
        {btn('xlsx',  '↓ XLSX',  exportXLSX)}
        {btn('json',  '↓ JSON',  exportJSON)}
        {!compact && btn('email', '✉ EMAIL', email)}
      </div>
    );
  }

  // ─── React UI: ReportRunHistory ───────────────────────────────────
  function ReportRunHistory({ reportId, limit }) {
    const [history, setHistory] = _S(readHistory);
    _E(() => {
      const h = () => setHistory(readHistory());
      window.addEventListener('astro-report-run', h);
      return () => window.removeEventListener('astro-report-run', h);
    }, []);
    const list = _M(() => {
      let l = history;
      if (reportId) l = l.filter(r => r.reportId === reportId);
      return l.slice(0, limit || 30);
    }, [history, reportId, limit]);

    if (!list.length) {
      return (
        <div style={{ padding: 20, textAlign: 'center', color: 'var(--ink-3)', fontSize: 11, fontStyle: 'italic' }}>
          No export history yet.
        </div>
      );
    }
    return (
      <div style={{ border: '1px solid var(--rule)' }}>
        <div style={{
          display: 'grid', gridTemplateColumns: '160px 1fr 80px 100px 1fr 70px',
          gap: 10, padding: '8px 14px', borderBottom: '1px solid var(--rule)', background: 'var(--bg-2)',
        }}>
          {['When', 'Report', 'Format', 'Size', 'Destination', 'Run'].map(h => (
            <span key={h} className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '0.08em', color: 'var(--ink-3)' }}>{h}</span>
          ))}
        </div>
        {list.map(r => (
          <div key={r.id} style={{
            display: 'grid', gridTemplateColumns: '160px 1fr 80px 100px 1fr 70px',
            gap: 10, padding: '8px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center', fontSize: 11,
          }}>
            <span className="ff-mono" style={{ color: 'var(--ink-2)', fontSize: 10 }}>{new Date(r.ts).toLocaleString()}</span>
            <span>{r.name}</span>
            <span className="ff-mono upper" style={{
              fontSize: 9, padding: '1px 6px', letterSpacing: '0.08em',
              background: 'var(--bg-2)', color: 'var(--ink)', display: 'inline-block', width: 'fit-content',
            }}>{(r.format || '?').toUpperCase()}</span>
            <span className="ff-mono" style={{ color: 'var(--ink-3)', fontSize: 10 }}>{r.size || '—'}</span>
            <span style={{ color: 'var(--ink-2)', fontSize: 10, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {r.destination === 'email' ? '✉ ' + (r.recipients || 'recipients') : r.destination}
            </span>
            <span className="ff-mono" style={{ fontSize: 9, color: 'var(--ink-3)' }}>{(r.id || '').slice(-6)}</span>
          </div>
        ))}
      </div>
    );
  }

  // ─── public ──────────────────────────────────────────────────────
  window.ReportExport = {
    toCSV, toJSON, toXLSX, toPdfHtml, FLATTEN,
    exportPDF, exportCSV, exportXLSX, exportJSON, email,
    runHistory: readHistory, logRun,
  };
  window.ReportExportBar   = ReportExportBar;
  window.ReportRunHistory  = ReportRunHistory;

  console.log('[ReportExport] loaded · pdf/csv/xlsx/json + history');
})();
