// bulk-import.jsx — Bulk Catalog Import screen
// ─────────────────────────────────────────────────────────────────
// Five-tab pipeline UI for importing catalog assets in bulk:
//
//   01 UPLOAD   — drop zone + paste · format & entity sniff
//   02 MAP      — column mapping table (auto-suggested + editable)
//   03 VALIDATE — row-level errors / warnings · severity filter
//   04 PREVIEW  — row-by-row diff preview with create/update/dup tags
//   05 COMMIT   — dry-run summary + commit to RS · run history
//
// Sits alongside the existing Import & Conform screen (which is for
// migration audits) — this is for routine bulk operations: dropping
// in a CSV from a label / sub-publisher / DSP / PRO.
//
// Exports: window.ScreenBulkImport
// ─────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined' || !window.React) return;
  const _S = React.useState, _E = React.useEffect, _M = React.useMemo, _R = React.useRef, _C = React.useCallback;

  const E = window.BULK_IMPORT_ENGINE;
  if (!E) { console.warn('[bulk-import] BULK_IMPORT_ENGINE missing'); return; }

  // ── Mono helper (matches stmt-parser convention) ────────────────
  function Mono({ children, upper, size, color, style, ...rest }) {
    return <span className={'ff-mono' + (upper?' upper':'')} style={{ fontSize: size||11, color: color||'var(--ink)', letterSpacing: upper?'.08em':0, ...style }} {...rest}>{children}</span>;
  }

  function Cell({ label, value, sub, tone }) {
    return (
      <div style={{ padding: '18px 22px', borderRight: '1px solid var(--rule)' }}>
        <Mono upper size={9} color="var(--ink-3)">{label}</Mono>
        <div className="ff-display" style={{ fontSize: 24, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 4, color: tone || 'var(--ink)' }}>{value}</div>
        <div style={{ fontSize: 11, color: 'var(--ink-2)', marginTop: 3 }}>{sub}</div>
      </div>
    );
  }

  function Pill({ tone, children }) {
    const tones = {
      ok:    { bg:'#0a8754', fg:'#fff' },
      warn:  { bg:'#d4881f', fg:'#fff' },
      err:   { bg:'#a32a18', fg:'#fff' },
      info:  { bg:'#1a4ed8', fg:'#fff' },
      mono:  { bg:'var(--ink)', fg:'var(--bg)' },
      ghost: { bg:'transparent', fg:'var(--ink-2)', bd:'1px solid var(--rule)' },
    };
    const t = tones[tone] || tones.mono;
    return <span className="ff-mono upper" style={{ fontSize: 9, padding: '2px 7px', background: t.bg, color: t.fg, border: t.bd || 0, letterSpacing: '.08em' }}>{children}</span>;
  }

  function ActionPill({ action }) {
    if (action === 'create') return <Pill tone="ok">CREATE</Pill>;
    if (action === 'update') return <Pill tone="info">UPDATE</Pill>;
    if (action === 'duplicate-title') return <Pill tone="warn">DUP TITLE</Pill>;
    return <Pill tone="ghost">SKIP</Pill>;
  }

  function fmtDate(t) { return new Date(t).toLocaleString(); }

  // ════════════════════════════════════════════════════════════════
  // UPLOAD TAB
  // ════════════════════════════════════════════════════════════════
  function UploadTab({ session, onResult }) {
    const [over, setOver] = _S(false);
    const [paste, setPaste] = _S('');
    const [busy, setBusy] = _S(false);

    function ingest(name, text) {
      setBusy(true);
      setTimeout(() => {
        try {
          const r = E.runPipeline({ name, text });
          onResult(name, text, r);
        } finally { setBusy(false); }
      }, 50);
    }

    function onFile(e) {
      const f = e.target.files?.[0];
      if (!f) return;
      const r = new FileReader();
      r.onload = () => ingest(f.name, String(r.result || ''));
      r.readAsText(f);
    }

    function onDrop(e) {
      e.preventDefault(); setOver(false);
      const f = e.dataTransfer.files?.[0];
      if (!f) return;
      const r = new FileReader();
      r.onload = () => ingest(f.name, String(r.result || ''));
      r.readAsText(f);
    }

    function onPaste() {
      if (!paste.trim()) return;
      ingest('pasted.csv', paste);
    }

    function onSample(entity) {
      const seedTexts = {
        works: 'Work Title,ISWC,Duration,Language,Copyright Date,CWR Work Type\n"Echoes In Glass",T-915.076.808-4,3:42,English,2024-04-01,Pop\n"Half Light",T-927.118.221-9,4:15,English,2024-06-12,Pop\n"Una Vela Encendida",,2:58,Spanish,2023-11-30,Latin\n"Hold The Line (Acoustic)",T-009.221.118-6,3:51,English,2024-07-04,Pop',
        recordings: 'Track Name,ISRC,Artist,Label,Duration,Status,Genre,Language\n"Echoes In Glass",USPLU2400001,Mira Avalon,Pluralis Music,3:42,Released,Pop,English\n"Half Light (Radio Edit)",USPLU2400002,Mira Avalon,Pluralis Music,3:18,Released,Pop,English\n"Una Vela Encendida",MX-ABC-23-09918,Tony Music,Half-Tone Records,2:58,Released,Latin Urban,Spanish',
        profiles: 'first_name,last_name,artist_name,ipi_name_number,pro_affiliation,profile_type\nMira,Avalon,MIRA,00891133140,ASCAP,Person\nLuca,Reeves,LUCAS R.,00712054088,BMI,Person\nGenne,Pizarro,Ginox,00807161456,BMI,Person',
        publishers: 'name,ipi,role,territory,pro\nPluralis Music,00578913241,Original Publisher,World,ASCAP\nHalf-Tone Publishing,00712054088,Co-Publisher,World,BMI\nSolange Knowles Pub.,00892331140,Administrator,World,SESAC',
        agreements: 'agreement_number,agreement_type,assignor,acquirer,effective_date,expiry_date,territory,pr_share,mr_share\nAGR-2024-0014,Original,Mira Avalon,Pluralis Music,2024-01-01,2027-01-01,World,50,50\nAGR-2024-0015,Sub-Publishing,Pluralis Music,Half-Tone Publishing,2024-04-01,2026-04-01,Europe,25,25',
      };
      ingest(`sample_${entity}.csv`, seedTexts[entity] || seedTexts.works);
    }

    const recent = E.seedRunHistory().slice(0, 5);

    return (
      <div>
        {/* drop zone + paste */}
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:18, marginBottom:24 }}>
          <div
            onDragOver={(e) => { e.preventDefault(); setOver(true); }}
            onDragLeave={() => setOver(false)}
            onDrop={onDrop}
            style={{ border: '2px dashed ' + (over ? 'var(--ink)' : 'var(--rule)'), padding:'40px 22px', textAlign:'center', background: over ? 'var(--bg-2)' : 'var(--paper)', transition:'all .15s' }}>
            <div style={{ fontSize: 40, marginBottom: 8, opacity: 0.5 }}>⤓</div>
            <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>Drop a catalog file</div>
            <div style={{ fontSize: 12, color:'var(--ink-2)', marginBottom: 18, maxWidth: 360, margin:'0 auto 18px' }}>
              CSV · TSV · JSON · DDEX-XML · CWR. The pipeline will sniff the format, detect the entity, and suggest column mappings.
            </div>
            <label style={{ display:'inline-block', padding:'9px 18px', border:'1px solid var(--ink)', cursor:'pointer', fontSize:12, background:'var(--ink)', color:'var(--bg)' }}>
              Browse files
              <input type="file" accept=".csv,.tsv,.txt,.json,.xml,.v21,.v22,.v30,.v31" onChange={onFile} style={{ display:'none' }}/>
            </label>
            {busy && <div style={{ marginTop: 16, fontSize: 11, color:'var(--ink-3)' }}>Parsing…</div>}
          </div>
          <div style={{ border:'1px solid var(--rule)', padding:18, background:'var(--paper)' }}>
            <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 8, display:'block' }}>OR PASTE CSV / TSV / JSON</Mono>
            <textarea value={paste} onChange={(e) => setPaste(e.target.value)} placeholder="Paste rows — first row should be headers" style={{ width:'100%', height:140, padding:10, border:'1px solid var(--rule)', background:'var(--bg)', fontFamily:'IBM Plex Mono, monospace', fontSize:11, color:'var(--ink)', boxSizing:'border-box', resize:'vertical' }}/>
            <div style={{ display:'flex', gap:8, marginTop:10, alignItems:'center' }}>
              <button onClick={onPaste} disabled={!paste.trim()} style={{ padding:'8px 16px', border:'1px solid var(--ink)', background: paste.trim() ? 'var(--ink)' : 'var(--rule-soft)', color: paste.trim() ? 'var(--bg)' : 'var(--ink-3)', fontSize:12, cursor: paste.trim() ? 'pointer' : 'not-allowed' }}>Run pipeline</button>
              <span style={{ flex:1 }}/>
              <Mono size={10} color="var(--ink-3)">Try a sample:</Mono>
              {['works','recordings','profiles','agreements'].map(k => (
                <button key={k} onClick={() => onSample(k)} style={{ padding:'4px 9px', border:'1px solid var(--rule)', background:'transparent', fontSize:10.5, cursor:'pointer' }}>{k}</button>
              ))}
            </div>
          </div>
        </div>

        {/* Supported entities */}
        <div style={{ marginBottom: 24 }}>
          <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 10, display:'block' }}>SUPPORTED ENTITIES · 7</Mono>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap: 12 }}>
            {Object.entries(E.SCHEMAS).map(([k, s]) => (
              <div key={k} style={{ padding:14, border:'1px solid var(--rule)', background:'var(--paper)' }}>
                <div style={{ fontSize:13, fontWeight:600, marginBottom:4 }}>{s.label}</div>
                <Mono size={10} color="var(--ink-3)">{s.fields.length} fields · {s.fields.filter(f=>f.req).length} required</Mono>
                <div style={{ fontSize: 10.5, color:'var(--ink-2)', marginTop: 8 }}>
                  Natural key: <span className="ff-mono">{s.natural}</span>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Recent runs */}
        <div>
          <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 10, display:'block' }}>RECENT IMPORTS</Mono>
          <div style={{ border:'1px solid var(--rule)' }}>
            {recent.map(r => {
              const tone = r.status === 'ok' ? '#0a8754' : r.status === 'partial' ? '#d4881f' : '#a32a18';
              return (
                <div key={r.id} style={{ display:'grid', gridTemplateColumns:'10px 1fr 160px 120px 90px 80px', gap:14, padding:'12px 16px', borderBottom:'1px solid var(--rule-soft)', alignItems:'center' }}>
                  <span style={{ width:8, height:8, borderRadius:'50%', background: tone, display:'inline-block' }}/>
                  <div>
                    <div style={{ fontSize:12.5, fontWeight:500 }}>{r.fileName}</div>
                    <Mono size={10} color="var(--ink-3)" style={{ marginTop:2 }}>{fmtDate(r.startedAt)} · {r.user}</Mono>
                  </div>
                  <Mono size={11}>{r.entity ? E.SCHEMAS[r.entity]?.label.split(' ')[0] : '—'}</Mono>
                  <Mono size={11} style={{ textAlign:'right' }}>+{r.createCount.toLocaleString()} <span style={{ color:'var(--ink-3)' }}>· ↻{r.updateCount.toLocaleString()}</span></Mono>
                  <Mono size={11} style={{ textAlign:'right', color: r.errCount > 0 ? '#a32a18' : 'var(--ink-3)', fontWeight: r.errCount > 0 ? 600 : 400 }}>{r.errCount} err</Mono>
                  <Mono upper size={9} style={{ textAlign:'right', color: tone, fontWeight:600 }}>{r.status}</Mono>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  // ════════════════════════════════════════════════════════════════
  // MAP TAB — column mapping editor
  // ════════════════════════════════════════════════════════════════
  function MapTab({ session, onUpdate }) {
    if (!session.result) return <Empty msg="Upload a file first to configure mapping."/>;
    const r = session.result;
    const schema = E.SCHEMAS[session.entity || r.entity];
    const mapping = session.mapping || r.mapping;
    const headers = r.headers;

    function setMapping(field, header) {
      const next = { ...mapping };
      if (header === '') delete next[field]; else next[field] = header;
      onUpdate({ mapping: next });
    }

    function setEntity(eKey) {
      const newSchema = E.SCHEMAS[eKey];
      const auto = E.suggestMapping(headers, eKey);
      onUpdate({ entity: eKey, mapping: auto });
    }

    const usedHeaders = new Set(Object.values(mapping));
    const unusedHeaders = headers.filter(h => !usedHeaders.has(h));

    return (
      <div>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 16, padding:'14px 18px', background:'var(--bg-2)', border:'1px solid var(--rule)' }}>
          <div>
            <Mono upper size={9} color="var(--ink-3)">DETECTED · {r.format.toUpperCase()}</Mono>
            <div style={{ fontSize:14, fontWeight:600, marginTop: 2 }}>
              <span style={{ color:'var(--ink-3)' }}>Entity:</span> {schema.label}
              <span style={{ color:'var(--ink-3)', marginLeft: 16 }}>{r.rowCount.toLocaleString()} rows · {headers.length} cols</span>
            </div>
          </div>
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <Mono upper size={9} color="var(--ink-3)">CHANGE ENTITY</Mono>
            <select value={session.entity || r.entity} onChange={(e) => setEntity(e.target.value)} style={{ border:'1px solid var(--rule)', background:'var(--bg)', fontSize:12, padding:'0 24px 0 10px' }}>
              {Object.entries(E.SCHEMAS).map(([k, s]) => (
                <option key={k} value={k}>{s.label}</option>
              ))}
            </select>
          </div>
        </div>

        {/* Mapping table */}
        <div style={{ border:'1px solid var(--rule)' }}>
          <div style={{ display:'grid', gridTemplateColumns:'24px 220px 50px 60px 1fr 200px', gap:14, padding:'10px 16px', borderBottom:'1px solid var(--rule)', background:'var(--bg-2)' }}>
            <Mono upper size={9} color="var(--ink-3)"></Mono>
            <Mono upper size={9} color="var(--ink-3)">CANONICAL FIELD</Mono>
            <Mono upper size={9} color="var(--ink-3)">TYPE</Mono>
            <Mono upper size={9} color="var(--ink-3)">REQ</Mono>
            <Mono upper size={9} color="var(--ink-3)">SOURCE COLUMN</Mono>
            <Mono upper size={9} color="var(--ink-3)">EXAMPLE</Mono>
          </div>
          {schema.fields.map((f, i) => {
            const src = mapping[f.k];
            const matched = !!src;
            const sample = src && r.sample[0] ? r.sample[0].row[src] : null;
            return (
              <div key={f.k} style={{ display:'grid', gridTemplateColumns:'24px 220px 50px 60px 1fr 200px', gap:14, padding:'10px 16px', borderBottom: i < schema.fields.length-1 ? '1px solid var(--rule-soft)' : 0, alignItems:'center' }}>
                <span style={{ width:8, height:8, borderRadius:'50%', background: matched ? '#0a8754' : (f.req ? '#a32a18' : 'var(--rule)'), display:'inline-block' }}/>
                <div>
                  <div style={{ fontSize: 12.5, fontWeight: 500 }}>{f.k}</div>
                  <Mono size={10} color="var(--ink-3)" style={{ marginTop:1 }}>e.g. {f.ex}</Mono>
                </div>
                <Mono size={10.5} color="var(--ink-2)">{f.t}</Mono>
                {f.req
                  ? <Mono upper size={9} style={{ color:'#a32a18', fontWeight:600 }}>YES</Mono>
                  : <Mono upper size={9} color="var(--ink-3)">—</Mono>}
                <select value={src || ''} onChange={(e) => setMapping(f.k, e.target.value)} style={{ border:'1px solid ' + (matched ? 'var(--rule)' : (f.req ? '#a32a18' : 'var(--rule)')), background: matched ? 'var(--bg)' : (f.req ? '#fdebe8' : 'var(--bg)'), fontSize:12, padding:'0 24px 0 10px', width:'100%' }}>
                  <option value="">— unmapped —</option>
                  {headers.map(h => <option key={h} value={h}>{h}</option>)}
                </select>
                <Mono size={10.5} color="var(--ink-3)" style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{sample != null ? String(sample).slice(0, 40) : '—'}</Mono>
              </div>
            );
          })}
        </div>

        {unusedHeaders.length > 0 && (
          <div style={{ marginTop: 14, padding:'12px 16px', border:'1px solid var(--rule)', background:'var(--paper)' }}>
            <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 6, display:'block' }}>UNMAPPED COLUMNS · {unusedHeaders.length} · IGNORED ON IMPORT</Mono>
            <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
              {unusedHeaders.map(h => (
                <span key={h} className="ff-mono" style={{ fontSize: 10.5, padding:'3px 8px', border:'1px solid var(--rule)', background:'var(--bg-2)' }}>{h}</span>
              ))}
            </div>
          </div>
        )}

        <div style={{ marginTop: 16, display:'flex', justifyContent:'space-between', alignItems:'center' }}>
          <Mono size={11} color="var(--ink-3)">
            {Object.keys(mapping).length} of {schema.fields.length} canonical fields mapped
            {schema.fields.filter(f => f.req && !mapping[f.k]).length > 0 && (
              <span style={{ color:'#a32a18', marginLeft: 12 }}>· {schema.fields.filter(f => f.req && !mapping[f.k]).length} required unmapped</span>
            )}
          </Mono>
          <button onClick={() => onUpdate({ tab: 'validate', revalidate: true })} style={{ padding:'9px 20px', border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--bg)', fontSize:12, cursor:'pointer' }}>
            Validate →
          </button>
        </div>
      </div>
    );
  }

  // ════════════════════════════════════════════════════════════════
  // VALIDATE TAB — issue inbox
  // ════════════════════════════════════════════════════════════════
  function ValidateTab({ session, onUpdate }) {
    if (!session.result) return <Empty msg="Upload a file first."/>;
    const result = session.revalidatedResult || session.result;

    // Re-run pipeline if the user edited mapping/entity
    _E(() => {
      if (session.revalidate) {
        const r = E.runPipeline({
          name: session.fileName,
          text: session.text,
          entityOverride: session.entity,
          mappingOverride: session.mapping,
        });
        onUpdate({ revalidatedResult: r, revalidate: false });
      }
    }, [session.revalidate]);

    const [filter, setFilter] = _S('all');
    const findings = result.findings || [];
    const filtered = filter === 'all' ? findings :
                     filter === 'err' ? findings.filter(f => f.sev === 'error') :
                     findings.filter(f => f.sev === 'warn');

    const byKind = _M(() => {
      const m = {};
      findings.forEach(f => { m[f.kind] = (m[f.kind] || 0) + 1; });
      return Object.entries(m).sort((a,b) => b[1] - a[1]);
    }, [findings]);

    return (
      <div>
        {/* KPIs */}
        <div style={{ display:'grid', gridTemplateColumns:'repeat(5, 1fr)', borderTop:'1px solid var(--rule)', borderBottom:'1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="ROWS" value={result.rowCount.toLocaleString()} sub="parsed from file"/>
          <Cell label="CLEAN ROWS" value={(result.rowCount - result.stats.errors).toLocaleString()} sub="no validation errors" tone="#0a8754"/>
          <Cell label="ROWS · ERRORS" value={result.stats.errors.toLocaleString()} sub="will be skipped" tone={result.stats.errors > 0 ? '#a32a18' : 'var(--ink-3)'}/>
          <Cell label="ROWS · WARNINGS" value={result.stats.warnings.toLocaleString()} sub="commit but flag" tone={result.stats.warnings > 0 ? '#d4881f' : 'var(--ink-3)'}/>
          <Cell label="ISSUE TYPES" value={byKind.length} sub="distinct error kinds"/>
        </div>

        {/* By-kind summary */}
        {byKind.length > 0 && (
          <div style={{ display:'flex', flexWrap:'wrap', gap: 8, marginBottom: 18 }}>
            {byKind.map(([k, n]) => (
              <span key={k} className="ff-mono" style={{ fontSize: 11, padding:'5px 9px', border:'1px solid var(--rule)', background:'var(--paper)' }}>
                <span style={{ color:'var(--ink-3)' }}>{k}</span>
                <span style={{ marginLeft: 6, fontWeight: 600 }}>{n}</span>
              </span>
            ))}
          </div>
        )}

        {/* Filter bar */}
        <div style={{ display:'flex', gap:6, marginBottom: 14, flexWrap:'wrap' }}>
          {[{k:'all',l:'All'}, {k:'err',l:'Errors'}, {k:'warn',l:'Warnings'}].map(f => (
            <button key={f.k} onClick={() => setFilter(f.k)} className="ff-mono upper" style={{ fontSize:9, padding:'5px 9px', background: filter === f.k ? 'var(--ink)' : 'transparent', color: filter === f.k ? '#fff' : 'var(--ink-2)', border:'1px solid ' + (filter === f.k ? 'var(--ink)' : 'var(--rule)'), cursor:'pointer' }}>{f.l}</button>
          ))}
        </div>

        {/* Findings table */}
        <div style={{ border:'1px solid var(--rule)' }}>
          <div style={{ display:'grid', gridTemplateColumns:'80px 60px 180px 200px 1fr', gap:14, padding:'10px 16px', borderBottom:'1px solid var(--rule)', background:'var(--bg-2)' }}>
            <Mono upper size={9} color="var(--ink-3)">SEVERITY</Mono>
            <Mono upper size={9} color="var(--ink-3)">ROW #</Mono>
            <Mono upper size={9} color="var(--ink-3)">FIELD</Mono>
            <Mono upper size={9} color="var(--ink-3)">ISSUE KIND</Mono>
            <Mono upper size={9} color="var(--ink-3)">VALUE</Mono>
          </div>
          {filtered.length === 0
            ? <div style={{ padding:'40px 22px', textAlign:'center', color:'var(--ink-3)', fontSize: 12 }}>No issues found 🎉</div>
            : filtered.slice(0, 200).map((f, i) => (
              <div key={i} style={{ display:'grid', gridTemplateColumns:'80px 60px 180px 200px 1fr', gap:14, padding:'10px 16px', borderBottom:'1px solid var(--rule-soft)', alignItems:'baseline' }}>
                <Mono upper size={9} style={{ background: f.sev === 'error' ? '#a32a18' : '#d4881f', color:'#fff', padding:'2px 6px', display:'inline-block', width:'fit-content' }}>{f.sev}</Mono>
                <Mono size={11} color="var(--ink-3)">#{f.row + 2}</Mono>
                <Mono size={11.5} style={{ fontWeight: 500 }}>{f.field}</Mono>
                <Mono size={11} color="var(--ink-2)">{f.kind}</Mono>
                <Mono size={10.5} color="var(--ink-3)" style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{f.value || '∅'}</Mono>
              </div>
            ))}
          {filtered.length > 200 && (
            <div style={{ padding:'10px 16px', fontSize: 11, color:'var(--ink-3)', background:'var(--bg-2)' }}>
              Showing first 200 of {filtered.length}. Export the run for the full list.
            </div>
          )}
        </div>

        <div style={{ marginTop: 18, display:'flex', justifyContent:'space-between', alignItems:'center' }}>
          <button onClick={() => onUpdate({ tab: 'map' })} style={{ padding:'8px 16px', border:'1px solid var(--rule)', background:'transparent', fontSize:12, cursor:'pointer' }}>← Back to mapping</button>
          <button onClick={() => onUpdate({ tab: 'preview' })} style={{ padding:'9px 20px', border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--bg)', fontSize:12, cursor:'pointer' }}>Preview rows →</button>
        </div>
      </div>
    );
  }

  // ════════════════════════════════════════════════════════════════
  // PREVIEW TAB — row-by-row diff with create/update/dup tags
  // ════════════════════════════════════════════════════════════════
  function PreviewTab({ session, onUpdate }) {
    if (!session.result) return <Empty msg="Upload a file first."/>;
    const result = session.revalidatedResult || session.result;
    const schema = E.SCHEMAS[result.entity];
    const [filter, setFilter] = _S('all');
    const [activeRow, setActiveRow] = _S(null);

    const filtered = (result.sample || []).filter(s => {
      if (filter === 'create')    return s.dedup.action === 'create';
      if (filter === 'update')    return s.dedup.action === 'update';
      if (filter === 'duplicate') return s.dedup.action === 'duplicate-title';
      if (filter === 'errors')    return s.errors.length > 0;
      return true;
    });

    const titleField = schema.fields.find(f => f.req)?.k;
    const idField = schema.idKey;
    const naturalField = schema.natural;

    return (
      <div>
        <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', borderTop:'1px solid var(--rule)', borderBottom:'1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="WILL CREATE" value={result.stats.create.toLocaleString()} sub="new rows in RS" tone="#0a8754"/>
          <Cell label="WILL UPDATE" value={result.stats.update.toLocaleString()} sub="match natural key" tone="#1a4ed8"/>
          <Cell label="DUPLICATE" value={result.stats.duplicate.toLocaleString()} sub="title match · review" tone="#d4881f"/>
          <Cell label="WILL SKIP" value={result.stats.errors.toLocaleString()} sub="error rows" tone={result.stats.errors > 0 ? '#a32a18' : 'var(--ink-3)'}/>
        </div>

        <div style={{ display:'flex', gap:6, marginBottom:14, flexWrap:'wrap' }}>
          {[{k:'all',l:'All'}, {k:'create',l:'Create'}, {k:'update',l:'Update'}, {k:'duplicate',l:'Duplicate'}, {k:'errors',l:'Errors'}].map(f => (
            <button key={f.k} onClick={() => setFilter(f.k)} className="ff-mono upper" style={{ fontSize:9, padding:'5px 9px', background: filter === f.k ? 'var(--ink)' : 'transparent', color: filter === f.k ? '#fff' : 'var(--ink-2)', border:'1px solid ' + (filter === f.k ? 'var(--ink)' : 'var(--rule)'), cursor:'pointer' }}>{f.l}</button>
          ))}
        </div>

        <div style={{ display:'grid', gridTemplateColumns: activeRow != null ? '1fr 380px' : '1fr', gap: 18 }}>
          <div style={{ border:'1px solid var(--rule)' }}>
            <div style={{ display:'grid', gridTemplateColumns:'80px 60px 1fr 160px 60px', gap:14, padding:'10px 16px', borderBottom:'1px solid var(--rule)', background:'var(--bg-2)' }}>
              <Mono upper size={9} color="var(--ink-3)">ACTION</Mono>
              <Mono upper size={9} color="var(--ink-3)">ROW #</Mono>
              <Mono upper size={9} color="var(--ink-3)">{titleField || 'TITLE'}</Mono>
              <Mono upper size={9} color="var(--ink-3)">{naturalField}</Mono>
              <Mono upper size={9} color="var(--ink-3)" style={{ textAlign:'right' }}>ISSUES</Mono>
            </div>
            {filtered.slice(0, 100).map((s, i) => {
              const title = titleField ? (s.row[ session.mapping?.[titleField] || result.mapping[titleField] ] || '') : '';
              const natural = naturalField ? (s.row[ session.mapping?.[naturalField] || result.mapping[naturalField] ] || '') : '';
              const totalIssues = s.errors.length + s.warnings.length;
              return (
                <div key={i} onClick={() => setActiveRow(s.rowIndex)} style={{ display:'grid', gridTemplateColumns:'80px 60px 1fr 160px 60px', gap:14, padding:'10px 16px', borderBottom:'1px solid var(--rule-soft)', alignItems:'center', cursor:'pointer', background: activeRow === s.rowIndex ? 'var(--bg-2)' : 'transparent' }}>
                  <ActionPill action={s.dedup.action}/>
                  <Mono size={10.5} color="var(--ink-3)">#{s.rowIndex + 2}</Mono>
                  <div style={{ fontSize: 12.5, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{title || <span style={{ color:'var(--ink-3)' }}>—</span>}</div>
                  <Mono size={10.5} color="var(--ink-3)" style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{natural || '—'}</Mono>
                  <Mono size={10.5} style={{ textAlign:'right', color: s.errors.length > 0 ? '#a32a18' : (s.warnings.length > 0 ? '#d4881f' : 'var(--ink-3)') }}>
                    {totalIssues || '—'}
                  </Mono>
                </div>
              );
            })}
            {filtered.length > 100 && (
              <div style={{ padding:'10px 16px', fontSize:11, color:'var(--ink-3)', background:'var(--bg-2)' }}>
                Showing first 100 of {filtered.length.toLocaleString()}.
              </div>
            )}
          </div>

          {/* Row detail drawer */}
          {activeRow != null && (() => {
            const s = result.sample.find(x => x.rowIndex === activeRow);
            if (!s) return null;
            return (
              <div style={{ border:'1px solid var(--rule)', borderTop:'4px solid var(--ink)', background:'var(--paper)', padding: 18, position:'sticky', top: 80, height:'fit-content', maxHeight: 'calc(100vh - 120px)', overflow:'auto' }}>
                <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 14 }}>
                  <Mono upper size={9} color="var(--ink-3)">ROW #{s.rowIndex + 2}</Mono>
                  <button onClick={() => setActiveRow(null)} style={{ fontSize: 16, color:'var(--ink-3)', padding: 4 }}>×</button>
                </div>
                <div style={{ display:'flex', gap:8, marginBottom: 14, flexWrap:'wrap' }}>
                  <ActionPill action={s.dedup.action}/>
                  {s.dedup.existing && <Pill tone="ghost">match: {s.dedup.key.slice(0,18)}</Pill>}
                </div>
                {schema.fields.map(f => {
                  const src = (session.mapping || result.mapping)[f.k];
                  const incoming = src ? s.row[src] : '';
                  const existing = s.dedup.existing ? s.dedup.existing[f.k] : null;
                  if (!incoming && !existing) return null;
                  const changed = s.dedup.action === 'update' && existing != null && existing !== incoming && incoming !== '';
                  return (
                    <div key={f.k} style={{ marginBottom: 10, paddingBottom: 10, borderBottom:'1px solid var(--rule-soft)' }}>
                      <Mono upper size={9} color="var(--ink-3)">{f.k}</Mono>
                      <div style={{ fontSize: 12, marginTop: 2, color: changed ? '#1a4ed8' : 'var(--ink)' }}>
                        {existing != null && existing !== incoming
                          ? <>
                              <span style={{ color:'var(--ink-3)', textDecoration: incoming ? 'line-through' : 'none' }}>{String(existing) || '∅'}</span>
                              {incoming ? <span style={{ marginLeft: 8, fontWeight: 600 }}>→ {String(incoming)}</span> : null}
                            </>
                          : (incoming || existing || '∅')}
                      </div>
                    </div>
                  );
                })}
                {(s.errors.length > 0 || s.warnings.length > 0) && (
                  <div style={{ marginTop: 10, padding: 10, background:'var(--bg-2)', border:'1px solid var(--rule)' }}>
                    <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 6, display:'block' }}>ISSUES</Mono>
                    {[...s.errors.map(e => ({...e, sev:'error'})), ...s.warnings.map(w => ({...w, sev:'warn'}))].map((iss, i) => (
                      <div key={i} style={{ fontSize: 11, marginBottom: 4 }}>
                        <Mono upper size={9} style={{ background: iss.sev === 'error' ? '#a32a18' : '#d4881f', color:'#fff', padding:'1px 5px' }}>{iss.sev}</Mono>
                        <span style={{ marginLeft: 6 }}>{iss.field} · {iss.kind}</span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            );
          })()}
        </div>

        <div style={{ marginTop: 18, display:'flex', justifyContent:'space-between', alignItems:'center' }}>
          <button onClick={() => onUpdate({ tab: 'validate' })} style={{ padding:'8px 16px', border:'1px solid var(--rule)', background:'transparent', fontSize:12, cursor:'pointer' }}>← Back to validation</button>
          <button onClick={() => onUpdate({ tab: 'commit' })} style={{ padding:'9px 20px', border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--bg)', fontSize:12, cursor:'pointer' }}>Commit →</button>
        </div>
      </div>
    );
  }

  // ════════════════════════════════════════════════════════════════
  // COMMIT TAB
  // ════════════════════════════════════════════════════════════════
  function CommitTab({ session, onUpdate }) {
    if (!session.result) return <Empty msg="Upload a file first."/>;
    const result = session.revalidatedResult || session.result;
    const schema = E.SCHEMAS[result.entity];
    const [committed, setCommitted] = _S(null);

    function doCommit() {
      const c = E.commit(result);
      setCommitted({ ...c, at: Date.now() });
      // Add to run history
      const runs = E.seedRunHistory();
      runs.unshift({
        id: 'bi_live_' + Date.now(),
        fileName: session.fileName || 'commit.csv',
        entity: result.entity,
        rowCount: result.rowCount,
        createCount: c.created,
        updateCount: c.updated,
        dupCount: 0,
        errCount: result.stats.errors,
        warnCount: result.stats.warnings,
        status: result.stats.errors > 0 ? 'partial' : 'ok',
        startedAt: Date.now() - 200,
        ms: result.ms | 0,
        user: 'Avery Cohen',
        format: result.format,
        source: 'Live import',
      });
    }

    if (committed) {
      return (
        <div style={{ padding: 40, textAlign:'center' }}>
          <div style={{ fontSize: 64, marginBottom: 14 }}>✓</div>
          <div style={{ fontSize: 22, fontWeight: 700, marginBottom: 8 }}>Import committed</div>
          <Mono size={12} color="var(--ink-2)" style={{ marginBottom: 22 }}>
            {fmtDate(committed.at)} · {schema.label.toLowerCase()}
          </Mono>
          <div style={{ display:'inline-grid', gridTemplateColumns:'repeat(4, 1fr)', gap: 0, border:'1px solid var(--rule)', maxWidth: 700, margin:'0 auto' }}>
            <Cell label="CREATED" value={committed.created.toLocaleString()} sub="new rows" tone="#0a8754"/>
            <Cell label="UPDATED" value={committed.updated.toLocaleString()} sub="existing rows" tone="#1a4ed8"/>
            <Cell label="SKIPPED" value={committed.skipped.toLocaleString()} sub="error / dup" tone={committed.skipped > 0 ? '#d4881f' : 'var(--ink-3)'}/>
            <Cell label="TOTAL APPLIED" value={committed.applied.toLocaleString()} sub="committed to RS"/>
          </div>
          <div style={{ marginTop: 22 }}>
            <button onClick={() => onUpdate({ result: null, fileName: null, text: null, entity: null, mapping: null, revalidatedResult: null, tab: 'upload' })} style={{ padding:'9px 20px', border:'1px solid var(--ink)', background:'var(--ink)', color:'var(--bg)', fontSize:12, cursor:'pointer' }}>Import another file</button>
          </div>
        </div>
      );
    }

    const blockers = schema.fields.filter(f => f.req && !((session.mapping || result.mapping)[f.k])).length;

    return (
      <div>
        <div style={{ padding:'18px 22px', background:'var(--bg-2)', border:'1px solid var(--rule)', marginBottom: 22 }}>
          <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 4, display:'block' }}>FINAL REVIEW · DRY-RUN</Mono>
          <div style={{ fontSize: 13.5, color:'var(--ink-2)', lineHeight: 1.5 }}>
            Committing will apply this import to <span style={{ fontWeight: 600 }}>{schema.label}</span> in the active workspace. Error rows are skipped automatically.
            Title-only duplicates are <span style={{ fontWeight: 600 }}>NOT auto-merged</span> — review each in the Preview tab and re-run if needed.
          </div>
        </div>

        <div style={{ display:'grid', gridTemplateColumns:'repeat(4, 1fr)', borderTop:'1px solid var(--rule)', borderBottom:'1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="WILL CREATE" value={result.stats.create.toLocaleString()} sub="new rows in RS" tone="#0a8754"/>
          <Cell label="WILL UPDATE" value={result.stats.update.toLocaleString()} sub="match natural key" tone="#1a4ed8"/>
          <Cell label="DUPLICATE · SKIP" value={result.stats.duplicate.toLocaleString()} sub="title match · review later" tone="#d4881f"/>
          <Cell label="ERROR · SKIP" value={result.stats.errors.toLocaleString()} sub="will be skipped" tone={result.stats.errors > 0 ? '#a32a18' : 'var(--ink-3)'}/>
        </div>

        <div style={{ border:'1px solid var(--rule)', padding: 18, background:'var(--paper)', marginBottom: 18 }}>
          <Mono upper size={9} color="var(--ink-3)" style={{ marginBottom: 10, display:'block' }}>RUN MANIFEST</Mono>
          <div style={{ display:'grid', gridTemplateColumns:'180px 1fr', gap: 8, fontSize: 12 }}>
            <Mono size={11} color="var(--ink-3)">SOURCE FILE</Mono><div>{session.fileName || 'pasted.csv'}</div>
            <Mono size={11} color="var(--ink-3)">FORMAT</Mono><div>{result.format.toUpperCase()}</div>
            <Mono size={11} color="var(--ink-3)">ENTITY</Mono><div>{schema.label}</div>
            <Mono size={11} color="var(--ink-3)">FIELDS MAPPED</Mono><div>{Object.keys(session.mapping || result.mapping).length} of {schema.fields.length}</div>
            <Mono size={11} color="var(--ink-3)">ROWS</Mono><div>{result.rowCount.toLocaleString()}</div>
            <Mono size={11} color="var(--ink-3)">PARSE TIME</Mono><div>{(result.ms | 0)} ms</div>
            <Mono size={11} color="var(--ink-3)">WORKSPACE</Mono><div>Pluralis Music · Avery Cohen</div>
          </div>
        </div>

        {blockers > 0 ? (
          <div style={{ padding: '16px 18px', background:'#fdebe8', border:'1px solid #a32a18', marginBottom: 18 }}>
            <Mono upper size={9} style={{ color:'#a32a18', marginBottom: 4, display:'block' }}>BLOCKED · {blockers} REQUIRED FIELDS UNMAPPED</Mono>
            <div style={{ fontSize: 12, color:'var(--ink-2)' }}>Go back to <button onClick={() => onUpdate({ tab:'map' })} style={{ color:'var(--ink)', textDecoration:'underline', fontSize: 12 }}>Mapping</button> and connect every required field before committing.</div>
          </div>
        ) : null}

        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
          <button onClick={() => onUpdate({ tab: 'preview' })} style={{ padding:'8px 16px', border:'1px solid var(--rule)', background:'transparent', fontSize:12, cursor:'pointer' }}>← Back to preview</button>
          <button onClick={doCommit} disabled={blockers > 0} style={{ padding:'11px 28px', border:'1px solid #0a8754', background: blockers > 0 ? 'var(--rule-soft)' : '#0a8754', color: blockers > 0 ? 'var(--ink-3)' : '#fff', fontSize:13, fontWeight: 600, cursor: blockers > 0 ? 'not-allowed' : 'pointer' }}>
            Commit {result.stats.create + result.stats.update} rows
          </button>
        </div>
      </div>
    );
  }

  function Empty({ msg }) {
    return <div style={{ padding: '60px 30px', textAlign:'center', color:'var(--ink-3)', fontSize: 13 }}>{msg}</div>;
  }

  // ════════════════════════════════════════════════════════════════
  // MAIN
  // ════════════════════════════════════════════════════════════════
  function ScreenBulkImport({ go, payload }) {
    const PageHeader = window.PageHeader;
    const [tab, setTab] = _S(payload?.tab || 'upload');
    const [session, setSession] = _S({
      fileName: null, text: null, result: null,
      entity: null, mapping: null, revalidatedResult: null,
      revalidate: false,
    });

    const onUpdate = (patch) => {
      setSession(s => ({ ...s, ...patch }));
      if (patch.tab) setTab(patch.tab);
    };

    const onResult = (fileName, text, result) => {
      setSession({
        fileName, text, result,
        entity: result.entity, mapping: result.mapping,
        revalidatedResult: null, revalidate: false,
      });
      if (result.ok) setTab('map');
    };

    const TABS = [
      { k: 'upload',   l: '01 · Upload' },
      { k: 'map',      l: '02 · Map' },
      { k: 'validate', l: '03 · Validate' },
      { k: 'preview',  l: '04 · Preview' },
      { k: 'commit',   l: '05 · Commit' },
    ];

    const counts = E.seedRunHistory();
    const eyebrow = ['CATALOG', 'BULK IMPORT', `${Object.keys(E.SCHEMAS).length} ENTITIES`, `${counts.length} PAST RUNS`];

    return (
      <div data-screen-label="Bulk Catalog Import">
        {PageHeader && (
          <PageHeader
            eyebrow={eyebrow}
            title="bulk import."
            highlight="bulk import."
            sub="Drop CSVs · TSVs · JSON · DDEX-XML · CWR. Auto-detects entity (works · recordings · releases · profiles · publishers · agreements · videos), suggests column mappings, validates, dedups against your existing catalog, and commits in a dry-run-first pipeline."
          />
        )}

        <div style={{ borderBottom:'1px solid var(--rule)', display:'flex', gap: 0, marginBottom: 24 }}>
          {TABS.map(t => {
            const enabled = t.k === 'upload' || !!session.result;
            return (
              <button key={t.k} onClick={() => enabled && setTab(t.k)} style={{
                padding:'14px 22px', background:'transparent', border:0,
                borderBottom:'2px solid ' + (tab === t.k ? 'var(--ink)' : 'transparent'),
                cursor: enabled ? 'pointer' : 'not-allowed',
                color: tab === t.k ? 'var(--ink)' : (enabled ? 'var(--ink-3)' : 'var(--ink-4)'),
                fontSize:13, fontWeight: tab === t.k ? 600 : 400, opacity: enabled ? 1 : 0.55,
              }}>{t.l}</button>
            );
          })}
        </div>

        {tab === 'upload'   && <UploadTab session={session} onResult={onResult}/>}
        {tab === 'map'      && <MapTab session={session} onUpdate={onUpdate}/>}
        {tab === 'validate' && <ValidateTab session={session} onUpdate={onUpdate}/>}
        {tab === 'preview'  && <PreviewTab session={session} onUpdate={onUpdate}/>}
        {tab === 'commit'   && <CommitTab session={session} onUpdate={onUpdate}/>}
      </div>
    );
  }

  window.ScreenBulkImport = ScreenBulkImport;
})();
