// OM Group ERP — DATA HEALTH & RECOVERY CONSOLE (administrative, read-only)
// Every panel here reads. The only write path is the snapshot restore, which
// runs a dry run first, requires explicit approval, is additive-only, preserves
// original IDs, and verifies itself against physical storage afterwards.
const { useState: dhSt, useEffect: dhEf, useMemo: dhMemo, useRef: dhRef } = React;

const DH = {
  wrap: { display: 'flex', flexDirection: 'column', gap: 12 },
  sec: { padding: '12px 14px' },
  h: { fontSize: 12, fontWeight: 700, letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--txt2)', margin: '0 0 10px' },
  mono: { fontFamily: 'ui-monospace,SFMono-Regular,Menlo,monospace', fontSize: 11.5 },
  kv: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(180px,1fr))', gap: 10 },
  stat: { border: '1px solid var(--bdr)', borderRadius: 6, padding: '9px 11px', background: '#FCFCFB' },
  statN: { fontSize: 20, fontWeight: 700, lineHeight: 1.1, fontVariantNumeric: 'tabular-nums' },
  statL: { fontSize: 10.5, color: 'var(--txt2)', textTransform: 'uppercase', letterSpacing: '.05em', marginTop: 3 },
  pill: (bg, fg, bd) => ({ display: 'inline-block', padding: '1px 7px', borderRadius: 20, fontSize: 10.5, fontWeight: 700, background: bg, color: fg, border: '1px solid ' + bd, whiteSpace: 'nowrap' }),
};
const OK_P = DH.pill('#F0FDF4', '#166534', '#BBF7D0');
const WARN_P = DH.pill('#FFFBEB', '#92400E', '#FDE68A');
const ERR_P = DH.pill('#FEF2F2', '#991B1B', '#FECACA');
const INFO_P = DH.pill('#EFF6FF', '#1E40AF', '#BFDBFE');

function DHBadge({ kind, children }) {
  const s = kind === 'ok' ? OK_P : kind === 'warn' ? WARN_P : kind === 'info' ? INFO_P : ERR_P;
  return <span style={s}>{children}</span>;
}

function DHStat({ n, label, kind }) {
  const c = kind === 'err' ? '#991B1B' : kind === 'warn' ? '#92400E' : kind === 'ok' ? '#166534' : 'var(--txt)';
  return <div style={DH.stat}><div style={{ ...DH.statN, color: c }}>{n}</div><div style={DH.statL}>{label}</div></div>;
}

function DHTally({ title, obj, empty }) {
  const keys = Object.keys(obj || {}).sort();
  if (!keys.length) return <div style={{ fontSize: 11.5, color: 'var(--txt3)' }}>{empty || '—'}</div>;
  return (
    <div>
      <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--txt2)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 5 }}>{title}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
        {keys.map(k => (
          <div key={k} style={{ display: 'flex', justifyContent: 'space-between', gap: 10, fontSize: 11.5, borderBottom: '1px dotted var(--bdr)', padding: '2px 0' }}>
            <span style={{ color: 'var(--txt2)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{k}</span>
            <strong style={{ fontVariantNumeric: 'tabular-nums' }}>{obj[k]}</strong>
          </div>
        ))}
      </div>
    </div>
  );
}

function DHProfile({ label, p, extra }) {
  if (!p) return null;
  return (
    <div style={{ border: '1px solid var(--bdr)', borderRadius: 6, padding: '10px 12px' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
        <strong style={{ fontSize: 13 }}>{label}</strong>
        <span style={{ ...DH.mono, color: 'var(--txt2)' }}>{p.total} record{p.total !== 1 ? 's' : ''}</span>
        <span style={{ ...DH.mono, color: 'var(--txt3)' }}>
          {p.range.earliest ? p.range.earliest + ' → ' + p.range.latest : 'no dated records'}
          {p.range.undated ? ' · ' + p.range.undated + ' undated' : ''}
        </span>
        {extra}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(190px,1fr))', gap: 14 }}>
        <DHTally title="By month" obj={p.byMonth} />
        <DHTally title="By company" obj={p.byCompany} />
        {p.byStatus && <DHTally title="By status" obj={p.byStatus} />}
        {p.byVendor && <DHTally title="By vendor" obj={p.byVendor} />}
        {p.byFlow && <DHTally title="Company flow" obj={p.byFlow} />}
      </div>
    </div>
  );
}

function DHIssueTable({ title, rows, cols }) {
  const [open, setOpen] = dhSt(false);
  const n = (rows || []).length;
  return (
    <div style={{ border: '1px solid ' + (n ? '#FECACA' : 'var(--bdr)'), borderRadius: 6, overflow: 'hidden' }}>
      <div onClick={() => n && setOpen(o => !o)} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, padding: '8px 11px', background: n ? '#FEF2F2' : '#FAFAF8', cursor: n ? 'pointer' : 'default' }}>
        <span style={{ fontSize: 12, fontWeight: 600 }}>{title}</span>
        <span style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
          <DHBadge kind={n ? 'err' : 'ok'}>{n === 0 ? 'NONE' : n}</DHBadge>
          {n > 0 && <span style={{ fontSize: 10.5, color: 'var(--txt2)' }}>{open ? 'hide' : 'show'}</span>}
        </span>
      </div>
      {open && n > 0 && (
        <div className="tbl-w" style={{ maxHeight: 260, overflow: 'auto' }}>
          <table className="tbl"><thead><tr>{cols.map(c => <th key={c.k}>{c.t}</th>)}</tr></thead>
            <tbody>{rows.slice(0, 300).map((r, i) => <tr key={i}>{cols.map(c => <td key={c.k} style={DH.mono}>{String(r[c.k] === undefined ? '—' : r[c.k])}</td>)}</tr>)}</tbody>
          </table>
        </div>
      )}
    </div>
  );
}

function DHConflictRow({ c, DI, onResolved, admin, allowUseIncoming, selectable, selected, onToggle }) {
  const [busy, setBusy] = dhSt(false);
  const fact = r => ({
    challan: r.challanNumber || r.billNumber || '—',
    date: r.date || '—',
    material: r.materialId ? Store.name('materials', r.materialId) : (r.items && r.items[0] ? Store.name('materials', r.items[0].materialId) : '—'),
    quantity: r.quantity != null ? r.quantity : (r.items ? r.items.reduce((s, i) => s + (parseFloat(i.quantity) || 0), 0) : '—'),
    amount: r.total != null ? r.total : (r.amount != null ? r.amount : '—'),
    vehicle: r.vehicleFull || '—',
  });
  const cf = fact(c.existing), inf = fact(c.incoming);
  const canUseIncoming = allowUseIncoming !== false;
  async function act(action) {
    setBusy(true);
    try { await DI.resolveConflict(c.collection, c.existing.id, c.incoming, action); window.toast && window.toast('Resolved (' + action + ').', 'ok'); await onResolved(); }
    catch (e) { window.toast && window.toast('Could not resolve: ' + e.message, 'er'); }
    setBusy(false);
  }
  return (
    <div style={{ border: '1px solid #FDE68A', borderRadius: 6, padding: '9px 11px', marginBottom: 8, background: '#fff', display: 'flex', gap: 9 }}>
      {selectable && <input type="checkbox" checked={!!selected} onChange={() => onToggle(c)} style={{ marginTop: 3 }} />}
      <div style={{ flex: 1 }}>
      <div style={{ fontSize: 11.5, fontWeight: 600, marginBottom: 6 }}>{c.collection} — {c.reason}</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, fontSize: 11, marginBottom: 8 }}>
        <div style={{ border: '1px solid var(--bdr)', borderRadius: 5, padding: '6px 8px' }}>
          <div style={{ fontWeight: 700, color: 'var(--txt2)', marginBottom: 3 }}>CURRENT (here)</div>
          {Object.keys(cf).map(k => <div key={k} style={DH.mono}>{k}: {String(cf[k])}</div>)}
        </div>
        <div style={{ border: '1px solid var(--bdr)', borderRadius: 5, padding: '6px 8px' }}>
          <div style={{ fontWeight: 700, color: 'var(--txt2)', marginBottom: 3 }}>INCOMING (snapshot)</div>
          {Object.keys(inf).map(k => <div key={k} style={DH.mono}>{k}: {String(inf[k])}</div>)}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
        <button className="btn btn-wh btn-sm" disabled={busy} onClick={() => act('keep-current')}>Keep current</button>
        {canUseIncoming && (admin
          ? <button className="btn btn-wh btn-sm" disabled={busy} onClick={() => act('use-incoming')}>Use incoming</button>
          : <span title="Administrator required"><button className="btn btn-wh btn-sm" disabled>Use incoming</button></span>)}
        <button className="btn btn-wh btn-sm" disabled={busy} onClick={() => act('keep-both')}>{canUseIncoming ? 'Keep both' : 'Import as new'}</button>
        {!canUseIncoming && <span style={{ fontSize: 10.5, color: 'var(--txt3)' }}>Identity unconfirmed — overwrite disabled</span>}
        {canUseIncoming && !admin && <span style={{ fontSize: 10.5, color: 'var(--txt3)' }}>Sign in as ADMIN/SUPER_ADMIN to use incoming values</span>}
      </div>
      </div>
    </div>
  );
}

function DHBulkConflictBar({ total, selectedCount, allSelected, someSelected, onToggleAll, onClear, onAction, admin }) {
  const cbRef = dhRef(null);
  dhEf(() => { if (cbRef.current) cbRef.current.indeterminate = someSelected && !allSelected; }, [someSelected, allSelected]);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', border: '1px solid var(--bdr)', borderRadius: 6, padding: '7px 10px', marginBottom: 8, background: '#FAFAF8' }}>
      <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, fontWeight: 600, cursor: 'pointer' }}>
        <input ref={cbRef} type="checkbox" checked={allSelected} onChange={onToggleAll} /> Select all
      </label>
      <span style={{ fontSize: 11.5, color: 'var(--txt2)' }}>{total} conflict{total !== 1 ? 's' : ''} requiring review · Selected: {selectedCount}</span>
      <div style={{ display: 'flex', gap: 6, marginLeft: 'auto' }}>
        <button className="btn btn-wh btn-sm" disabled={!selectedCount} onClick={() => onAction('keep-current')}>Keep current</button>
        <button className="btn btn-wh btn-sm" disabled={!selectedCount || !admin} title={admin ? '' : 'Administrator required'} onClick={() => onAction('use-incoming')}>Use incoming{!admin ? ' 🔒' : ''}</button>
        <button className="btn btn-wh btn-sm" disabled={!selectedCount} onClick={() => onAction('keep-both')}>Keep both</button>
        <button className="btn btn-wh btn-sm" disabled={!selectedCount} onClick={onClear}>Clear selection</button>
      </div>
    </div>
  );
}

