// stmt-parser-custom.jsx — Custom adapter / column-mapping UI
// ─────────────────────────────────────────────────────────────────
// Lets a user upload an unrecognized statement file, preview rows,
// drag-map source columns to canonical fields, save the mapping
// as a custom adapter, and re-use it on subsequent runs.
//
// Adds a "Custom mapping" button to the stmt-parser ingest tab.
// Mapped runs go through STMT_PARSER_ENGINE.ingest() with a
// dynamically-injected adapter so existing validation/matching/
// commit pipeline still applies.
//
// Persists user adapters via localStorage (astro.stmt.custom.v1).
// Exports: window.ScreenStmtCustom, window.STMT_CUSTOM_ENGINE
// ─────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined' || !window.React) return;
  const _S = React.useState, _E = React.useEffect, _M = React.useMemo;

  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>;
  }

  const STORAGE_KEY = 'astro.stmt.custom.v1';

  function loadCustom() {
    try { const r = localStorage.getItem(STORAGE_KEY); if (r) return JSON.parse(r); } catch (e) {}
    return [];
  }
  function saveCustom(list) {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); } catch (e) {}
  }

  // Canonical fields with friendly groupings
  const CANONICAL_GROUPS = [
    { label: 'Required', fields: ['workTitle', 'grossAmount', 'currency', 'territory', 'periodStart', 'periodEnd', 'productType'] },
    { label: 'Identifiers', fields: ['iswc', 'isrc', 'sourceId'] },
    { label: 'Usage', fields: ['dsp', 'usageType', 'units'] },
    { label: 'Money', fields: ['netAmount', 'fxToUsd', 'sharePct'] },
    { label: 'Other', fields: ['writers', 'rightsType'] },
  ];

  function buildAdapterFromMapping({ id, label, vendor, mapping, sample }) {
    return {
      id,
      label,
      vendor,
      format: 'CSV-mapped',
      _custom: true,
      _mapping: mapping,
      sniff: () => 0, // never auto-detect; only fired manually
      parse: function (file, ctx) {
        const lines = [];
        const errors = [];
        const warnings = [];
        const rows = ctx.rows || [];
        rows.forEach((row, idx) => {
          const out = { rawRowIndex: idx + 1, rawRow: row, sourceId: id };
          for (const [canonical, sourceCol] of Object.entries(mapping)) {
            if (!sourceCol) continue;
            const v = row[sourceCol];
            if (v == null || v === '') continue;
            if (canonical === 'grossAmount' || canonical === 'netAmount' || canonical === 'units' || canonical === 'fxToUsd' || canonical === 'sharePct') {
              const num = parseFloat(String(v).replace(/[^0-9.\-]/g, ''));
              if (!isNaN(num)) out[canonical] = num;
            } else {
              out[canonical] = String(v).trim();
            }
          }
          // Defaults
          if (!out.productType && mapping._defaultProductType) out.productType = mapping._defaultProductType;
          if (!out.currency && mapping._defaultCurrency) out.currency = mapping._defaultCurrency;
          out.lineId = `${id}_${idx + 1}`;
          if ((+out.grossAmount || 0) > 0) {
            lines.push(out);
          } else {
            errors.push({ rowIndex: idx + 1, kind: 'missing-gross', msg: 'Gross amount missing or zero' });
          }
        });
        return { lines, warnings, errors };
      },
    };
  }

  // CSV parse — RFC 4180-ish
  function parseCSV(text) {
    const rows = [];
    let row = [], cell = '', q = false, i = 0;
    while (i < text.length) {
      const c = text[i];
      if (q) {
        if (c === '"' && text[i + 1] === '"') { cell += '"'; i += 2; continue; }
        if (c === '"') { q = false; i++; continue; }
        cell += c; i++;
      } else {
        if (c === '"') { q = true; i++; }
        else if (c === ',') { row.push(cell); cell = ''; i++; }
        else if (c === '\n' || c === '\r') {
          row.push(cell); cell = '';
          if (row.length && (row.length > 1 || row[0] !== '')) rows.push(row);
          row = [];
          if (c === '\r' && text[i + 1] === '\n') i += 2; else i++;
        } else { cell += c; i++; }
      }
    }
    if (cell !== '' || row.length) { row.push(cell); rows.push(row); }
    return rows;
  }

  function csvToObjects(rows) {
    if (!rows.length) return [];
    const headers = rows[0].map(h => h.trim());
    return rows.slice(1).map(r => {
      const o = {};
      headers.forEach((h, i) => { o[h] = (r[i] || '').trim(); });
      return o;
    });
  }

  window.STMT_CUSTOM_ENGINE = { loadCustom, saveCustom, buildAdapterFromMapping };

  // ─── SCREEN ──────────────────────────────────────────────────
  function ScreenStmtCustom({ go, payload }) {
    const PageHeader = window.PageHeader;
    const [tab, setTab] = _S(payload?.tab || 'new');

    return (
      <div>
        {PageHeader && (
          <PageHeader
            eyebrow={['STATEMENTS', 'CUSTOM ADAPTER', 'COLUMN MAPPER']}
            title="map your own."
            highlight="own"
            sub="Upload an unrecognized CSV, drag your columns onto our canonical fields, save it as a reusable adapter."
          />
        )}

        <div style={{ borderBottom: '1px solid var(--rule)', display: 'flex', gap: 0, marginBottom: 24 }}>
          {[{ k: 'new', l: 'New mapping' }, { k: 'saved', l: 'Saved adapters' }].map(t => (
            <button key={t.k} onClick={() => setTab(t.k)} style={{
              padding: '14px 22px', background: 'transparent',
              borderBottom: '2px solid ' + (tab === t.k ? 'var(--ink)' : 'transparent'),
              color: tab === t.k ? 'var(--ink)' : 'var(--ink-3)',
              fontSize: 13, fontWeight: tab === t.k ? 600 : 400, cursor: 'pointer',
            }}>{t.l}</button>
          ))}
        </div>

        {tab === 'new' && <NewMappingTab/>}
        {tab === 'saved' && <SavedTab/>}
      </div>
    );
  }

  function NewMappingTab() {
    const [file, setFile] = _S(null);
    const [headers, setHeaders] = _S([]);
    const [rows, setRows] = _S([]);
    const [mapping, setMapping] = _S({});
    const [meta, setMeta] = _S({ label: '', vendor: '', defaultProductType: 'performance', defaultCurrency: 'USD' });
    const [stage, setStage] = _S('upload'); // upload | map | preview

    function onPick(e) {
      const f = e.target.files?.[0];
      if (!f) return;
      const reader = new FileReader();
      reader.onload = () => {
        const text = String(reader.result || '');
        const rawRows = parseCSV(text);
        if (!rawRows.length) return;
        const hs = rawRows[0].map(h => h.trim());
        setHeaders(hs);
        setRows(csvToObjects(rawRows));
        setFile(f);
        setMeta(m => ({ ...m, label: m.label || f.name.replace(/\.csv$/i, '') }));
        setStage('map');
      };
      reader.readAsText(f);
    }

    function setMap(canonical, source) {
      setMapping(prev => {
        const next = { ...prev };
        if (!source) delete next[canonical]; else next[canonical] = source;
        return next;
      });
    }

    function persist() {
      const id = 'custom_' + Date.now().toString(36);
      const fullMapping = { ...mapping, _defaultProductType: meta.defaultProductType, _defaultCurrency: meta.defaultCurrency };
      const adapter = { id, label: meta.label || 'Custom adapter', vendor: meta.vendor || 'Custom', mapping: fullMapping, headers, savedAt: new Date().toISOString() };
      const list = loadCustom();
      list.push(adapter);
      saveCustom(list);
      // Inject into engine for current session
      if (window.STMT_PARSER_ENGINE) {
        const built = buildAdapterFromMapping({ id, label: adapter.label, vendor: adapter.vendor, mapping: fullMapping });
        if (!window.STMT_PARSER_ENGINE.ADAPTERS.find(a => a.id === id)) {
          window.STMT_PARSER_ENGINE.ADAPTERS.push(built);
        }
      }
      alert('Saved · adapter "' + adapter.label + '" is now available in the parser inbox.');
      setStage('upload'); setFile(null); setHeaders([]); setRows([]); setMapping({}); setMeta({ label: '', vendor: '', defaultProductType: 'performance', defaultCurrency: 'USD' });
    }

    if (stage === 'upload') {
      return (
        <div style={{ border: '2px dashed var(--rule)', padding: 60, textAlign: 'center', background: 'var(--paper)' }}>
          <Mono upper size={9} color="var(--ink-3)">DROP A CSV TO START</Mono>
          <div className="ff-display" style={{ fontSize: 28, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 10, marginBottom: 18 }}>Pick the file we don't yet know how to read.</div>
          <input type="file" accept=".csv,text/csv" onChange={onPick} style={{ fontSize: 12 }}/>
        </div>
      );
    }

    return (
      <div>
        <div style={{ display: 'flex', gap: 12, alignItems: 'baseline', marginBottom: 18 }}>
          <Mono upper size={9} color="var(--ink-3)">SOURCE</Mono>
          <div style={{ fontSize: 13, fontWeight: 500 }}>{file?.name}</div>
          <Mono size={11} color="var(--ink-3)">· {rows.length} rows · {headers.length} columns</Mono>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 320px', gap: 22 }}>
          <div>
            <Mono upper size={9} color="var(--ink-3)">MAP CANONICAL → YOUR COLUMN</Mono>
            <div style={{ border: '1px solid var(--rule)', marginTop: 10 }}>
              {CANONICAL_GROUPS.map((grp, gi) => (
                <div key={grp.label} style={{ borderBottom: gi < CANONICAL_GROUPS.length - 1 ? '1px solid var(--rule)' : 0 }}>
                  <div style={{ padding: '7px 14px', background: 'var(--bg-2)', borderBottom: '1px solid var(--rule-soft)' }}>
                    <Mono upper size={9} color="var(--ink-3)">{grp.label}</Mono>
                  </div>
                  {grp.fields.map(f => (
                    <div key={f} style={{ display: 'grid', gridTemplateColumns: '180px 1fr 200px', gap: 12, padding: '8px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
                      <Mono size={11}>{f}</Mono>
                      <select value={mapping[f] || ''} onChange={(e) => setMap(f, e.target.value)} style={{ ...inp, width: '100%' }}>
                        <option value="">— skip —</option>
                        {headers.map(h => <option key={h} value={h}>{h}</option>)}
                      </select>
                      <Mono size={10} color="var(--ink-3)" style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {mapping[f] && rows[0]?.[mapping[f]] ? 'sample: ' + String(rows[0][mapping[f]]).slice(0, 40) : ''}
                      </Mono>
                    </div>
                  ))}
                </div>
              ))}
            </div>
          </div>

          <div>
            <Mono upper size={9} color="var(--ink-3)">ADAPTER METADATA</Mono>
            <div style={{ border: '1px solid var(--rule)', padding: 16, marginTop: 10, background: 'var(--paper)' }}>
              <div style={{ display: 'grid', gridTemplateColumns: '90px 1fr', gap: 10, alignItems: 'center', fontSize: 12 }}>
                <label>Label</label>
                <input value={meta.label} onChange={(e) => setMeta({ ...meta, label: e.target.value })} style={inp}/>
                <label>Vendor</label>
                <input value={meta.vendor} onChange={(e) => setMeta({ ...meta, vendor: e.target.value })} style={inp}/>
                <label>Default type</label>
                <select value={meta.defaultProductType} onChange={(e) => setMeta({ ...meta, defaultProductType: e.target.value })} style={inp}>
                  <option value="performance">performance</option>
                  <option value="mechanical">mechanical</option>
                  <option value="sync">sync</option>
                  <option value="neighbour">neighbour</option>
                </select>
                <label>Default currency</label>
                <input value={meta.defaultCurrency} onChange={(e) => setMeta({ ...meta, defaultCurrency: e.target.value.toUpperCase() })} style={inp}/>
              </div>
              <div style={{ borderTop: '1px solid var(--rule-soft)', marginTop: 14, paddingTop: 14 }}>
                <Mono upper size={9} color="var(--ink-3)">MAPPED FIELDS · {Object.keys(mapping).length}</Mono>
                <div style={{ marginTop: 6, fontSize: 11 }}>
                  {['workTitle', 'grossAmount', 'currency', 'territory'].map(req => (
                    <div key={req} style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
                      <Mono size={10}>{req}</Mono>
                      <Mono size={10} color={mapping[req] ? '#0a8754' : '#a32a18'}>{mapping[req] ? '✓ ' + mapping[req] : '× missing'}</Mono>
                    </div>
                  ))}
                </div>
              </div>
            </div>
            <button onClick={persist} disabled={!mapping.workTitle || !mapping.grossAmount} style={{ width: '100%', padding: '10px 16px', marginTop: 12, border: '1px solid var(--ink)', background: (!mapping.workTitle || !mapping.grossAmount) ? 'var(--rule)' : 'var(--ink)', color: (!mapping.workTitle || !mapping.grossAmount) ? 'var(--ink-3)' : 'var(--bg)', fontSize: 12, fontWeight: 600, cursor: (!mapping.workTitle || !mapping.grossAmount) ? 'not-allowed' : 'pointer' }}>
              Save adapter & inject
            </button>
            <button onClick={() => { setStage('upload'); setFile(null); setHeaders([]); setRows([]); setMapping({}); }} style={{ width: '100%', padding: '8px 16px', marginTop: 8, border: '1px solid var(--rule)', background: 'transparent', fontSize: 11, cursor: 'pointer' }}>
              Cancel
            </button>
          </div>
        </div>

        {/* Sample preview */}
        {rows.length > 0 && (
          <div style={{ marginTop: 26 }}>
            <Mono upper size={9} color="var(--ink-3)">PREVIEW · FIRST 3 ROWS</Mono>
            <div style={{ border: '1px solid var(--rule)', marginTop: 10, overflowX: 'auto' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11 }}>
                <thead><tr style={{ background: 'var(--bg-2)' }}>{headers.map(h => <th key={h} style={{ padding: '6px 10px', textAlign: 'left', borderBottom: '1px solid var(--rule)', whiteSpace: 'nowrap' }}>{h}</th>)}</tr></thead>
                <tbody>{rows.slice(0, 3).map((r, i) => (
                  <tr key={i} style={{ borderBottom: '1px solid var(--rule-soft)' }}>
                    {headers.map(h => <td key={h} style={{ padding: '5px 10px', whiteSpace: 'nowrap' }}>{String(r[h] || '').slice(0, 40)}</td>)}
                  </tr>
                ))}</tbody>
              </table>
            </div>
          </div>
        )}
      </div>
    );
  }

  function SavedTab() {
    const [list, setList] = _S(loadCustom());
    function del(id) {
      if (!confirm('Delete adapter?')) return;
      const next = list.filter(a => a.id !== id);
      setList(next); saveCustom(next);
      if (window.STMT_PARSER_ENGINE) {
        window.STMT_PARSER_ENGINE.ADAPTERS = window.STMT_PARSER_ENGINE.ADAPTERS.filter(a => a.id !== id);
      }
    }
    if (!list.length) {
      return <div style={{ padding: 40, textAlign: 'center', border: '1px solid var(--rule)' }}><Mono color="var(--ink-3)">No saved custom adapters yet</Mono></div>;
    }
    return (
      <div style={{ border: '1px solid var(--rule)' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 200px 130px 100px 80px', gap: 12, padding: '10px 16px', background: 'var(--bg-2)', borderBottom: '1px solid var(--rule)' }}>
          <Mono upper size={9} color="var(--ink-3)">LABEL</Mono>
          <Mono upper size={9} color="var(--ink-3)">VENDOR</Mono>
          <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>FIELDS</Mono>
          <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>SAVED</Mono>
          <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>—</Mono>
        </div>
        {list.map((a, i) => (
          <div key={a.id} style={{ display: 'grid', gridTemplateColumns: '1fr 200px 130px 100px 80px', gap: 12, padding: '11px 16px', borderBottom: i < list.length - 1 ? '1px solid var(--rule-soft)' : 0, alignItems: 'center' }}>
            <div style={{ fontSize: 13, fontWeight: 500 }}>{a.label}</div>
            <Mono size={11} color="var(--ink-3)">{a.vendor}</Mono>
            <Mono size={11} style={{ textAlign: 'right' }}>{Object.keys(a.mapping || {}).filter(k => !k.startsWith('_')).length}</Mono>
            <Mono size={10} color="var(--ink-3)" style={{ textAlign: 'right' }}>{a.savedAt?.slice(0, 10)}</Mono>
            <div style={{ textAlign: 'right' }}><button onClick={() => del(a.id)} style={{ padding: '4px 8px', border: '1px solid var(--rule)', background: 'transparent', fontSize: 10, cursor: 'pointer', color: '#a32a18' }}>Delete</button></div>
          </div>
        ))}
      </div>
    );
  }

  const inp = { padding: '7px 10px', border: '1px solid var(--rule)', background: 'var(--bg)', color: 'var(--ink)', fontSize: 12 };

  // Re-inject saved custom adapters at boot so the parser inbox sees them
  function rehydrate() {
    if (!window.STMT_PARSER_ENGINE) return;
    const saved = loadCustom();
    saved.forEach(a => {
      if (window.STMT_PARSER_ENGINE.ADAPTERS.find(x => x.id === a.id)) return;
      const built = buildAdapterFromMapping({ id: a.id, label: a.label, vendor: a.vendor, mapping: a.mapping });
      window.STMT_PARSER_ENGINE.ADAPTERS.push(built);
    });
  }
  // Defer: STMT_PARSER_ENGINE may not be ready yet
  setTimeout(rehydrate, 100);

  window.ScreenStmtCustom = ScreenStmtCustom;
  console.log('[StmtCustom] loaded');
})();
