// views/automap.jsx — the mapping agent. Reads what you have already
// classified, proposes the rest, applies only what it is sure about, and
// hands everything below the confidence bar to you for review.
function AutoMapView() {
  const s = useStore();
  const [threshold, setThreshold] = React.useState(() => Number(localStorage.getItem('fin.automap.threshold') || 90));
  const [dismissed, setDismissed] = React.useState(() => new Set());
  const [edits, setEdits] = React.useState({});
  const [undoStack, setUndoStack] = React.useState([]);
  const [sel, setSel] = React.useState(null); // null = all confident selected

  const model = React.useMemo(() => Classifier.build(s.transactions), [s.transactions]);

  const proposals = React.useMemo(() => s.transactions
    .filter(t => !t.classified && !dismissed.has(t.id))
    .map(t => ({ t, p: model.predict(t) }))
    .sort((a, b) => b.p.confidence - a.p.confidence), [s.transactions, model, dismissed]);

  const th = threshold / 100;
  const confident = proposals.filter(x => x.p.confidence >= th && x.p.type.value);
  const review = proposals.filter(x => x.p.confidence < th && x.p.type.value && x.p.confidence > 0);
  const blank = proposals.filter(x => !x.p.type.value || x.p.confidence === 0);
  const unclassifiedN = s.transactions.filter(t => !t.classified).length;

  const isSel = id => sel === null || sel.has(id);
  function toggleSel(id) {
    const base = sel === null ? new Set(confident.map(x => x.t.id)) : new Set(sel);
    if (base.has(id)) base.delete(id); else base.add(id);
    setSel(base);
  }

  // Only fields that clear the bar on their own merit get written. A sure
  // category next to a shaky customer guess should not drag the guess in.
  function patchFor(p, override) {
    const o = override || {};
    const patch = {};
    if (o.type || p.type.conf >= th) patch.type = o.type || p.type.value;
    if (o.category || (p.category.value && p.category.conf >= th)) patch.category = o.category || p.category.value;
    if (o.vendor || (p.vendor.value && p.vendor.conf >= th)) patch.vendor = o.vendor || p.vendor.value;
    if (o.customerId || (p.customerId.value && p.customerId.conf >= th)) patch.customerId = o.customerId || p.customerId.value;
    return patch;
  }

  function applyRows(rows, overrides) {
    if (!rows.length) return;
    const undo = rows.map(({ t }) => ({ id: t.id, prev: { type: t.type, category: t.category, vendor: t.vendor, customerId: t.customerId, classified: t.classified } }));
    rows.forEach(({ t, p }) => {
      const patch = patchFor(p, overrides ? overrides[t.id] : null);
      if (patch.type) Store.updateTx(t.id, patch);
    });
    setUndoStack(u => [{ at: Date.now(), rows: undo }, ...u].slice(0, 8));
    setSel(null);
    setEdits({});
  }

  function undoLast() {
    const batch = undoStack[0];
    if (!batch) return;
    batch.rows.forEach(r => Store.updateTx(r.id, r.prev));
    // updateTx re-derives `classified` from the type it is given, so a row that
    // was blank before goes back to blank on its own.
    setUndoStack(u => u.slice(1));
  }

  const custName = id => (s.customers.find(c => c.id === id) || {}).name || '';
  const toApply = confident.filter(x => isSel(x.t.id));

  return (
    <div>
      <div className="page-head">
        <div>
          <h1 className="page-title">The <em>mapping agent</em>.</h1>
          <p className="page-sub">
            Learned from {model.docs} classified {model.docs === 1 ? 'row' : 'rows'} · {model.patterns} narration patterns · everything is computed in this browser.
          </p>
        </div>
        <div className="page-actions">
          {undoStack.length > 0 && <button className="btn btn-ghost" onClick={undoLast}>Undo last batch ({undoStack[0].rows.length})</button>}
          <button className="btn btn-primary" disabled={!toApply.length} onClick={() => applyRows(toApply)}>
            Apply {toApply.length} confident {toApply.length === 1 ? 'mapping' : 'mappings'}
          </button>
        </div>
      </div>

      {model.docs < 12 && (
        <div className="agent-warn">
          Only {model.docs} classified rows to learn from. The agent will hold nearly everything back for review until the ledger has more history — classify a month by hand and come back.
        </div>
      )}

      <div className="stat-row">
        <div className="stat">
          <div className="stat-label">Unclassified</div>
          <div className="stat-value">{unclassifiedN}</div>
          <div className="stat-sub">in the ledger right now</div>
        </div>
        <div className="stat">
          <div className="stat-label">Agent is sure</div>
          <div className="stat-value" style={{ color: 'var(--green)' }}>{confident.length}</div>
          <div className="stat-sub">at or above {threshold}% confidence</div>
        </div>
        <div className="stat">
          <div className="stat-label">Needs your review</div>
          <div className="stat-value" style={{ color: 'var(--gold)' }}>{review.length}</div>
          <div className="stat-sub">suggested, but under the bar</div>
        </div>
        <div className="stat">
          <div className="stat-label">No suggestion</div>
          <div className="stat-value">{blank.length}</div>
          <div className="stat-sub">nothing comparable in history</div>
        </div>
      </div>

      <div className="toolbar">
        <span className="agent-th-label">Ask me when confidence is below</span>
        <input className="agent-slider" type="range" min="60" max="99" step="1" value={threshold}
          onChange={e => { const v = Number(e.target.value); setThreshold(v); localStorage.setItem('fin.automap.threshold', String(v)); setSel(null); }} />
        <b className="num agent-th-val">{threshold}%</b>
        <div className="sep"></div>
        <span className="muted" style={{ fontSize: 12 }}>
          Anything at or above applies without asking. Everything else waits below.
        </span>
        <div style={{ flex: 1 }}></div>
        {dismissed.size > 0 && <button className="btn btn-sm btn-ghost" onClick={() => setDismissed(new Set())}>Bring back {dismissed.size} skipped</button>}
      </div>

      {confident.length > 0 && (
        <div className="card" style={{ padding: 0, marginBottom: 24 }}>
          <div className="agent-sec-head">
            <div>
              <div className="agent-sec-title">Confident — will apply</div>
              <div className="muted" style={{ fontSize: 12 }}>Untick anything you would rather look at yourself.</div>
            </div>
            <button className="btn btn-sm" onClick={() => setSel(sel === null ? new Set() : null)}>{sel === null ? 'Untick all' : 'Tick all'}</button>
          </div>
          <div className="tbl-wrap">
            <table className="tbl">
              <thead>
                <tr>
                  <th style={{ width: 30 }}></th>
                  <th style={{ width: 68 }}>Date</th>
                  <th>Particulars</th>
                  <th style={{ width: 84 }}>Type</th>
                  <th style={{ width: 150 }}>Category</th>
                  <th style={{ width: 140 }}>Paid to / from</th>
                  <th style={{ width: 108, textAlign: 'right' }}>Amount</th>
                  <th style={{ width: 120 }}>Confidence</th>
                </tr>
              </thead>
              <tbody>
                {confident.map(({ t, p }) => (
                  <tr key={t.id} className={isSel(t.id) ? 'selected' : ''} onClick={() => toggleSel(t.id)}>
                    <td>
                      <div className={'checkbox ' + (isSel(t.id) ? 'checked' : '')}>
                        {isSel(t.id) && <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><polyline points="20 6 9 17 4 12" /></svg>}
                      </div>
                    </td>
                    <td className="num" style={{ fontSize: 12, color: 'var(--ink-2)' }}>{fmtDate(t.date)}</td>
                    <td>
                      <div style={{ fontWeight: 500, fontSize: 13, lineHeight: 1.35, wordBreak: 'break-word' }}>{t.particulars}</div>
                      <div className="muted" style={{ fontSize: 11 }}>{p.exact ? 'identical narration seen ' + p.repeats + '×' : 'matched on ' + (p.category.why || []).join(', ').toLowerCase()}</div>
                    </td>
                    <td><span className={'tag tag-' + p.type.value}>{p.type.value}</span></td>
                    <td style={{ fontSize: 12.5 }}>{p.category.value || <span className="muted">—</span>}</td>
                    <td style={{ fontSize: 12.5 }}>{(p.vendor.conf >= th && p.vendor.value) || <span className="muted">—</span>}</td>
                    <td className={'num t-amt ' + (t.direction === 'credit' ? 't-credit' : 't-debit')} style={{ fontWeight: 600 }}>
                      {t.direction === 'credit' ? '+' : '−'}{fmtINR(t.amount)}
                    </td>
                    <td><ConfMeter v={p.confidence} th={th} /></td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      <div className="agent-sec-title" style={{ marginBottom: 12 }}>
        Needs your review <span className="muted" style={{ fontWeight: 400, textTransform: 'none', letterSpacing: 0 }}>· {review.length} below {threshold}%</span>
      </div>

      {review.length === 0 && (
        <div className="card" style={{ textAlign: 'center', padding: 36, color: 'var(--ink-3)' }}>
          <div className="serif" style={{ fontSize: 22, color: 'var(--ink)' }}>Nothing waiting on you.</div>
          <div className="muted" style={{ fontSize: 12 }}>Every suggestion cleared the bar, or there is nothing left to map.</div>
        </div>
      )}

      <div className="agent-grid">
        {review.map(({ t, p }) => (
          <ReviewCard key={t.id} t={t} p={p} s={s} th={th}
            edit={edits[t.id] || {}}
            onEdit={patch => setEdits(e => ({ ...e, [t.id]: { ...(e[t.id] || {}), ...patch } }))}
            onAccept={vals => applyRows([{ t, p }], { [t.id]: vals })}
            onSkip={() => setDismissed(d => new Set([...d, t.id]))}
            custName={custName} />
        ))}
      </div>

      {blank.length > 0 && (
        <div className="card" style={{ marginTop: 24 }}>
          <div className="agent-sec-title" style={{ marginBottom: 8 }}>No comparable history · {blank.length}</div>
          <div className="muted" style={{ fontSize: 12, marginBottom: 12 }}>
            Nothing in the classified ledger resembles these. Classify one of each by hand in the ledger and the agent will carry the pattern forward.
          </div>
          <div className="agent-blank-list">
            {blank.slice(0, 30).map(({ t }) => (
              <div key={t.id} className="agent-blank-row" onClick={() => { window.__pendingTxEdit = t.id; location.hash = 'transactions'; }}>
                <span className="num" style={{ fontSize: 12, color: 'var(--ink-3)', width: 62 }}>{fmtDate(t.date)}</span>
                <span style={{ flex: 1, fontSize: 12.5, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.particulars}</span>
                <span className={'num ' + (t.direction === 'credit' ? 't-credit' : 't-debit')} style={{ fontSize: 12, fontWeight: 600 }}>
                  {t.direction === 'credit' ? '+' : '−'}{fmtINR(t.amount)}
                </span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function ConfMeter({ v, th }) {
  const pct = Math.round(v * 100);
  const tone = v >= th ? 'good' : v >= th - 0.2 ? 'mid' : 'low';
  return (
    <div className="conf">
      <div className={'conf-bar conf-' + tone}><i style={{ width: Math.max(4, pct) + '%' }}></i></div>
      <span className={'num conf-num conf-' + tone}>{pct}%</span>
    </div>
  );
}

function ReviewCard({ t, p, s, th, edit, onEdit, onAccept, onSkip, custName }) {
  const type = edit.type || p.type.value;
  const cats = type === 'income' ? s.categories.income : s.categories.expense;
  const cat = edit.category !== undefined ? edit.category : (p.category.value || '');
  const vendor = edit.vendor !== undefined ? edit.vendor : (p.vendor.value || '');
  const cust = edit.customerId !== undefined ? edit.customerId : (p.customerId.value || '');
  const alts = (p.category.alts || []).filter(a => a.conf > 0.05);
  const vendorNames = React.useMemo(() => {
    const set = new Set((s.vendors || []).map(v => v.name).filter(Boolean));
    (s.transactions || []).forEach(x => { if (x.vendor) set.add(x.vendor); });
    return [...set].sort((a, b) => a.localeCompare(b));
  }, [s.vendors, s.transactions]);

  return (
    <div className="card agent-card">
      <div className="agent-card-head">
        <div style={{ minWidth: 0 }}>
          <div className="muted" style={{ fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 600 }}>{fmtDateFull(t.date)} · {t.method}</div>
          <div className="agent-part">{t.particulars}</div>
        </div>
        <div style={{ textAlign: 'right' }}>
          <div className={'num ' + (t.direction === 'credit' ? 't-credit' : 't-debit')} style={{ fontSize: 18, fontWeight: 600 }}>
            {t.direction === 'credit' ? '+' : '−'}{fmtINR(t.amount)}
          </div>
          <ConfMeter v={p.confidence} th={th} />
        </div>
      </div>

      <div className="agent-why">
        <b>Why:</b>{' '}
        {p.exact
          ? <>this narration has been classified {p.repeats}× before</>
          : (p.category.why || p.type.why || []).length
            ? <>matched on {(p.category.why || p.type.why).map(w => <span key={w} className="agent-tok">{w}</span>)}</>
            : <>weak match — little in common with anything classified</>}
        {p.example && <> · closest prior row: <span className="agent-eg">{fmtDate(p.example.date)} {p.example.particulars}</span> → {p.example.category || p.example.type}</>}
      </div>

      <div className="agent-fields">
        <div>
          <label className="field-label">Type</label>
          <div className="pills">
            {['income', 'expense', 'transfer', 'draw'].map(x => (
              <div key={x} className={'pill ' + (type === x ? 'active' : '')} onClick={() => onEdit({ type: x, category: '' })}>{x}</div>
            ))}
          </div>
        </div>
        {(type === 'income' || type === 'expense') && (
          <div>
            <label className="field-label">
              Category
              {p.category.value && <span className="agent-alt-note">agent says {p.category.value} · {Math.round(p.category.conf * 100)}%</span>}
            </label>
            <select className="select" value={cat} onChange={e => onEdit({ category: e.target.value })}>
              <option value="">— pick category —</option>
              {cats.map(c => <option key={c} value={c}>{c}</option>)}
            </select>
            {alts.length > 0 && (
              <div className="agent-alts">
                also considered{' '}
                {alts.map(a => (
                  <button key={a.value} className="agent-alt" onClick={() => onEdit({ category: a.value })}>{a.value} · {Math.round(a.conf * 100)}%</button>
                ))}
              </div>
            )}
          </div>
        )}
        <div>
          <label className="field-label">{type === 'income' ? 'Received from' : 'Paid to'}</label>
          <select className="select" value={vendor} onChange={e => onEdit({ vendor: e.target.value })}>
            <option value="">— none —</option>
            {vendorNames.map(n => <option key={n} value={n}>{n}</option>)}
          </select>
        </div>
        <div>
          <label className="field-label">Customer</label>
          <select className="select" value={cust} onChange={e => onEdit({ customerId: e.target.value })}>
            <option value="">— none / overhead —</option>
            {s.customers.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
          </select>
        </div>
      </div>

      <div className="agent-card-foot">
        <button className="btn btn-ghost btn-sm" onClick={onSkip}>Skip for now</button>
        <div style={{ flex: 1 }}></div>
        <button className="btn btn-sm" onClick={() => { window.__pendingTxEdit = t.id; location.hash = 'transactions'; }}>Open in ledger</button>
        <button className="btn btn-sm btn-primary" disabled={!type} onClick={() => onAccept({ type, category: cat, vendor, customerId: cust })}>Confirm mapping</button>
      </div>
    </div>
  );
}

Object.assign(window, { AutoMapView, ConfMeter, ReviewCard });