function DHIntegrityBadge({ integrity }) {
  if (!integrity) return null;
  const map = {
    verified: ['ok', 'INTEGRITY VERIFIED', 'SHA-256 checksum matches the file contents.'],
    legacy: ['warn', 'LEGACY — NO CHECKSUM', 'Created before checksums existed. Other validation still applies; proceeding is allowed but not cryptographically verified.'],
    failed: ['err', 'INTEGRITY FAILED', 'Checksum mismatch — this file will not be imported.'],
    unverifiable: ['warn', 'CHECKSUM UNAVAILABLE', 'This browser has no Web Crypto support to recompute the hash.'],
    invalid: ['err', 'NOT A VALID SNAPSHOT', integrity.detail],
  };
  const [kind, label, note] = map[integrity.status] || ['info', integrity.status, integrity.detail];
  return <div style={{ marginBottom: 8, fontSize: 11.5 }}><DHBadge kind={kind}>{label}</DHBadge> <span style={{ color: 'var(--txt2)', marginLeft: 6 }}>{note}</span></div>;
}

function DHConfirmModal({ title, danger, width, children, footer, onClose }) {
  dhEf(() => {
    function onKey(e) { if (e.key === 'Escape' && onClose) onClose(); }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  return ReactDOM.createPortal(
    <div className="mbg">
      <div className="mod mod-sm" style={width ? { maxWidth: width } : null}>
        <div className="mod-hd"><h2 style={danger ? { color: '#991B1B' } : null}>{title}</h2>{onClose && <button className="mod-x" onClick={onClose}>×</button>}</div>
        <div className="mod-bd">{children}</div>
        {footer && <div className="mod-ft">{footer}</div>}
      </div>
    </div>, document.body);
}

function DataHealthPage() {
  window.useStoreSync && window.useStoreSync();
  const [inv, setInv] = dhSt(null);
  const [chk, setChk] = dhSt(null);
  const [scan, setScan] = dhSt(null);
  const [busy, setBusy] = dhSt('Reading physical storage…');
  const [baks, setBaks] = dhSt([]);
  const [plan, setPlan] = dhSt(null);
  const [snapFile, setSnapFile] = dhSt(null);
  const [restoreRep, setRestoreRep] = dhSt(null);
  const [showExtractor, setShowExtractor] = dhSt(false);
  const [importMode, setImportMode] = dhSt('merge');
  const [confirmReplace, setConfirmReplace] = dhSt(false);
  const [stor, setStor] = dhSt(null);
  const [pst, setPst] = dhSt(Store.persistState || null);
  const [snapMeta, setSnapMeta] = dhSt(null);
  const [registry, setRegistry] = dhSt([]);
  const [impHist, setImpHist] = dhSt([]);
  const [cmpFiles, setCmpFiles] = dhSt([null, null]);
  const [cmpResult, setCmpResult] = dhSt(null);
  const [replaceTyped, setReplaceTyped] = dhSt('');
  const [rollbackConfirm, setRollbackConfirm] = dhSt(null); // { key, note }
  const [rollbackTyped, setRollbackTyped] = dhSt('');
  const [findQuery, setFindQuery] = dhSt('');
  const [findFiles, setFindFiles] = dhSt([]); // [{name, snap}]
  const [findResults, setFindResults] = dhSt(null);
  const [selConflicts, setSelConflicts] = dhSt({});
  const [bulkConfirm, setBulkConfirm] = dhSt(null); // { action, items }
  const [bulkApplying, setBulkApplying] = dhSt(false);
  const [advancedOpen, setAdvancedOpen] = dhSt(false);
  const [freshFile, setFreshFile] = dhSt(null);
  const [freshStep, setFreshStep] = dhSt('pick'); // pick | confirm1 | confirm2 | done
  const [freshTyped, setFreshTyped] = dhSt('');
  const [freshBusy, setFreshBusy] = dhSt('');
  const [freshResult, setFreshResult] = dhSt(null);
  const [freshError, setFreshError] = dhSt(null);
  dhEf(() => (Store.onPersist ? Store.onPersist(setPst) : undefined), []);
  dhEf(() => {
    if (!window.DataIntegrity) return;
    if (window.DataIntegrity.lastSnapshotInfo) setSnapMeta(window.DataIntegrity.lastSnapshotInfo());
    if (window.DataIntegrity.snapshotRegistry) setRegistry(window.DataIntegrity.snapshotRegistry());
    if (window.DataIntegrity.importHistory) setImpHist(window.DataIntegrity.importHistory());
  }, []);
  const fileRef = dhRef(null);
  const DI = window.DataIntegrity;
  const admin = !!(DI && DI.isAdmin && DI.isAdmin());

  async function runAll() {
    if (!DI) { setBusy('Data integrity engine not loaded.'); return; }
    setBusy('Reading physical storage…');
    try {
      const i = await DI.inventory(); setInv(i);
      setBusy('Mapping cross-module references…');
      const stored = await DI.readStored();
      setChk(await DI.integrityCheck(stored.collections));
      setBusy('Scanning storage keys…');
      setScan(await DI.scanStorage());
      setBaks(Store.listBackups ? await Store.listBackups() : []);
      if (Store._maybeWarnQuota) { await Store._maybeWarnQuota(true); setStor(Store.storageStatus); }
      setBusy('');
    } catch (e) { setBusy('Diagnostic failed: ' + (e && e.message)); }
  }
  dhEf(() => { runAll(); }, []);

  const diag = Store.diagnostics || {};
  const quarantined = diag.quarantined || [];
  const failSafe = diag.failSafeEvents || [];
  const loadErrs = Object.keys(diag.loadErrors || {});
  const issues = chk ? (chk.brokenRefs.length + chk.orphans.length + chk.duplicateIds.length + chk.missingFks.length + chk.invalidDates.length + chk.invalidQty.length) : 0;
  const storageStatus = (loadErrs.length || failSafe.length || diag.writeLock) ? 'ERROR' : (issues || (inv && inv.decodeErrors.length)) ? 'WARNING' : 'HEALTHY';

  async function doExport() {
    const snap = await DI.snapshot();
    DI.download(snap);
    DI.recordSnapshotTaken && DI.recordSnapshotTaken(snap._counts);
    const entry = DI.registerSnapshot && DI.registerSnapshot({
      snapshotId: snap._snapshotId, snapshotDate: snap._snapshotDate || snap._createdAt.slice(0, 10), createdAt: snap._createdAt,
      counts: snap._counts, fileName: 'OM_GROUP_ERP_SNAPSHOT_' + (snap._snapshotDate || snap._createdAt.slice(0, 10)) + '.json',
      schemaVersion: snap._schemaVersion, appVersion: snap._appVersion, storageEngine: snap._storageEngine,
      checksumAlgorithm: snap._checksumAlgorithm, checksum: snap._checksum,
      integrityStatus: snap._checksum ? 'checksum-computed' : 'checksum-unavailable',
      verificationStatus: 'created', source: 'export',
    });
    setSnapMeta(DI.lastSnapshotInfo ? DI.lastSnapshotInfo() : null);
    if (DI.snapshotRegistry) setRegistry(DI.snapshotRegistry());
    const shrinkNote = entry && entry.shrinkWarnings && entry.shrinkWarnings.length ? ' ⚠ ' + entry.shrinkWarnings.length + ' collection(s) shrank vs. the previous snapshot — see the timeline below.' : '';
    window.toast && window.toast('Snapshot exported — ' + Object.values(snap._counts).reduce((a, b) => a + b, 0) + ' records across ' + Object.keys(snap._counts).length + ' collections. Nothing was modified.' + shrinkNote, shrinkNote ? 'er' : 'ok');
  }
  async function rerunDryRun() {
    if (!snapFile) return;
    setPlan(importMode === 'replace' ? await DI.replaceAll(snapFile, { dryRun: true }) : await DI.restore(snapFile, { dryRun: true }));
  }
  async function runCompare() {
    const [fa, fb] = cmpFiles;
    if (!fa || !fb) return;
    try {
      const [snapA, snapB] = await Promise.all([fa, fb].map(f => new Promise((res, rej) => { const rd = new FileReader(); rd.onload = () => { try { res(JSON.parse(rd.result)); } catch (e) { rej(e); } }; rd.readAsText(f); })));
      setCmpResult(await DI.compareSnapshotFiles(snapA, snapB));
    } catch (e) { window.toast && window.toast('Could not compare: ' + e.message, 'er'); }
  }
  function pickFile(e) {
    const f = e.target.files && e.target.files[0]; if (!f) return;
    const rd = new FileReader();
    rd.onload = async () => {
      try {
        const snap = JSON.parse(rd.result);
        setSnapFile(snap); setRestoreRep(null);
        setPlan(importMode === 'replace' ? await DI.replaceAll(snap, { dryRun: true }) : await DI.restore(snap, { dryRun: true }));
      } catch (err) { window.toast && window.toast('Could not read snapshot: ' + err.message, 'er'); }
    };
    rd.readAsText(f);
    e.target.value = '';
  }
  async function approveRestore() {
    if (!snapFile) return;
    if (!window.confirm('Add ' + plan.added + ' new record(s), preserving their original IDs?\n\nA pre-import snapshot is taken first. Nothing is deleted or overwritten — records that already exist (by ID or by matching business identity) are left exactly as they are. Conflicts are handled individually above, not by this button.')) return;
    setBusy('Merging and verifying…');
    try {
      const rep = await DI.restore(snapFile, { dryRun: false, acknowledgeLegacy: plan.integrity && plan.integrity.status === 'legacy' });
      setRestoreRep(rep); setPlan(null); setSnapFile(null);
      await runAll();
      if (DI.importHistory) setImpHist(DI.importHistory());
      window.toast && window.toast('Merged ' + rep.added + ' record(s). Read-back verification: ' + (rep.verifiedOk ? 'PASSED' : 'CHECK REPORT') + '.', rep.verifiedOk ? 'ok' : 'er');
    } catch (e) { setBusy(''); window.toast && window.toast('Merge aborted: ' + e.message, 'er'); }
  }
  async function approveReplace() {
    if (!snapFile) return;
    setConfirmReplace(false);
    setBusy('Replacing all data and verifying…');
    try {
      const rep = await DI.replaceAll(snapFile, { dryRun: false, acknowledgeLegacy: plan.integrity && plan.integrity.status === 'legacy' });
      setRestoreRep(rep); setPlan(null); setSnapFile(null);
      await runAll();
      if (DI.importHistory) setImpHist(DI.importHistory());
      window.toast && window.toast('Replaced all data — ' + rep.deleted + ' record(s) removed, ' + rep.incomingTotal + ' written. Read-back verification: ' + (rep.verifiedOk ? 'PASSED' : 'CHECK REPORT') + '.', rep.verifiedOk ? 'ok' : 'er');
    } catch (e) { setBusy(''); window.toast && window.toast('Replace aborted: ' + e.message, 'er'); }
  }

  function askRollback(key, note) { setRollbackConfirm({ key, note }); setRollbackTyped(''); }
  async function doRollback() {
    const key = rollbackConfirm.key;
    setRollbackConfirm(null); setRollbackTyped('');
    setBusy('Rolling back…');
    try {
      const rep = await DI.rollbackToBackup(key);
      setRestoreRep(null);
      await runAll();
      if (DI.importHistory) setImpHist(DI.importHistory());
      window.toast && window.toast('Rolled back. Read-back verification: ' + (rep.verifiedOk ? 'PASSED' : 'CHECK REPORT') + '.', rep.verifiedOk ? 'ok' : 'er');
    } catch (e) { setBusy(''); window.toast && window.toast('Rollback failed: ' + e.message, 'er'); }
  }
  function conflictKey(c) { return c.collection + '|' + c.existing.id; }
  function toggleConflict(c) { const k = conflictKey(c); setSelConflicts(s => { const n = { ...s }; if (n[k]) delete n[k]; else n[k] = true; return n; }); }
  function toggleAllConflicts(list) {
    setSelConflicts(s => {
      const allOn = list.every(c => s[conflictKey(c)]);
      if (allOn) return {};
      const n = {}; list.forEach(c => { n[conflictKey(c)] = true; }); return n;
    });
  }
  async function runBulkAction() {
    const { action, items } = bulkConfirm;
    setBulkApplying(true);
    try {
      const rep = await DI.resolveConflictsBatch(items, action);
      setSelConflicts({});
      await rerunDryRun();
      setBulkConfirm(null); setBulkApplying(false);
      const verb = action === 'keep-current' ? 'current ERP values were kept' : action === 'use-incoming' ? 'incoming snapshot values were applied' : 'both records were retained';
      window.toast && window.toast(rep.failed
        ? (rep.succeeded + ' of ' + rep.total + ' conflicts resolved — ' + rep.failed + ' FAILED and remain in the list for review.')
        : (rep.succeeded + ' conflict' + (rep.succeeded !== 1 ? 's' : '') + ' resolved — ' + verb + '.'), rep.failed ? 'er' : 'ok');
    } catch (e) { setBulkApplying(false); window.toast && window.toast('Bulk action aborted: ' + e.message, 'er'); }
  }
  dhEf(() => {
    if (!bulkConfirm && !rollbackConfirm) return;
    function onKey(e) { if (e.key === 'Escape' && !bulkApplying) { setBulkConfirm(null); setRollbackConfirm(null); } }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [bulkConfirm, rollbackConfirm, bulkApplying]);

  function rollBackReport() {
    const key = restoreRep && (restoreRep.preRestoreBackupKey || restoreRep.preReplaceBackupKey);
    if (!key) return;
    askRollback(key, 'This reverses the ' + (restoreRep.mode === 'replace' ? 'Replace All' : 'Merge') + ' just performed, restoring the exact dataset as it stood immediately before it.');
  }
  function rollBackHistoryEntry(entry) {
    if (!entry.backupKey) return window.toast && window.toast('No pre-import backup was recorded for this entry.', 'er');
    askRollback(entry.backupKey, 'This reverses Import ' + entry.id + ' (' + (entry.mode || 'merge').toUpperCase() + ', ' + entry.at.slice(0, 19).replace('T', ' ') + ') only — not the entire ERP history, and not changes made after it.');
  }

  async function runFind() {
    if (!findQuery.trim()) return;
    const points = [{ id: 'current', label: 'Current ERP', isCurrent: true, collections: Store.data }]
      .concat(findFiles.map(f => ({ id: f.name, label: f.name + (f.snap._snapshotDate ? ' (' + f.snap._snapshotDate + ')' : ''), collections: f.snap.collections })));
    setFindResults(DI.findAcrossSnapshots(findQuery, points));
  }
  function addFindFile(e) {
    const f = e.target.files && e.target.files[0]; if (!f) return;
    const rd = new FileReader();
    rd.onload = () => { try { const snap = JSON.parse(rd.result); setFindFiles(list => list.concat([{ name: f.name, snap }])); } catch (err) { window.toast && window.toast('Could not read file: ' + err.message, 'er'); } };
    rd.readAsText(f);
    e.target.value = '';
  }

  function pickFreshFile(e) {
    const f = e.target.files && e.target.files[0]; if (!f) return;
    const rd = new FileReader();
    rd.onload = async () => {
      try {
        const snap = JSON.parse(rd.result);
        if (!snap || snap._type !== 'omg-erp-snapshot' || !snap.collections) throw new Error('Not an OM ERP snapshot file.');
        const integrity = await DI.verifySnapshotIntegrity(snap);
        setFreshFile({ snap, integrity }); setFreshStep('preview'); setFreshResult(null); setFreshError(null); setFreshTyped('');
      } catch (err) { window.toast && window.toast('Could not read snapshot: ' + err.message, 'er'); }
    };
    rd.readAsText(f);
    e.target.value = '';
  }
  async function runClearAndImport() {
    setFreshBusy('Creating emergency backup and clearing local data…');
    setFreshError(null);
    let clearRep;
    try {
      clearRep = await DI.clearAllBusinessData({ confirmPhrase: freshTyped.trim() });
    } catch (e) {
      setFreshBusy(''); setFreshError({ stage: 'clear', message: e.message });
      window.toast && window.toast('Clear aborted — nothing was deleted: ' + e.message, 'er');
      return;
    }
    setFreshBusy('Importing snapshot…');
    try {
      const importRep = await DI.restore(freshFile.snap, { dryRun: false, acknowledgeLegacy: freshFile.integrity.status === 'legacy' });
      setFreshResult({ clearRep, importRep });
      setFreshStep('done'); setFreshBusy('');
      await runAll();
      DI.registerSnapshot && DI.registerSnapshot({ snapshotId: freshFile.snap._snapshotId, snapshotDate: freshFile.snap._snapshotDate, createdAt: freshFile.snap._createdAt, counts: freshFile.snap._counts, fileName: 'imported-fresh-snapshot.json', schemaVersion: freshFile.snap._schemaVersion, appVersion: freshFile.snap._appVersion, checksumAlgorithm: freshFile.snap._checksumAlgorithm, checksum: freshFile.snap._checksum, verificationStatus: importRep.verifiedOk ? 'verified' : 'check', source: 'fresh-import' });
      window.toast && window.toast('Local data cleared and fresh snapshot imported — verification ' + (importRep.verifiedOk ? 'PASSED' : 'NEEDS REVIEW') + '.', importRep.verifiedOk ? 'ok' : 'er');
    } catch (e) {
      setFreshBusy(''); setFreshError({ stage: 'import', message: e.message, backupKey: clearRep.backupKey });
      window.toast && window.toast('Local data was cleared, but import failed: ' + e.message + ' — your pre-clear backup is safe.', 'er');
    }
  }
  async function retryFreshImport() {
    setFreshBusy('Retrying import…'); setFreshError(null);
    try {
      const importRep = await DI.restore(freshFile.snap, { dryRun: false, acknowledgeLegacy: freshFile.integrity.status === 'legacy' });
      setFreshResult(r => ({ clearRep: r ? r.clearRep : null, importRep }));
      setFreshStep('done'); setFreshBusy('');
      await runAll();
      window.toast && window.toast('Import succeeded on retry — verification ' + (importRep.verifiedOk ? 'PASSED' : 'NEEDS REVIEW') + '.', importRep.verifiedOk ? 'ok' : 'er');
    } catch (e) { setFreshBusy(''); setFreshError({ stage: 'import', message: e.message }); }
  }
  async function downloadEmergencyBackup(key) {
    const r = await Store.readBackup(key);
    if (!r || r.error || !r.snapshot) return window.toast && window.toast('Backup unreadable.', 'er');
    DI.download(r.snapshot, 'OM_GROUP_ERP_PRE_CLEAR_BACKUP_' + (r.at || '').slice(0, 19).replace(/[:T]/g, '-') + '.json');
  }
  function resetFreshWizard() { setFreshFile(null); setFreshStep('pick'); setFreshTyped(''); setFreshResult(null); setFreshError(null); }

  const invRows = (inv && inv.rows || []).filter(r => r.stored !== undefined || r.memory !== undefined);
  const txn = new Set(DI ? DI.TXN_COLLECTIONS : []);

  return (
    <div>
      <div className="ph">
        <div><h1>Data Health</h1><p>Read-only integrity diagnostics, reference graph and snapshot recovery</p></div>
        <div className="ph-act" style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-wh btn-sm" onClick={runAll}>Re-run diagnostic</button>
          <button className="btn btn-sm" onClick={doExport}>Create Snapshot</button>
        </div>
      </div>

      <div style={DH.wrap}>
        {/* ── Status ── */}
        <div className="card" style={{ ...DH.sec, borderColor: storageStatus === 'ERROR' ? '#FECACA' : storageStatus === 'WARNING' ? '#FDE68A' : '#BBF7D0' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10, flexWrap: 'wrap' }}>
            <DHBadge kind={storageStatus === 'HEALTHY' ? 'ok' : storageStatus === 'WARNING' ? 'warn' : 'err'}>STORAGE {storageStatus}</DHBadge>
            <span style={{ ...DH.mono, color: 'var(--txt2)' }}>{diag.storageEngine} · {diag.idbName} v{diag.idbVersion} · key {diag.storageKey}</span>
            <span style={{ ...DH.mono, color: 'var(--txt3)' }}>origin {inv ? inv.origin : location.origin}</span>
            {busy && <span style={{ fontSize: 11.5, color: 'var(--or)' }}>{busy}</span>}
          </div>
          <div style={DH.kv}>
            <DHStat n={inv ? fmtN(inv.purchases.total) : '…'} label="Purchases stored" />
            <DHStat n={inv ? fmtN(inv.salesOrders.total) : '…'} label="Sales stored" />
            <DHStat n={inv ? fmtN(inv.internalTransfers.total) : '…'} label="Internal transfers" />
            <DHStat n={inv ? fmtN(inv.transportEntries.total) : '…'} label="Transport entries" />
            <DHStat n={inv ? fmtN(inv.settlementRecords.total) : '…'} label="Transport settlements" />
            <DHStat n={inv ? fmtN(inv.internalSettlements.total) : '…'} label="Internal settlements" />
            <DHStat n={chk ? chk.brokenRefs.length : '…'} label="Broken references" kind={chk && chk.brokenRefs.length ? 'err' : 'ok'} />
            <DHStat n={chk ? chk.orphans.length : '…'} label="Orphaned records" kind={chk && chk.orphans.length ? 'err' : 'ok'} />
            <DHStat n={chk ? chk.duplicateIds.length : '…'} label="Duplicate IDs" kind={chk && chk.duplicateIds.length ? 'err' : 'ok'} />
            <DHStat n={quarantined.length} label="Quarantined collections" kind={quarantined.length ? 'err' : 'ok'} />
            <DHStat n={baks.length} label="Pre-write backups" kind="info" />
            <DHStat n={inv ? inv.auditFirstLast.count : '…'} label="Audit entries" />
          </div>
          {(loadErrs.length > 0 || failSafe.length > 0 || diag.writeLock) && (
            <div style={{ marginTop: 11, border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '9px 11px' }}>
              <strong style={{ fontSize: 12, color: '#991B1B' }}>Storage errors detected — records are NOT deleted</strong>
              {diag.writeLock && (
                <div style={{ ...DH.mono, color: '#991B1B', marginTop: 4 }}>
                  WRITE LOCK ACTIVE — {diag.writeLock}. All saving is disabled so the stored records cannot be overwritten. Close other OM ERP tabs and reload this page.
                </div>
              )}
              {loadErrs.map(k => {
                const lkg = Store.data && Store.data._lastKnownGoodCounts && Store.data._lastKnownGoodCounts[k];
                return (
                <div key={k} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, ...DH.mono, color: '#991B1B', marginTop: 4, padding: '6px 8px', background: '#FEF2F2', borderRadius: 4 }}>
                  <span>{k}: {diag.loadErrors[k].reason}{typeof lkg === 'number' ? ' — last verified ' + lkg + ' record(s), not 0' : ''}. Writes stay blocked until restored from a verified snapshot (Import Snapshot \u2192 Merge below).</span>
                  <button className="btn btn-sm" style={{ background: '#991B1B', color: '#fff', border: 'none', whiteSpace: 'nowrap', flexShrink: 0 }} onClick={async () => {
                    if (confirm('This archives the corrupted ' + k + ' payload for forensic safekeeping. It does NOT delete, reset or unblock ' + k + ' — recovery still requires a verified snapshot import. Continue?')) {
                      try { await window.recoverQuarantinedCollection(k); } catch (e) { window.toast && window.toast('Failed: ' + e.message, 'er'); }
                    }
                  }}>Archive corrupted payload</button>
                </div>
              );})}
              {failSafe.map((f, i) => (
                <div key={i} style={{ ...DH.mono, color: '#991B1B', marginTop: 4 }}>
                  FAIL SAFE: a write that would have emptied {f.collection} ({f.from} → 0) was refused at {f.at}.
                </div>
              ))}
            </div>
          )}
        </div>

        {/* ── Data protection / monthly snapshot freshness ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Data protection — monthly snapshot</h3>
          {(() => {
            const ageDays = snapMeta ? Math.floor((Date.now() - new Date(snapMeta.at).getTime()) / 86400000) : null;
            const stale = ageDays == null || ageDays > 31;
            return (
              <div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 8 }}>
                  <DHBadge kind={!snapMeta ? 'warn' : stale ? 'warn' : 'ok'}>{!snapMeta ? 'NO SNAPSHOT YET' : stale ? 'BACKUP OVERDUE' : 'CURRENT'}</DHBadge>
                  <span style={{ ...DH.mono, color: 'var(--txt2)' }}>
                    {snapMeta ? 'last snapshot ' + snapMeta.at.replace('T', ' ').slice(0, 19) + ' · ' + ageDays + ' day' + (ageDays === 1 ? '' : 's') + ' ago · ' + Object.values(snapMeta.counts || {}).reduce((a, b) => a + b, 0).toLocaleString('en-IN') + ' records' : 'no external snapshot has been created from this browser yet'}
                  </span>
                </div>
                {stale && <div style={{ fontSize: 11.5, color: '#92400E', marginBottom: 8, lineHeight: 1.55 }}>Browser storage is not a backup by itself — a downloaded snapshot is the independent copy that survives a browser reset, a device failure or a redeployment. Create one at least monthly and send it to whoever holds your external backups.</div>}
                <button className="btn btn-sm" onClick={doExport}>Create snapshot now</button>
              </div>
            );
          })()}
        </div>

        {/* ── Storage capacity & write health ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Storage capacity and write health</h3>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 10 }}>
            <DHBadge kind={!stor ? 'info' : stor.level === 'GREEN' ? 'ok' : stor.level === 'AMBER' ? 'warn' : 'err'}>{stor ? stor.level : 'MEASURING'}</DHBadge>
            <span style={{ ...DH.mono, color: 'var(--txt2)' }}>
              {stor && stor.usage != null ? mb(stor.usage) + ' used of ' + mb(stor.quota) + ' (' + (stor.pct >= 0.001 ? Math.round(stor.pct * 100) : '<0.1') + '%)' : 'browser did not report a quota'}
            </span>
            <span style={{ ...DH.mono, color: 'var(--txt3)' }}>engine {stor ? stor.engine : '…'}</span>
            {pst && <DHBadge kind={pst.status === 'saved' ? 'ok' : pst.status === 'idle' ? 'info' : pst.status === 'failed' ? 'err' : 'info'}>WRITES {String(pst.status).toUpperCase()}</DHBadge>}
          </div>
          {stor && stor.usage != null && (
            <div style={{ height: 8, borderRadius: 4, background: '#F1F1EE', overflow: 'hidden', marginBottom: 10 }}>
              <div style={{ width: Math.min(100, Math.round(stor.pct * 100)) + '%', height: '100%', background: stor.level === 'GREEN' ? '#16A34A' : stor.level === 'AMBER' ? '#D97706' : '#DC2626' }}></div>
            </div>
          )}
          <div style={DH.kv}>
            <div style={DH.stat}><div style={{ ...DH.mono, fontSize: 12 }}>{pst && pst.lastSuccessAt ? pst.lastSuccessAt.replace('T', ' ').slice(0, 19) : 'no write yet this session'}</div><div style={DH.statL}>Last successful write</div></div>
            <div style={DH.stat}><div style={{ ...DH.mono, fontSize: 12 }}>{stor && stor.measuredAt ? stor.measuredAt.replace('T', ' ').slice(0, 19) : '—'}</div><div style={DH.statL}>Capacity measured at</div></div>
            <div style={DH.stat}><div style={{ ...DH.mono, fontSize: 12 }}>{(pst && pst.pendingKeys && pst.pendingKeys.length) ? pst.pendingKeys.join(', ') : 'none'}</div><div style={DH.statL}>Unsaved collections</div></div>
          </div>
          {pst && pst.status === 'failed' && (
            <div style={{ marginTop: 10, border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '9px 11px', fontSize: 11.5, color: '#991B1B', lineHeight: 1.6 }}>
              <strong>Writes are failing — {pst.failure && pst.failure.reason}.</strong> Existing records are untouched and nothing has been trimmed to make room.
              New entry is blocked until this clears. Use the red bar at the bottom of the screen to download the unsaved work.
            </div>
          )}
          {Store._legacyRecovery && (
            <div style={{ marginTop: 10, border: '1px solid ' + (Store._legacyRecovery.verified ? '#BBF7D0' : '#FDE68A'), background: Store._legacyRecovery.verified ? '#F0FDF4' : '#FFFBEB', borderRadius: 6, padding: '9px 11px', fontSize: 11.5, lineHeight: 1.6 }}>
              <strong>Legacy storage recovery:</strong> {Store._legacyRecovery.recovered} record(s) recovered from localStorage key
              <code style={DH.mono}> {Store._legacyRecovery.key}</code> into IndexedDB — counts {Store._legacyRecovery.verified ? 'verified' : 'NOT yet verified, will retry on next load'}.
              The source blob was left untouched.
            </div>
          )}
          <div style={{ marginTop: 10, fontSize: 11, color: 'var(--txt2)', lineHeight: 1.6 }}>
            These figures come from the browser's own storage estimate, not a hardcoded limit. The obsolete “~5&nbsp;MB” warning came from the
            single-blob localStorage writer and has been removed — with IndexedDB active the practical ceiling is the quota shown above.
          </div>
        </div>

        {/* ── Inventory: stored vs loaded vs visible ── */}
        <div className="card">
          <div className="card-hd" style={{ background: '#F9FAFB' }}>
            <div><strong style={{ fontSize: 13 }}>Data inventory</strong>
              <span style={{ fontSize: 11, color: 'var(--txt2)', marginLeft: 8 }}>
                physical storage vs loaded in memory vs visible under current scope ({inv ? inv.scope : '…'})
              </span></div>
          </div>
          <div className="tbl-w" style={{ maxHeight: 460, overflow: 'auto' }}>
            <table className="tbl">
              <thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>Stored</th><th style={{ textAlign: 'right' }}>Loaded</th><th style={{ textAlign: 'right' }}>Visible</th><th style={{ textAlign: 'right' }}>Hidden by scope</th><th>State</th></tr></thead>
              <tbody>
                {invRows.map(r => {
                  const hid = (r.stored != null && r.visible != null) ? r.stored - r.visible : null;
                  const bad = r.unknown || (r.stored != null && r.memory != null && r.stored !== r.memory);
                  return (
                    <tr key={r.collection} style={txn.has(r.collection) ? { background: '#FFFDF7' } : null}>
                      <td style={{ fontWeight: txn.has(r.collection) ? 600 : 400 }}>{r.collection}</td>
                      <td style={{ textAlign: 'right', ...DH.mono }}>{r.unknown ? 'UNKNOWN' : (r.stored === undefined ? '—' : r.stored)}</td>
                      <td style={{ textAlign: 'right', ...DH.mono }}>{r.memory === undefined ? '—' : r.memory}</td>
                      <td style={{ textAlign: 'right', ...DH.mono }}>{r.visible == null ? '—' : r.visible}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, color: hid ? 'var(--or)' : 'var(--txt3)' }}>{hid == null ? '—' : hid}</td>
                      <td>{r.unknown ? <DHBadge kind="err">READ ERROR</DHBadge> : bad ? <DHBadge kind="warn">MISMATCH</DHBadge> : <DHBadge kind="ok">OK</DHBadge>}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <div style={{ padding: '8px 12px', fontSize: 11, color: 'var(--txt2)', borderTop: '1px solid var(--bdr)' }}>
            <strong>Stored</strong> is read straight off IndexedDB, bypassing every filter, scope and migration — it is the answer to “were the records deleted, or just hidden?”.
            A read failure shows as <strong>UNKNOWN</strong>, never as 0.
          </div>
        </div>

        {/* ── Transaction profiles ── */}
        {inv && (
          <div className="card" style={DH.sec}>
            <h3 style={DH.h}>Transaction inventory — counts by month, company, vendor, status</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              <DHProfile label="Purchases" p={inv.purchases} />
              <DHProfile label="Sales orders" p={inv.salesOrders} />
              <DHProfile label="Internal transfers / internal purchases" p={inv.internalTransfers} />
              <DHProfile label="Transport entries" p={inv.transportEntries} />
            </div>
          </div>
        )}

        {/* ── Referential integrity ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Referential integrity — cross-module reference graph</h3>
          {!chk ? <div style={{ fontSize: 12, color: 'var(--txt2)' }}>{busy || 'Checking…'}</div> : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
              <DHIssueTable title="Broken references (record points at a master/transaction that does not exist)" rows={chk.brokenRefs}
                cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'Record ID' }, { k: 'date', t: 'Date' }, { k: 'challan', t: 'Challan' }, { k: 'label', t: 'Missing' }, { k: 'value', t: 'Referenced ID' }]} />
              <DHIssueTable title="Orphaned records (internal transfer / sale whose source purchase is missing)" rows={chk.orphans}
                cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'Record ID' }, { k: 'date', t: 'Date' }, { k: 'missing', t: 'Missing source' }, { k: 'status', t: 'Marked' }]} />
              <DHIssueTable title="Duplicate IDs" rows={chk.duplicateIds} cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'ID' }, { k: 'occurrences', t: 'Copies' }]} />
              <DHIssueTable title="Missing company attribution" rows={chk.missingFks} cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'Record ID' }, { k: 'field', t: 'Missing field' }]} />
              <DHIssueTable title="Invalid or missing dates" rows={chk.invalidDates} cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'Record ID' }, { k: 'date', t: 'Date value' }]} />
              <DHIssueTable title="Invalid quantities" rows={chk.invalidQty} cols={[{ k: 'collection', t: 'Collection' }, { k: 'id', t: 'Record ID' }, { k: 'quantity', t: 'Quantity' }]} />
              <div style={{ fontSize: 11, color: 'var(--txt2)' }}>
                Orphans are <strong>marked, never deleted</strong>. Recovering the missing source record re-resolves the relationship automatically, because original IDs are preserved on restore.
              </div>
            </div>
          )}
        </div>

        {/* ── Recovery ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Recovery</h3>
          <div style={{ fontSize: 12, color: 'var(--txt2)', lineHeight: 1.65, marginBottom: 11 }}>
            Browser storage is <strong>origin-scoped</strong>: data entered on another deployment, domain or browser profile is physically unreachable from this one, by any code running here.
            Recovery across origins therefore works by snapshot — extract from the origin that still shows the records, import here.
            <strong> Merge</strong> only adds what's missing here and never deletes anything, even if the incoming snapshot is missing records this ERP already has.
            <strong> Restore/Replace All</strong> is the only mode where this ERP is made to become exactly the snapshot — use it deliberately, not as the default.
            Running the same snapshot through Merge twice changes nothing the second time.
          </div>
          <div style={{ fontSize: 11.5, color: '#92400E', background: '#FFFBEB', border: '1px solid #FDE68A', borderRadius: 6, padding: '8px 10px', marginBottom: 11 }}>
            For the simple monthly workflow — “treat this month's snapshot as the complete authoritative state, don't reconcile it” — use <strong>Advanced data administration → Clear All Data → Import Snapshot</strong> below instead of Merge/Replace All. It skips conflict resolution entirely by clearing first.
          </div>
          {importMode === 'replace' && (
            <div style={{ border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '8px 10px', marginBottom: 11, fontSize: 11.5, color: '#991B1B' }}>
              <strong>Replace All is selected.</strong> Importing a snapshot in this mode deletes every current record (except audit logs) and makes the snapshot the sole source of truth. A full backup is taken automatically before anything is deleted, and it is reversible from Backups below.
            </div>
          )}
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 11 }}>
            <button className="btn btn-wh btn-sm" onClick={doExport}>1 · Create Snapshot</button>
            <button className="btn btn-wh btn-sm" onClick={() => setShowExtractor(s => !s)}>2 · Extract from another origin</button>
            <div style={{ display: 'flex', alignItems: 'center', gap: 0, border: '1px solid var(--bd)', borderRadius: 6, overflow: 'hidden' }}>
              <button type="button" onClick={() => setImportMode('merge')} style={{ padding: '6px 10px', fontSize: 11.5, fontWeight: 600, border: 'none', cursor: 'pointer', background: importMode === 'merge' ? 'var(--txt)' : '#fff', color: importMode === 'merge' ? '#fff' : 'var(--txt2)' }}>Merge</button>
              <button type="button" disabled={!admin} title={admin ? '' : 'Administrator required'} onClick={() => admin && setImportMode('replace')} style={{ padding: '6px 10px', fontSize: 11.5, fontWeight: 600, border: 'none', cursor: admin ? 'pointer' : 'not-allowed', opacity: admin ? 1 : .5, background: importMode === 'replace' ? '#991B1B' : '#fff', color: importMode === 'replace' ? '#fff' : 'var(--txt2)' }}>Restore / Replace All{!admin ? ' 🔒' : ''}</button>
            </div>
            <button className="btn btn-sm" onClick={() => fileRef.current && fileRef.current.click()}>3 · Import snapshot…</button>
            <input ref={fileRef} type="file" accept=".json,application/json" onChange={pickFile} style={{ display: 'none' }} />
          </div>

          {showExtractor && DI && (
            <div style={{ border: '1px solid var(--bdr)', borderRadius: 6, padding: '10px 12px', marginBottom: 11, background: '#FCFCFB' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginBottom: 7 }}>
                <strong style={{ fontSize: 12 }}>Read-only extractor — run in the DevTools console of the deployment that still shows your data</strong>
                <button className="btn btn-wh btn-sm" onClick={() => { navigator.clipboard.writeText(DI.EXTRACTOR).then(() => window.toast && window.toast('Extractor copied. Open that deployment → DevTools → Console → paste → Enter.', 'ok')); }}>Copy script</button>
              </div>
              <div style={{ fontSize: 11.5, color: 'var(--txt2)', lineHeight: 1.6, marginBottom: 7 }}>
                It only reads and downloads a snapshot file. It writes nothing, clears nothing, and migrates nothing. Bring the downloaded file back here and use <strong>Import snapshot</strong>.
              </div>
              <pre style={{ ...DH.mono, margin: 0, maxHeight: 180, overflow: 'auto', background: '#1F2937', color: '#E5E7EB', padding: 10, borderRadius: 5, fontSize: 10.5, whiteSpace: 'pre-wrap' }}>{DI.EXTRACTOR}</pre>
            </div>
          )}

          {plan && plan.mode === 'replace' && (
            <div style={{ border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '10px 12px', marginBottom: 11 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
                <strong style={{ fontSize: 12.5, color: '#991B1B' }}>DRY RUN — REPLACE ALL — nothing has been written yet</strong>
                <span style={{ ...DH.mono, color: '#991B1B' }}>from {plan.sourceOrigin || 'unknown origin'} · {plan.sourceCreatedAt || ''}</span>
              </div>
              <DHIntegrityBadge integrity={plan.integrity} />
              <div style={{ ...DH.kv, marginBottom: 9 }}>
                <DHStat n={plan.deleted} label="Existing records to delete" kind="err" />
                <DHStat n={plan.incomingTotal} label="Records to write from snapshot" kind="ok" />
              </div>
              <div className="tbl-w" style={{ maxHeight: 220, overflow: 'auto', background: '#fff', borderRadius: 5 }}>
                <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>Here now (deleted)</th><th style={{ textAlign: 'right' }}>In snapshot (written)</th></tr></thead>
                  <tbody>{plan.plan.filter(r => r.existing > 0 || r.incoming > 0).map(r => (
                    <tr key={r.collection}><td>{r.collection}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, color: r.existing ? '#991B1B' : 'var(--txt3)' }}>{r.existing}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, fontWeight: 700, color: r.incoming ? '#166534' : 'var(--txt3)' }}>{r.incoming}</td></tr>
                  ))}</tbody>
                </table>
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                <button className="btn btn-sm" style={{ background: '#991B1B', opacity: plan.integrity && plan.integrity.status === 'failed' ? .5 : 1 }} disabled={plan.integrity && plan.integrity.status === 'failed'} onClick={() => setConfirmReplace(true)}>Replace all data…</button>
                <button className="btn btn-wh btn-sm" onClick={() => { setPlan(null); setSnapFile(null); }}>Cancel</button>
                <span style={{ fontSize: 11.5, color: '#991B1B' }}>Audit logs are preserved; every other collection is fully replaced.</span>
              </div>
            </div>
          )}

          {confirmReplace && plan && (
            <DHConfirmModal title="This will delete all current data" danger onClose={() => { setConfirmReplace(false); setReplaceTyped(''); }} footer={<>
              <button className="btn btn-wh btn-sm" onClick={() => { setConfirmReplace(false); setReplaceTyped(''); }}>Cancel</button>
              <button className="btn btn-sm" disabled={replaceTyped.trim().toUpperCase() !== 'REPLACE'} style={{ background: '#991B1B', opacity: replaceTyped.trim().toUpperCase() === 'REPLACE' ? 1 : .5 }} onClick={() => { setReplaceTyped(''); approveReplace(); }}>Yes, delete and replace</button>
            </>}>
              <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6, margin: '0 0 12px' }}>
                <strong>{plan.deleted}</strong> existing record(s) across {plan.plan.filter(r => r.existing > 0).length} collection(s) will be permanently removed and replaced with <strong>{plan.incomingTotal}</strong> record(s) from the snapshot. Audit logs are kept. A full backup is taken first and can be restored from Backups below if needed.
              </p>
              <label style={{ fontSize: 11.5, color: 'var(--txt2)', display: 'block', marginBottom: 6 }}>Type <strong>REPLACE</strong> to confirm</label>
              <input value={replaceTyped} onChange={e => setReplaceTyped(e.target.value)} style={{ width: '100%', padding: '7px 9px', border: '1px solid var(--bdr)', borderRadius: 5, fontSize: 13 }} placeholder="REPLACE" />
            </DHConfirmModal>
          )}

          {plan && plan.mode !== 'replace' && (
            <div style={{ border: '1px solid #FDE68A', background: '#FFFBEB', borderRadius: 6, padding: '10px 12px', marginBottom: 11 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
                <strong style={{ fontSize: 12.5, color: '#92400E' }}>DRY RUN — nothing has been written yet</strong>
                <span style={{ ...DH.mono, color: '#92400E' }}>from {plan.sourceOrigin || 'unknown origin'} · {plan.sourceCreatedAt || ''}</span>
              </div>
              <DHIntegrityBadge integrity={plan.integrity} />
              <div style={{ ...DH.kv, marginBottom: 9 }}>
                <DHStat n={plan.added} label="Records to add" kind="ok" />
                <DHStat n={plan.skippedIdentical} label="Already present (skip)" kind="info" />
                <DHStat n={plan.conflicts} label="Same identity, different content" kind={plan.conflicts ? 'warn' : 'ok'} />
                <DHStat n={plan.unresolved || 0} label="Unresolved — needs review" kind={plan.unresolved ? 'warn' : 'ok'} />
                {plan.resolvedPreviously > 0 && <DHStat n={plan.resolvedPreviously} label="Already resolved this import" kind="ok" />}
              </div>
              <div className="tbl-w" style={{ maxHeight: 220, overflow: 'auto', background: '#fff', borderRadius: 5 }}>
                <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>Here now</th><th style={{ textAlign: 'right' }}>In snapshot</th><th style={{ textAlign: 'right' }}>Will add</th><th style={{ textAlign: 'right' }}>Identical</th><th style={{ textAlign: 'right' }}>Conflicts</th></tr></thead>
                  <tbody>{plan.plan.filter(r => r.incoming > 0).map(r => (
                    <tr key={r.collection}><td>{r.collection}</td><td style={{ textAlign: 'right', ...DH.mono }}>{r.existing}</td><td style={{ textAlign: 'right', ...DH.mono }}>{r.incoming}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, fontWeight: 700, color: r.willAdd ? '#166534' : 'var(--txt3)' }}>{r.willAdd}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, color: 'var(--txt3)' }}>{r.identical}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, color: r.conflicts ? '#92400E' : 'var(--txt3)' }}>{r.conflicts}</td></tr>
                  ))}</tbody>
                </table>
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                <button className="btn btn-sm" onClick={approveRestore} disabled={!plan.added || (plan.integrity && plan.integrity.status === 'failed')}>Approve &amp; merge {plan.added} new record{plan.added !== 1 ? 's' : ''}</button>
                <button className="btn btn-wh btn-sm" onClick={() => { setPlan(null); setSnapFile(null); }}>Cancel</button>
                {plan.conflicts > 0 && <span style={{ fontSize: 11.5, color: '#92400E' }}>Resolve the {plan.conflicts} conflict{plan.conflicts !== 1 ? 's' : ''} below individually — they are never auto-replaced.</span>}
              </div>
              {plan.conflictRecords && plan.conflictRecords.length > 0 && (
                <div style={{ marginTop: 12 }}>
                  <div style={{ fontSize: 11.5, fontWeight: 700, color: '#92400E', marginBottom: 6 }}>
                    Conflicts requiring review ({plan.conflictRecords.length}{plan.conflictRecords.length >= 300 ? '+ — showing first 300' : ''})
                  </div>
                  {(() => {
                    const list = plan.conflictRecords;
                    const selCount = list.filter(c => selConflicts[conflictKey(c)]).length;
                    return <DHBulkConflictBar total={list.length} selectedCount={selCount} allSelected={selCount === list.length && list.length > 0} someSelected={selCount > 0}
                      onToggleAll={() => toggleAllConflicts(list)} onClear={() => setSelConflicts({})} admin={admin}
                      onAction={action => setBulkConfirm({ action, items: list.filter(c => selConflicts[conflictKey(c)]) })} />;
                  })()}
                  {plan.conflictRecords.map((c, i) => <DHConflictRow key={i} c={c} DI={DI} admin={admin} onResolved={rerunDryRun} selectable selected={!!selConflicts[conflictKey(c)]} onToggle={toggleConflict} />)}
                </div>
              )}
              {plan.unresolvedRecords && plan.unresolvedRecords.length > 0 && (
                <div style={{ marginTop: 12 }}>
                  <div style={{ fontSize: 11.5, fontWeight: 700, color: '#92400E', marginBottom: 6 }}>
                    Unresolved — identity could not be confirmed ({plan.unresolvedRecords.length}{plan.unresolvedRecords.length >= 300 ? '+ — showing first 300' : ''})
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--txt2)', marginBottom: 6 }}>Same reference number, but not enough matching fields to safely call these the same transaction or a genuine duplicate. Choose Keep current to skip, or Import as new — never auto-overwritten.</div>
                  {plan.unresolvedRecords.map((c, i) => <DHConflictRow key={i} c={c} DI={DI} admin={admin} allowUseIncoming={false} onResolved={rerunDryRun} />)}
                </div>
              )}
            </div>
          )}

          {restoreRep && restoreRep.mode === 'replace' && (
            <div style={{ border: '1px solid ' + (restoreRep.verifiedOk ? '#BBF7D0' : '#FECACA'), background: restoreRep.verifiedOk ? '#F0FDF4' : '#FEF2F2', borderRadius: 6, padding: '10px 12px', marginBottom: 11 }}>
              <strong style={{ fontSize: 12.5, color: restoreRep.verifiedOk ? '#166534' : '#991B1B' }}>
                Replace complete — {restoreRep.deleted} deleted, {restoreRep.incomingTotal} written. Read-back verification: {restoreRep.verifiedOk ? 'PASSED' : 'REVIEW'}
              </strong>
              <div className="tbl-w" style={{ maxHeight: 200, overflow: 'auto', background: '#fff', borderRadius: 5, marginTop: 8 }}>
                <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>Before</th><th style={{ textAlign: 'right' }}>After</th><th style={{ textAlign: 'right' }}>Stored now</th></tr></thead>
                  <tbody>{Object.keys(restoreRep.verified || {}).filter(k => restoreRep.after[k] !== restoreRep.before[k]).map(k => (
                    <tr key={k}><td>{k}</td><td style={{ textAlign: 'right', ...DH.mono }}>{restoreRep.before[k]}</td><td style={{ textAlign: 'right', ...DH.mono }}>{restoreRep.after[k]}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, fontWeight: 700, color: restoreRep.verified[k].storedNow === restoreRep.verified[k].expected ? '#166534' : '#991B1B' }}>{String(restoreRep.verified[k].storedNow)}</td></tr>
                  ))}</tbody>
                </table>
              </div>
              {restoreRep.preReplaceBackupKey && admin && <div style={{ marginTop: 8 }}><button className="btn btn-wh btn-sm" onClick={rollBackReport}>Roll back this import</button></div>}
            </div>
          )}

          {restoreRep && restoreRep.mode !== 'replace' && (
            <div style={{ border: '1px solid ' + (restoreRep.verifiedOk ? '#BBF7D0' : '#FECACA'), background: restoreRep.verifiedOk ? '#F0FDF4' : '#FEF2F2', borderRadius: 6, padding: '10px 12px', marginBottom: 11 }}>
              <strong style={{ fontSize: 12.5, color: restoreRep.verifiedOk ? '#166534' : '#991B1B' }}>
                Merge complete — {restoreRep.added} added, {restoreRep.skippedIdentical} already present, {restoreRep.conflicts} conflicts untouched. Read-back verification: {restoreRep.verifiedOk ? 'PASSED' : 'REVIEW'}
              </strong>
              <div className="tbl-w" style={{ maxHeight: 200, overflow: 'auto', background: '#fff', borderRadius: 5, marginTop: 8 }}>
                <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>Before</th><th style={{ textAlign: 'right' }}>After</th><th style={{ textAlign: 'right' }}>Stored now</th></tr></thead>
                  <tbody>{Object.keys(restoreRep.verified || {}).filter(k => restoreRep.after[k] !== restoreRep.before[k]).map(k => (
                    <tr key={k}><td>{k}</td><td style={{ textAlign: 'right', ...DH.mono }}>{restoreRep.before[k]}</td><td style={{ textAlign: 'right', ...DH.mono }}>{restoreRep.after[k]}</td>
                      <td style={{ textAlign: 'right', ...DH.mono, fontWeight: 700, color: restoreRep.verified[k].storedNow === restoreRep.verified[k].expected ? '#166534' : '#991B1B' }}>{String(restoreRep.verified[k].storedNow)}</td></tr>
                  ))}</tbody>
                </table>
              </div>
              {restoreRep.preRestoreBackupKey && admin && <div style={{ marginTop: 8 }}><button className="btn btn-wh btn-sm" onClick={rollBackReport}>Roll back this import</button></div>}
            </div>
          )}

          {bulkConfirm && (
            <DHConfirmModal title={'Apply ' + bulkConfirm.action.replace('-', ' ') + ' to ' + bulkConfirm.items.length + ' conflict' + (bulkConfirm.items.length !== 1 ? 's' : '') + '?'} onClose={bulkApplying ? null : () => setBulkConfirm(null)} footer={<>
              <button className="btn btn-wh btn-sm" disabled={bulkApplying} onClick={() => setBulkConfirm(null)}>Cancel</button>
              <button className="btn btn-sm" disabled={bulkApplying} onClick={runBulkAction}>{bulkApplying ? 'Applying…' : 'Confirm'}</button>
            </>}>
              <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6, margin: 0 }}>
                {bulkConfirm.action === 'keep-current' && 'The current ERP values are kept for every selected conflict; the incoming snapshot values are discarded for these records.'}
                {bulkConfirm.action === 'use-incoming' && 'The incoming snapshot values replace the current values for every selected conflict. Each record is backed up individually before being overwritten.'}
                {bulkConfirm.action === 'keep-both' && 'Both versions are preserved for every selected conflict — the incoming record is added as a new record alongside the current one.'}
              </p>
            </DHConfirmModal>
          )}

          {rollbackConfirm && (
            <DHConfirmModal title="Roll back this import" danger onClose={() => { setRollbackConfirm(null); setRollbackTyped(''); }} footer={<>
              <button className="btn btn-wh btn-sm" onClick={() => { setRollbackConfirm(null); setRollbackTyped(''); }}>Cancel</button>
              <button className="btn btn-sm" disabled={rollbackTyped.trim().toUpperCase() !== 'RESTORE'} style={{ background: '#991B1B', opacity: rollbackTyped.trim().toUpperCase() === 'RESTORE' ? 1 : .5 }} onClick={doRollback}>Yes, roll back</button>
            </>}>
              <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6, margin: '0 0 12px' }}>{rollbackConfirm.note}</p>
              <label style={{ fontSize: 11.5, color: 'var(--txt2)', display: 'block', marginBottom: 6 }}>Type <strong>RESTORE</strong> to confirm</label>
              <input value={rollbackTyped} onChange={e => setRollbackTyped(e.target.value)} style={{ width: '100%', padding: '7px 9px', border: '1px solid var(--bdr)', borderRadius: 5, fontSize: 13 }} placeholder="RESTORE" />
            </DHConfirmModal>
          )}

          {/* backup ring */}
          <div style={{ border: '1px solid var(--bdr)', borderRadius: 6, overflow: 'hidden' }}>
            <div style={{ padding: '8px 11px', background: '#FAFAF8', fontSize: 12, fontWeight: 600, display: 'flex', justifyContent: 'space-between' }}>
              <span>Pre-write and pre-restore backups — automatic copy kept before any shrink or recovery</span>
              <DHBadge kind="info">{baks.length}</DHBadge>
            </div>
            {baks.length === 0
              ? <div style={{ padding: '10px 11px', fontSize: 11.5, color: 'var(--txt3)' }}>No shrink or recovery events recorded — no collection has lost records since this safeguard was installed.</div>
              : <div className="tbl-w" style={{ maxHeight: 220, overflow: 'auto' }}>
                <table className="tbl"><thead><tr><th>When</th><th>Collection</th><th style={{ textAlign: 'right' }}>Records held</th><th>Reason</th><th></th></tr></thead>
                  <tbody>{baks.map(b => (
                    <tr key={b.key}><td style={DH.mono}>{(b.at || '').replace('T', ' ').slice(0, 19)}</td><td>{b.collection}</td>
                      <td style={{ textAlign: 'right', ...DH.mono }}>{b.count}</td>
                      <td>{b.reason === 'catastrophic-shrink' ? <DHBadge kind="err">EMPTIED — BLOCKED</DHBadge> : b.reason === 'pre-restore' ? <DHBadge kind="info">pre-restore</DHBadge> : <DHBadge kind="warn">shrink</DHBadge>}</td>
                      <td><button className="btn btn-wh btn-sm" onClick={async () => {
                        const r = await Store.readBackup(b.key);
                        if (!r || r.error) return window.toast && window.toast('Backup unreadable: ' + ((r && r.error) || 'not found'), 'er');
                        if (r.snapshot) { DI.download(r.snapshot, 'omg-erp-pre-restore-' + (r.at || '').slice(0, 19).replace(/[:T]/g, '-') + '.json'); return window.toast && window.toast('Pre-restore snapshot exported — the exact dataset as it stood before that recovery.', 'ok'); }
                        DI.download({ _type: 'omg-erp-snapshot', _schema: 2, _createdAt: r.at, _origin: location.origin, _counts: { [r.collection]: r.rows.length }, collections: { [r.collection]: r.rows } }, 'omg-erp-backup-' + r.collection + '.json');
                        window.toast && window.toast('Backup of ' + r.rows.length + ' ' + r.collection + ' record(s) exported. Import it above to restore.', 'ok');
                      }}>Export</button></td></tr>
                  ))}</tbody>
                </table>
              </div>}
          </div>
        </div>

        {/* ── Snapshot registry / monthly timeline ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Snapshot registry — monthly timeline</h3>
          {registry.length === 0 ? (
            <div style={{ fontSize: 11.5, color: 'var(--txt3)' }}>No snapshots registered yet from this browser. Every "Create Snapshot" adds an entry here automatically.</div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
              {registry.map(r => (
                <div key={r.id} style={{ border: '1px solid ' + (r.shrinkWarnings && r.shrinkWarnings.length ? '#FECACA' : 'var(--bdr)'), borderRadius: 6, padding: '8px 11px', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                  <strong style={{ fontSize: 12, minWidth: 90 }}>{r.snapshotDate || (r.createdAt || '').slice(0, 10)}</strong>
                  <DHBadge kind={r.shrinkWarnings && r.shrinkWarnings.length ? 'warn' : 'ok'}>{r.shrinkWarnings && r.shrinkWarnings.length ? 'SHRINK DETECTED' : 'VERIFIED'}</DHBadge>
                  <span style={{ ...DH.mono, color: 'var(--txt2)' }}>{Object.values(r.counts || {}).reduce((a, b) => a + b, 0).toLocaleString('en-IN')} records · {r.source === 'export' ? 'created here' : 'imported'} · {r.fileName || ''}</span>
                  {r.shrinkWarnings && r.shrinkWarnings.length > 0 && (
                    <div style={{ width: '100%', fontSize: 11, color: '#991B1B' }}>
                      {r.shrinkWarnings.map(w => 'WARNING: ' + w.collection + ' decreased by ' + w.drop + ' (' + w.before + ' → ' + w.after + ') vs. the previous verified snapshot.').join(' ')}
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>

        {/* ── Compare snapshots ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Compare two snapshots</h3>
          <div style={{ fontSize: 11.5, color: 'var(--txt2)', marginBottom: 9 }}>Pick an earlier and a later snapshot file to see exactly what changed — added, removed, changed or unresolved, per collection. Read-only; nothing is written.</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 10 }}>
            <input type="file" accept=".json" onChange={e => setCmpFiles([e.target.files[0] || null, cmpFiles[1]])} />
            <span style={{ color: 'var(--txt3)' }}>vs.</span>
            <input type="file" accept=".json" onChange={e => setCmpFiles([cmpFiles[0], e.target.files[0] || null])} />
            <button className="btn btn-sm" disabled={!cmpFiles[0] || !cmpFiles[1]} onClick={runCompare}>Compare</button>
          </div>
          {cmpResult && (
            <div className="tbl-w" style={{ maxHeight: 300, overflow: 'auto' }}>
              <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>A</th><th style={{ textAlign: 'right' }}>B</th><th style={{ textAlign: 'right' }}>Added</th><th style={{ textAlign: 'right' }}>Removed</th><th style={{ textAlign: 'right' }}>Changed</th><th style={{ textAlign: 'right' }}>Unresolved</th></tr></thead>
                <tbody>{cmpResult.rows.map(r => (
                  <tr key={r.collection}><td>{r.collection}</td><td style={{ textAlign: 'right', ...DH.mono }}>{r.inA}</td><td style={{ textAlign: 'right', ...DH.mono }}>{r.inB}</td>
                    <td style={{ textAlign: 'right', ...DH.mono, color: r.added ? '#166534' : 'var(--txt3)' }}>{r.added ? '+' + r.added : '—'}</td>
                    <td style={{ textAlign: 'right', ...DH.mono, color: r.removed ? '#991B1B' : 'var(--txt3)' }}>{r.removed ? '−' + r.removed : '—'}</td>
                    <td style={{ textAlign: 'right', ...DH.mono, color: r.changed ? '#92400E' : 'var(--txt3)' }}>{r.changed || '—'}</td>
                    <td style={{ textAlign: 'right', ...DH.mono, color: r.unresolved ? '#92400E' : 'var(--txt3)' }}>{r.unresolved || '—'}</td></tr>
                ))}</tbody>
              </table>
            </div>
          )}
        </div>

        {/* ── Import history ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Import history</h3>
          {impHist.length === 0 ? (
            <div style={{ fontSize: 11.5, color: 'var(--txt3)' }}>No merges or replaces have been performed from this browser yet.</div>
          ) : (
            <div className="tbl-w" style={{ maxHeight: 260, overflow: 'auto' }}>
              <table className="tbl"><thead><tr><th>When</th><th>Mode</th><th style={{ textAlign: 'right' }}>Added</th><th style={{ textAlign: 'right' }}>Identical</th><th style={{ textAlign: 'right' }}>Conflicts</th><th style={{ textAlign: 'right' }}>Unresolved</th><th>Verified</th><th></th></tr></thead>
                <tbody>{impHist.map(h => (
                  <tr key={h.id}>
                    <td style={DH.mono}>{(h.at || '').replace('T', ' ').slice(0, 19)}</td>
                    <td>{h.mode === 'replace' ? <DHBadge kind="err">REPLACE</DHBadge> : <DHBadge kind="info">MERGE</DHBadge>}</td>
                    <td style={{ textAlign: 'right', ...DH.mono }}>{h.added}</td>
                    <td style={{ textAlign: 'right', ...DH.mono }}>{h.identical != null ? h.identical : '—'}</td>
                    <td style={{ textAlign: 'right', ...DH.mono }}>{h.conflicts != null ? h.conflicts : '—'}</td>
                    <td style={{ textAlign: 'right', ...DH.mono }}>{h.unresolved != null ? h.unresolved : '—'}</td>
                    <td>{h.verifiedOk ? <DHBadge kind="ok">PASSED</DHBadge> : <DHBadge kind="err">CHECK</DHBadge>}</td>
                    <td>{h.backupKey && admin && <button className="btn btn-wh btn-sm" onClick={() => rollBackHistoryEntry(h)}>Roll back</button>}</td>
                  </tr>
                ))}</tbody>
              </table>
            </div>
          )}
        </div>

        {/* ── Find a transaction across snapshots ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Find a transaction across recovery points</h3>
          <div style={{ fontSize: 11.5, color: 'var(--txt2)', marginBottom: 9 }}>
            Search by challan/bill number, record ID, vehicle or date fragment. Checked against the <strong>current ERP</strong> plus any snapshot files you add below — this tool only knows about points you actually supply, it does not invent history.
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 9 }}>
            <input value={findQuery} onChange={e => setFindQuery(e.target.value)} placeholder="e.g. 33618" style={{ padding: '7px 9px', border: '1px solid var(--bdr)', borderRadius: 5, fontSize: 13, minWidth: 180 }} />
            <input id="dh-find-file" type="file" accept=".json" onChange={addFindFile} style={{ display: 'none' }} />
            <button className="btn btn-wh btn-sm" onClick={() => document.getElementById('dh-find-file').click()}>Add snapshot file…</button>
            <button className="btn btn-sm" disabled={!findQuery.trim()} onClick={runFind}>Search</button>
            {findFiles.length > 0 && <span style={{ fontSize: 11, color: 'var(--txt2)' }}>{findFiles.length} file(s) added: {findFiles.map(f => f.name).join(', ')}</span>}
          </div>
          {findResults && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
              {findResults.map(r => (
                <div key={r.pointId} style={{ border: '1px solid var(--bdr)', borderRadius: 6, padding: '8px 11px', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                  <strong style={{ fontSize: 12, minWidth: 140 }}>{r.label}</strong>
                  {r.status === 'not-found' && <DHBadge kind="err">NOT FOUND</DHBadge>}
                  {r.status === 'found' && <DHBadge kind="ok">FOUND ({r.count})</DHBadge>}
                  {r.status === 'conflict-with-current' && <DHBadge kind="warn">FOUND — CONFLICTS WITH CURRENT</DHBadge>}
                  {r.status === 'unresolved-vs-current' && <DHBadge kind="warn">FOUND — UNRESOLVED VS CURRENT</DHBadge>}
                  {r.count > 0 && <span style={{ ...DH.mono, color: 'var(--txt2)' }}>{r.matches.map(m => m.collection + ':' + (m.record.challanNumber || m.record.billNumber || m.record.id)).join(', ')}</span>}
                </div>
              ))}
              <div style={{ fontSize: 11, color: 'var(--txt3)' }}>Absence from a snapshot only means "not present in that file" — it is not evidence of deletion unless the audit log records one.</div>
            </div>
          )}
        </div>

        {/* ── Raw storage scan ── */}
        {scan && (
          <div className="card" style={DH.sec}>
            <h3 style={DH.h}>Raw storage scan — every key in this origin</h3>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(280px,1fr))', gap: 14 }}>
              <div>
                <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--txt2)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 5 }}>localStorage ({scan.localStorage.length} keys · {(scan.totalLsBytes / 1024).toFixed(1)} KB)</div>
                {scan.localStorage.map(k => (
                  <div key={k.key} style={{ display: 'flex', justifyContent: 'space-between', gap: 10, fontSize: 11.5, borderBottom: '1px dotted var(--bdr)', padding: '2px 0' }}>
                    <span style={{ ...DH.mono, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{k.key}</span>
                    <strong style={{ ...DH.mono, whiteSpace: 'nowrap' }}>{(k.bytes / 1024).toFixed(1)} KB</strong>
                  </div>
                ))}
              </div>
              <div>
                <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--txt2)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 5 }}>Legacy / alternate ERP keys</div>
                {scan.legacyHits.length === 0
                  ? <div style={{ fontSize: 11.5, color: 'var(--txt3)' }}>None present. No data is sitting under an older storage key in this origin.</div>
                  : scan.legacyHits.map(h => (
                    <div key={h.key} style={{ fontSize: 11.5, borderBottom: '1px dotted var(--bdr)', padding: '3px 0' }}>
                      <strong style={DH.mono}>{h.key}</strong> — {(h.bytes / 1024).toFixed(1)} KB {h.parseError ? <DHBadge kind="err">unparseable</DHBadge> : null}
                      <div style={{ ...DH.mono, color: 'var(--txt2)' }}>{Object.keys(h.collections).map(c => c + ':' + h.collections[c]).join('  ') || '—'}</div>
                    </div>
                  ))}
              </div>
              <div>
                <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--txt2)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: 5 }}>IndexedDB databases</div>
                {scan.idb.map(d => (
                  <div key={d.name} style={{ fontSize: 11.5, borderBottom: '1px dotted var(--bdr)', padding: '3px 0' }}>
                    <strong style={DH.mono}>{d.name}</strong> <span style={{ color: 'var(--txt3)' }}>v{d.version}</span>
                    <div style={{ ...DH.mono, color: 'var(--txt2)' }}>{Object.keys(d.stores).map(s => s + ':' + d.stores[s]).join('  ')}{d.error ? ' ' + d.error : ''}</div>
                  </div>
                ))}
              </div>
            </div>
            {inv && (
              <div style={{ marginTop: 11, fontSize: 11.5, color: 'var(--txt2)', lineHeight: 1.6, borderTop: '1px solid var(--bdr)', paddingTop: 9 }}>
                Audit trail spans <strong>{(inv.auditFirstLast.first || '—').slice(0, 19).replace('T', ' ')}</strong> → <strong>{(inv.auditFirstLast.last || '—').slice(0, 19).replace('T', ' ')}</strong>
                {' '}across {inv.auditFirstLast.count} entries, of which <strong>{inv.auditFirstLast.deleteEvents}</strong> are delete/reset events.
                A dataset that was never created in this origin leaves no create entries here — which distinguishes deletion from a different-origin dataset.
              </div>
            )}
          </div>
        )}

        {/* ── Advanced Data Administration / Danger Zone ── */}
        <div className="card" style={{ ...DH.sec, borderColor: '#FECACA' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer' }} onClick={() => setAdvancedOpen(o => !o)}>
            <h3 style={{ ...DH.h, margin: 0, color: '#991B1B' }}>Advanced data administration {advancedOpen ? '\u25be' : '\u25b8'}</h3>
            <DHBadge kind="err">DANGER ZONE</DHBadge>
          </div>
          {advancedOpen && (
            <div style={{ marginTop: 12 }}>
              <div style={{ fontSize: 11.5, color: 'var(--txt2)', lineHeight: 1.6, marginBottom: 12 }}>
                For the temporary browser-local trial period: clears every business collection in this browser and replaces it with a complete monthly snapshot — the simple, deterministic alternative to merging month over month. Does not touch the deployed application, another browser, or this browser's login accounts.
                A verified emergency backup is created automatically before anything is deleted, and is required to succeed before deletion proceeds.
              </div>

              {!admin && <div style={{ fontSize: 11.5, color: '#991B1B', marginBottom: 10 }}>🔒 Administrator (ADMIN/SUPER_ADMIN) required.</div>}

              {freshStep === 'pick' && (
                <div>
                  <button className="btn btn-wh btn-sm" disabled={!admin} onClick={() => document.getElementById('dh-fresh-file').click()}>Choose fresh snapshot to import…</button>
                  <input id="dh-fresh-file" type="file" accept=".json" onChange={pickFreshFile} style={{ display: 'none' }} />
                </div>
              )}

              {freshFile && freshStep === 'preview' && (
                <div style={{ border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '10px 12px' }}>
                  <strong style={{ fontSize: 12.5 }}>Snapshot preview — nothing has been cleared or imported yet</strong>
                  <DHIntegrityBadge integrity={freshFile.integrity} />
                  <div style={{ ...DH.kv, margin: '9px 0' }}>
                    <DHStat n={freshFile.snap._snapshotDate || (freshFile.snap._createdAt || '').slice(0, 10)} label="Snapshot date" />
                    <DHStat n={Object.keys(freshFile.snap._counts || {}).length} label="Collections" />
                    <DHStat n={Object.values(freshFile.snap._counts || {}).reduce((a, b) => a + b, 0).toLocaleString('en-IN')} label="Total records" />
                  </div>
                  <div className="tbl-w" style={{ maxHeight: 180, overflow: 'auto', background: '#fff', borderRadius: 5, marginBottom: 10 }}>
                    <table className="tbl"><thead><tr><th>Collection</th><th style={{ textAlign: 'right' }}>In snapshot</th></tr></thead>
                      <tbody>{Object.keys(freshFile.snap._counts || {}).sort().map(c => <tr key={c}><td>{c}</td><td style={{ textAlign: 'right', ...DH.mono }}>{freshFile.snap._counts[c]}</td></tr>)}</tbody>
                    </table>
                  </div>
                  <div style={{ display: 'flex', gap: 8 }}>
                    <button className="btn btn-wh btn-sm" onClick={resetFreshWizard}>Cancel</button>
                    <button className="btn btn-sm" style={{ background: '#991B1B', opacity: (!admin || freshFile.integrity.status === 'failed') ? .5 : 1 }} disabled={!admin || freshFile.integrity.status === 'failed'} onClick={() => setFreshStep('confirm1')}>Continue to Clear All…</button>
                  </div>
                </div>
              )}

              {freshStep === 'confirm1' && (
                <DHConfirmModal title="⚠️ Clear all local ERP data" danger onClose={() => setFreshStep('preview')} footer={<>
                  <button className="btn btn-wh btn-sm" onClick={() => setFreshStep('preview')}>Cancel</button>
                  <button className="btn btn-sm" style={{ background: '#991B1B' }} onClick={() => setFreshStep('confirm2')}>Continue</button>
                </>}>
                  <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6 }}>This will permanently remove ALL business records currently stored in this browser — purchases, sales orders, transport entries, internal transfers, customers, vendors, transporters, materials, companies, settlements, and every other business collection. Login accounts and app settings are preserved.</p>
                  <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6, margin: 0 }}>A verified emergency backup is created automatically first. After clearing, this browser's ERP will contain <strong>zero</strong> business records, and the selected snapshot will then be imported.</p>
                </DHConfirmModal>
              )}

              {freshStep === 'confirm2' && (
                <DHConfirmModal title="Type to confirm" danger onClose={freshBusy ? null : () => { setFreshStep('preview'); setFreshTyped(''); }} footer={<>
                  <button className="btn btn-wh btn-sm" disabled={!!freshBusy} onClick={() => { setFreshStep('preview'); setFreshTyped(''); }}>Cancel</button>
                  <button className="btn btn-sm" style={{ background: '#991B1B', opacity: freshTyped.trim() === 'DELETE ALL DATA' && !freshBusy ? 1 : .5 }} disabled={freshTyped.trim() !== 'DELETE ALL DATA' || !!freshBusy} onClick={runClearAndImport}>{freshBusy || 'Clear All Data → Import Snapshot'}</button>
                </>}>
                  <p style={{ fontSize: 12.5, color: 'var(--txt2)', lineHeight: 1.6 }}>To permanently clear all local ERP data and import the selected snapshot, type <strong>DELETE ALL DATA</strong> below.</p>
                  <input value={freshTyped} onChange={e => setFreshTyped(e.target.value)} disabled={!!freshBusy} style={{ width: '100%', padding: '7px 9px', border: '1px solid var(--bdr)', borderRadius: 5, fontSize: 13 }} placeholder="DELETE ALL DATA" />
                </DHConfirmModal>
              )}

              {freshError && (
                <div style={{ border: '1px solid #FECACA', background: '#FEF2F2', borderRadius: 6, padding: '10px 12px', marginTop: 10 }}>
                  <strong style={{ fontSize: 12.5, color: '#991B1B' }}>{freshError.stage === 'clear' ? 'Clear cancelled — nothing was deleted' : 'Snapshot import failed — local data was already cleared'}</strong>
                  <div style={{ fontSize: 11.5, color: '#991B1B', marginTop: 4 }}>{freshError.message}</div>
                  {freshError.stage === 'import' && freshError.backupKey && (
                    <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                      <button className="btn btn-wh btn-sm" onClick={() => downloadEmergencyBackup(freshError.backupKey)}>Download emergency backup</button>
                      <button className="btn btn-sm" onClick={retryFreshImport}>Retry import</button>
                    </div>
                  )}
                </div>
              )}

              {freshStep === 'done' && freshResult && (
                <div style={{ border: '1px solid #BBF7D0', background: '#F0FDF4', borderRadius: 6, padding: '10px 12px', marginTop: 10 }}>
                  <strong style={{ fontSize: 12.5, color: '#166534' }}>Local ERP data cleared and fresh snapshot imported</strong>
                  <div style={{ ...DH.kv, margin: '9px 0' }}>
                    <DHStat n={freshResult.clearRep ? freshResult.clearRep.totalBefore : '—'} label="Records removed" kind="err" />
                    <DHStat n={freshResult.clearRep ? (freshResult.clearRep.verifiedZero ? 'YES' : 'CHECK') : '—'} label="Verified zero before import" kind={freshResult.clearRep && freshResult.clearRep.verifiedZero ? 'ok' : 'warn'} />
                    <DHStat n={freshResult.importRep.added} label="Records imported" kind="ok" />
                    <DHStat n={freshResult.importRep.verifiedOk ? 'PASSED' : 'REVIEW'} label="Import verification" kind={freshResult.importRep.verifiedOk ? 'ok' : 'err'} />
                  </div>
                  {freshResult.clearRep && <div style={{ fontSize: 11, color: 'var(--txt2)' }}>Emergency pre-clear backup: <code style={DH.mono}>{freshResult.clearRep.backupKey}</code> — <button className="btn btn-wh btn-sm" onClick={() => downloadEmergencyBackup(freshResult.clearRep.backupKey)}>Download</button></div>}
                  <div style={{ marginTop: 8 }}><button className="btn btn-wh btn-sm" onClick={resetFreshWizard}>Done</button></div>
                </div>
              )}
            </div>
          )}
        </div>

        {/* ── Safeguards ── */}
        <div className="card" style={DH.sec}>
          <h3 style={DH.h}>Active safeguards</h3>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(250px,1fr))', gap: 9, fontSize: 11.5, lineHeight: 1.55 }}>
            {[
              ['Read error ≠ empty', 'A collection that fails to decode is reported UNKNOWN and quarantined. It is never shown as zero records.'],
              ['Quarantine blocks writes', 'Once quarantined, no save can overwrite that collection with an empty replacement — the undecodable payload stays as the only copy until a verified restore lands.'],
              ['Last known good count survives quarantine', 'A quarantined collection keeps its last verified record count. A write can never silently convert 1,000+ records into a persisted zero — that write is refused, not the collection\'s memory of what it held.'],
              ['Restore lifts quarantine, empty writes never do', 'The only way out of quarantine is a write carrying verified records back in (Import Snapshot \u2192 Merge). "Recover" only archives the corrupted bytes for forensics — it never deletes, resets or unblocks the collection by itself.'],
              ['Pre-write backup on shrink', 'Any save that reduces a collection stores the previous payload in the backup ring first. Deletes stay reversible.'],
              ['Fail-safe on emptying', 'A write that would take a collection from 5+ records to zero is refused outright and surfaced, not silently committed.'],
              ['Failed writes are never silent', 'A write that storage does not acknowledge raises a blocking banner, keeps its dirty keys for retry, and offers the unsaved work as a download.'],
              ['Nothing is trimmed to make room', 'Storage pressure never deletes records or audit entries. The write is refused and reported instead.'],
              ['Legacy storage is recovered, not replaced', 'An older localStorage blob is backed up verbatim, merged additively by original ID, verified, and left in place afterwards.'],
              ['Backup before repair', 'A restore writes a full pre-restore snapshot into the backup ring first, and aborts outright if that backup cannot be written.'],
              ['Restore is additive', 'Recovery only ever adds. It never deletes, never overwrites a differing record, and the count invariant is checked before anything is mutated.'],
              ['Idempotent recovery', 'Records are matched on original ID, so importing the same snapshot twice adds nothing the second time.'],
              ['Read-back verification', 'After a restore the write is awaited and physical storage is polled until the counts match the expected result.'],
              ['Read-only diagnostics', 'Everything on this page except the restore button reads. Reports, exports and AI queries never mutate transactional data.'],
            ].map(([t, d]) => (
              <div key={t} style={{ border: '1px solid var(--bdr)', borderRadius: 6, padding: '8px 10px' }}>
                <strong style={{ fontSize: 12 }}>{t}</strong>
                <div style={{ color: 'var(--txt2)', marginTop: 3 }}>{d}</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

function fmtN(n) { return n == null ? '—' : Number(n).toLocaleString('en-IN'); }
function mb(n) {
  if (n >= 1099511627776) return (n / 1099511627776).toFixed(1) + ' TB';
  if (n >= 1073741824) return (n / 1073741824).toFixed(1) + ' GB';
  if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB';
  return Math.round(n / 1024) + ' KB';
}

window.DataHealthPage = DataHealthPage;
