/* ============================================================
   MUIY — Lab Results Import  ·  LabImportSheet
   Upload → Process (enhance + OCR) → Review → Save
   Photos · screenshots · scanned reports · multi-page PDFs.
   Reads window.LABX (OCR engine) + window.MON (registry).
   ============================================================ */

/* ---- tiny presentational bits ---- */
function ConfBar({ v }) {
  const pct = Math.round((v || 0) * 100);
  const col = pct >= 85 ? 'var(--success)' : pct >= 60 ? 'var(--waiting)' : 'var(--orange)';
  return (
    <div className="row" style={{ gap: 6, alignItems: 'center' }} title={'OCR confidence ' + pct + '%'}>
      <div style={{ width: 34, height: 5, borderRadius: 999, background: 'var(--hairline)', overflow: 'hidden', flex: 'none' }}>
        <div style={{ width: pct + '%', height: '100%', background: col, borderRadius: 999 }} />
      </div>
      <span style={{ fontSize: 10.5, fontWeight: 800, color: col, fontVariantNumeric: 'tabular-nums' }}>{pct}%</span>
    </div>
  );
}

function fmtSize(b) {
  if (b < 1024) return b + ' B';
  if (b < 1048576) return Math.round(b / 1024) + ' KB';
  return (b / 1048576).toFixed(1) + ' MB';
}
function fileKind(f) {
  if (LABX.isPdf(f)) return 'PDF';
  if (LABX.isHeic(f)) return 'HEIC';
  return (f.type.split('/')[1] || 'image').toUpperCase();
}

/* ---- assign / relabel a result to a registry test ---- */
function RemapPicker({ rawName, onPick, onClose }) {
  const [q, setQ] = React.useState('');
  const list = React.useMemo(() => {
    if (q.trim()) return MON.REGISTRY.filter((t) => MON.searchTest(t, q)).slice(0, 14);
    return LABX.suggestions(rawName, 8).filter((s) => s.score > 0.18).map((s) => s.test);
  }, [q, rawName]);
  return (
    <div className="card-flat" style={{ padding: 11, marginTop: 9, background: 'var(--surface-2)' }}>
      <div className="row" style={{ gap: 8, marginBottom: 9 }}>
        <input className="input" autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder={'Match “' + (rawName || 'this result').slice(0, 22) + '” to…'} style={{ flex: 1 }} />
        <button className="icon-btn" style={{ width: 38, height: 38, flex: 'none' }} onClick={onClose} aria-label="Close"><Icon name="x" size={15} color="var(--ink-2)" /></button>
      </div>
      <div style={{ maxHeight: 188, overflowY: 'auto', scrollbarWidth: 'none', display: 'flex', flexDirection: 'column' }}>
        {list.map((t) => {
          const g = MON.groupOfCat(t.category);
          return (
            <button key={t.id} className="row" style={{ gap: 10, padding: '9px 4px', background: 'none', border: 'none', borderTop: '1px solid var(--hairline)', cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit', width: '100%' }} onClick={() => onPick(t)}>
              <span style={{ width: 8, height: 8, borderRadius: 999, flex: 'none', background: (MON.catMeta(t.category) || {}).color || 'var(--orange)' }} />
              <span style={{ flex: 1, minWidth: 0 }}>
                <span style={{ display: 'block', fontWeight: 800, fontSize: 13.5, color: 'var(--ink)' }}>{t.name}</span>
                <span className="muted" style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-3)' }}>{g ? g.label : t.category}{t.unit ? ' · ' + t.unit : ''}</span>
              </span>
              <Icon name="chevR" size={15} color="var(--ink-3)" />
            </button>
          );
        })}
        {!list.length && <div className="body" style={{ fontSize: 12.5, padding: '8px 4px' }}>No matching test — try another name.</div>}
      </div>
    </div>
  );
}

/* ---- one editable result row ---- */
function ResultRow({ row, onChange, onRemove, onRemap }) {
  const [open, setOpen] = React.useState(false);
  const t = row.testId ? MON.testById(row.testId) : null;
  const flag = t && t.normal_range ? MON.valueFlag(t, row.value) : null;
  const hasErr = row.warnings.some((w) => w.level === 'error');
  const hasWarn = row.warnings.some((w) => w.level === 'warn');
  const lowConf = row.ocrConfidence < 0.6;
  const accent = row.status === 'unmatched' ? 'var(--ink-3)'
    : hasErr ? 'var(--orange)' : (hasWarn || lowConf) ? 'var(--waiting)' : 'var(--hairline)';
  return (
    <div className="card-flat" style={{ padding: 12, borderColor: accent, borderWidth: (hasErr || hasWarn || lowConf || row.status === 'unmatched') ? 2 : 1.5, opacity: row.include ? 1 : 0.55 }}>
      <div className="between" style={{ gap: 8, marginBottom: 9 }}>
        <button onClick={() => onChange({ include: !row.include })} className="row" style={{ gap: 9, minWidth: 0, flex: 1, background: 'none', border: 'none', padding: 0, cursor: 'pointer', textAlign: 'left', fontFamily: 'inherit' }}>
          <span style={{ width: 20, height: 20, borderRadius: 6, flex: 'none', display: 'grid', placeItems: 'center', background: row.include ? 'var(--orange)' : 'var(--surface-2)', border: row.include ? 'none' : '1.5px solid var(--hairline-2)' }}>
            {row.include && <Icon name="check" size={13} color="#fff" sw={3} />}
          </span>
          <span style={{ minWidth: 0 }}>
            <span style={{ display: 'block', fontWeight: 800, fontSize: 13.8, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t ? t.name : (row.rawName || 'Unrecognised')}</span>
            {row.testId && row.rawName && LABX.norm(row.rawName) !== LABX.norm(t.name) && (
              <span className="muted" style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--ink-3)' }}>read as “{row.rawName}”</span>
            )}
          </span>
        </button>
        <ConfBar v={row.ocrConfidence} />
      </div>

      <div className="row" style={{ gap: 8 }}>
        <input className="input" value={row.value} onChange={(e) => onChange({ value: e.target.value })} placeholder="Value" style={{ flex: 1.2, borderColor: hasErr ? 'var(--orange)' : (flag && flag.dir !== 'normal' ? flag.color : undefined) }} />
        <input className="input" value={row.unit || ''} onChange={(e) => onChange({ unit: e.target.value })} placeholder="Unit" style={{ flex: 1 }} />
      </div>
      <div className="row" style={{ gap: 8, marginTop: 8, alignItems: 'center' }}>
        <input className="input" type="date" value={row.date} onChange={(e) => onChange({ date: e.target.value })} style={{ flex: 1 }} />
        {flag && flag.dir !== 'normal' && <span className="chip-status" style={{ background: flag.wash, color: flag.color, flex: 'none' }}>{flag.label}</span>}
        {row.range && <span className="muted" style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>ref {row.range}</span>}
      </div>

      {row.warnings.map((w, i) => (
        <div key={i} className="row" style={{ gap: 7, marginTop: 8, alignItems: 'flex-start' }}>
          <Icon name={w.level === 'info' ? 'info' : 'warn'} size={13} color={w.level === 'error' ? 'var(--orange)' : w.level === 'info' ? 'var(--sky-deep)' : 'var(--waiting)'} style={{ flex: 'none', marginTop: 1.5 }} />
          <span className="body" style={{ fontSize: 11.5, lineHeight: 1.4, color: 'var(--ink-2)' }}>{w.msg}</span>
        </div>
      ))}

      <div className="row" style={{ gap: 8, marginTop: 10, justifyContent: 'space-between' }}>
        <button className="link" style={{ padding: 0, fontSize: 12 }} onClick={() => setOpen((o) => !o)}>
          <Icon name="refresh" size={13} color="var(--orange)" /> {row.testId ? 'Change test' : 'Match to a test'}
        </button>
        <button className="icon-btn" style={{ width: 30, height: 30, background: 'var(--surface-2)', flex: 'none' }} onClick={onRemove} aria-label="Remove"><Icon name="trash" size={13.5} color="var(--ink-2)" /></button>
      </div>
      {open && <RemapPicker rawName={row.rawName} onClose={() => setOpen(false)} onPick={(t) => { onRemap(t); setOpen(false); }} />}
    </div>
  );
}

/* ---- thalassaemia-aware highlights ---- */
function ImportHighlights({ rows, store }) {
  const items = [];
  const keep = rows.filter((r) => r.include && r.testId && String(r.value).trim());
  const get = (id) => keep.find((r) => r.testId === id);

  const fer = get('ferritin');
  if (fer) {
    const v = LABX.numOf(fer.value);
    const tgt = (window.CLINICAL && CLINICAL.FERRITIN.target) || 1000;
    const mod = (window.CLINICAL && CLINICAL.FERRITIN.moderate) || 2500;
    // trend vs the most recent ferritin already on record
    const prior = (store.data.testLogs || []).filter((l) => l.testId === 'ferritin' && l.date < fer.date)
      .sort((a, b) => (a.date < b.date ? 1 : -1))[0];
    let trend = '';
    if (prior) { const pv = LABX.numOf(prior.value); if (!isNaN(pv) && !isNaN(v)) { const d = v - pv; trend = d === 0 ? 'unchanged' : (d < 0 ? '↓ ' + Math.abs(Math.round(d)) + ' from last' : '↑ ' + Math.round(d) + ' from last'); } }
    if (!isNaN(v)) {
      const over = v > mod ? 'high' : v > tgt ? 'warn' : 'good';
      items.push({
        icon: 'sparkle', tone: over,
        title: 'Ferritin ' + Math.round(v) + ' ng/mL',
        sub: (v > mod ? 'Above the ' + mod + ' risk threshold' : v > tgt ? 'Above the ' + tgt + ' target band' : 'Within target band') + (trend ? ' · ' + trend : ''),
      });
    }
  }
  const liver = ['alt', 'ast', 'ggt'].map(get).filter(Boolean).filter((r) => { const t = MON.testById(r.testId); const f = t && MON.valueFlag(t, r.value); return f && f.dir !== 'normal'; });
  if (liver.length) items.push({ icon: 'clipboard', tone: 'warn', title: 'Liver markers flagged', sub: liver.map((r) => MON.testById(r.testId).name + ' ' + r.value).join(' · ') });
  const endo = ['tsh', 'free_t4', 'fsh', 'lh', 'testosterone', 'estradiol', 'cortisol', 'hba1c'].map(get).filter(Boolean);
  if (endo.length) items.push({ icon: 'shield', tone: 'info', title: 'Endocrine results captured', sub: endo.map((r) => MON.testById(r.testId).name).join(' · ') });
  const renal = ['creatinine', 'egfr', 'urea'].map(get).filter(Boolean);
  if (renal.length) items.push({ icon: 'drop', tone: 'info', title: 'Renal results captured', sub: renal.map((r) => MON.testById(r.testId).name).join(' · ') });

  if (!items.length) return null;
  const TONE = { high: ['var(--orange-wash)', 'var(--orange)'], warn: ['var(--yellow-wash)', 'var(--waiting)'], info: ['var(--sky-wash)', 'var(--sky-deep)'], good: ['var(--green-wash)', 'var(--success)'] };
  return (
    <div className="card" style={{ padding: 14, marginBottom: 14 }}>
      <div className="row" style={{ gap: 8, marginBottom: 11 }}>
        <Icon name="heart" size={15} color="var(--orange)" fill="var(--orange)" />
        <span style={{ fontWeight: 900, fontSize: 11.5, letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--orange)' }}>For your thalassaemia care</span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {items.map((it, i) => {
          const [bg, col] = TONE[it.tone] || TONE.info;
          return (
            <div key={i} className="row" style={{ gap: 11, alignItems: 'flex-start' }}>
              <span style={{ width: 30, height: 30, borderRadius: 10, background: bg, flex: 'none', display: 'grid', placeItems: 'center' }}><Icon name={it.icon} size={15} color={col} fill={it.icon === 'heart' ? col : 'none'} /></span>
              <span style={{ minWidth: 0 }}>
                <span style={{ display: 'block', fontWeight: 800, fontSize: 13.5, color: 'var(--ink)' }}>{it.title}</span>
                <span className="body" style={{ fontSize: 12, lineHeight: 1.4 }}>{it.sub}</span>
              </span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ============================================================
   MAIN SHEET
   ============================================================ */
function LabImportSheet({ store, close, profile }) {
  const [stage, setStage] = React.useState('intake'); // intake | processing | review | saved | error
  const [files, setFiles] = React.useState([]);        // {id,file,name,size,kind,status,err,pages}
  const [enhance, setEnhance] = React.useState(true);
  const [paste, setPaste] = React.useState('');
  const [showPaste, setShowPaste] = React.useState(false);
  const [prog, setProg] = React.useState({ label: '', pct: 0 });
  const [meta, setMeta] = React.useState({});
  const [rows, setRows] = React.useState([]);
  const [err, setErr] = React.useState('');
  const [readWarning, setReadWarning] = React.useState('');
  const [savedInfo, setSavedInfo] = React.useState(null);
  const [addOpen, setAddOpen] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [keepOriginal, setKeepOriginal] = React.useState(false); // storage-opt: discard original by default
  const fileRef = React.useRef(null);
  const camRef = React.useRef(null);
  const dropRef = React.useRef(null);

  const addFiles = (list) => {
    const arr = Array.from(list || []);
    if (!arr.length) return;
    setFiles((cur) => cur.concat(arr.map((f) => ({
      id: (window.TC && TC.uid) ? TC.uid() : Math.random().toString(36).slice(2, 10),
      file: f, name: f.name || 'report', size: f.size, kind: fileKind(f), status: 'queued', err: '', pages: [], thumb: null,
    }))));
  };
  const onFile = (e) => { addFiles(e.target.files); e.target.value = ''; };
  const removeFile = (id) => setFiles((cur) => cur.filter((f) => f.id !== id));

  React.useEffect(() => {
    const el = dropRef.current; if (!el) return;
    const over = (e) => { e.preventDefault(); el.classList.add('drag'); };
    const leave = () => el.classList.remove('drag');
    const drop = (e) => { e.preventDefault(); el.classList.remove('drag'); addFiles(e.dataTransfer.files); };
    el.addEventListener('dragover', over); el.addEventListener('dragleave', leave); el.addEventListener('drop', drop);
    return () => { el.removeEventListener('dragover', over); el.removeEventListener('dragleave', leave); el.removeEventListener('drop', drop); };
  }, [stage]);

  const recompute = (list) => list.map((r) => ({ ...r, warnings: LABX.validateRow(r, store.data.testLogs || []) }));

  const run = async () => {
    if (!files.length && !paste.trim()) return;
    setStage('processing'); setErr(''); setReadWarning('');
    try {
      // 1 · prepare every file. Born-digital PDFs yield their TEXT layer (no OCR
      //     — exact + fast + bypasses the image path entirely); everything else
      //     becomes enhanced page images for vision OCR.
      const pageUrls = [];
      let digitalText = '';
      let anyDigital = false;
      let fi = 0;
      for (const f of files) {
        fi++;
        setFiles((cur) => cur.map((x) => x.id === f.id ? { ...x, status: 'processing' } : x));
        setProg({ label: 'Preparing ' + f.name + (files.length > 1 ? '  (' + fi + '/' + files.length + ')' : ''), pct: Math.round((fi - 0.5) / (files.length + 1) * 40) });
        try {
          const prepared = await LABX.prepareFile(f.file, {
            enhance,
            onPage: (p, n) => setProg({ label: 'Rendering page ' + p + ' of ' + n + ' · ' + f.name, pct: 10 + Math.round(p / n * 25) }),
            onText: (p, n) => setProg({ label: 'Reading text · page ' + p + ' of ' + n + ' · ' + f.name, pct: 10 + Math.round(p / n * 25) }),
          });
          if (prepared.mode === 'text') {
            digitalText += '\n\n' + prepared.text; anyDigital = true;
            setFiles((cur) => cur.map((x) => x.id === f.id ? { ...x, status: 'done', source: 'pdf-text', pages: [] } : x));
          } else {
            prepared.pages.forEach((pg) => pageUrls.push(pg.dataUrl));
            setFiles((cur) => cur.map((x) => x.id === f.id ? { ...x, status: 'done', source: prepared.source, pages: prepared.pages, thumb: prepared.pages[0] && prepared.pages[0].thumb } : x));
          }
        } catch (e) {
          setFiles((cur) => cur.map((x) => x.id === f.id ? { ...x, status: 'error', err: (e && e.friendly) || 'Couldn’t read this file.' } : x));
        }
      }
      const extraText = (digitalText + '\n' + paste).trim();
      if (!pageUrls.length && !extraText) {
        setErr(LABX.classifyError({ allFailed: true }).message); setStage('error'); return;
      }
      // 2 · extract + map
      const onP = (p) => {
        if (p.phase === 'reading') setProg({ label: 'Reading report' + (p.pages > 1 ? ' · page ' + p.page + '/' + p.pages : '') + '…', pct: 45 + Math.round((p.page / Math.max(1, p.pages)) * 40) });
        else if (p.phase === 'matching') setProg({ label: 'Matching to your tests…', pct: 92 });
      };
      const res = pageUrls.length
        ? await LABX.extractFromPages(pageUrls, extraText, onP)
        : await LABX.extractFromTextOnly(extraText, onP, anyDigital ? 'pdf-text' : 'text');
      setMeta(res.meta || {});
      setRows(recompute(res.rows));
      if (!res.rows.length) {
        // SOFT path — never a dead end. Land on review so the user can confirm or
        // add values by hand, with the page image alongside for reference.
        setReadWarning('We couldn’t automatically pull values from this report — the layout may be unusual. Add results by hand below, or paste the text. The page is shown here for reference.');
      }
      setStage('review');
    } catch (e) {
      setErr(LABX.classifyError(e).message); setStage('error');
    }
  };

  const patchRow = (id, patch) => setRows((cur) => recompute(cur.map((r) => r._id === id ? { ...r, ...patch } : r)));
  const remapRow = (id, t) => setRows((cur) => recompute(cur.map((r) => r._id === id ? { ...r, testId: t.id, status: 'matched', matchScore: 1, matchMethod: 'manual', include: true, unit: r.unit || t.unit || null } : r)));
  const removeRow = (id) => setRows((cur) => cur.filter((r) => r._id !== id));
  const addRow = (t) => { setRows((cur) => recompute(cur.concat([LABX.buildRow({ n: t.name, v: '', u: t.unit, c: 100, d: anchorDate() }, meta)].map((r) => ({ ...r, testId: t.id, status: 'matched', matchScore: 1, include: true }))))); setAddOpen(false); };
  const anchorDate = () => rows[0] ? rows[0].date : (LABX.todayStr());
  const applyDateAll = (d) => setRows((cur) => recompute(cur.map((r) => ({ ...r, date: d }))));

  const hasError = (r) => r.warnings && r.warnings.some((w) => w.level === 'error');
  const kept = rows.filter((r) => r.include && r.testId && String(r.value).trim() && !hasError(r));
  const blockedRows = rows.filter((r) => r.include && r.testId && String(r.value).trim() && hasError(r));
  const needsReview = rows.filter((r) => r.include && (r.status !== 'matched' || r.ocrConfidence < 0.6 || r.warnings.some((w) => w.level !== 'info')));
  const unmatched = rows.filter((r) => r.status === 'unmatched');

  const save = async () => {
    if (!kept.length) { close(); return; }
    if (saving) return;
    const importId = (window.TC && TC.uid) ? TC.uid() : Math.random().toString(36).slice(2, 10);
    const stamp = new Date().toISOString();
    const avgConf = Math.round(kept.reduce((s, r) => s + r.ocrConfidence, 0) / kept.length * 100);

    // STORAGE OPTIMISATION — by default we retain ONLY the structured values
    // below; the original photo/PDF (and its thumbnail) is discarded the moment
    // the user saves. They opt in via “Keep the original report”, which uploads
    // it to the private lab-reports bucket for future reference.
    setSaving(true);
    const fileMeta = [];
    if (keepOriginal) {
      for (const f of files) {
        let ref = null;
        try { ref = await window.TCFiles.putOriginal(f.file, { bucket: 'lab-reports', folder: importId, kind: 'lab_report', refId: importId, name: f.name }); }
        catch (e) { ref = null; }
        fileMeta.push({ name: f.name, kind: f.kind, size: f.size, thumb: f.thumb || null, ref: ref });
      }
    }
    setSaving(false);

    store.mutate((d) => {
      d.testLogs = d.testLogs || [];
      d.imports = d.imports || [];
      d.imports.push({
        id: importId, importedAt: stamp, method: 'ocr',
        lab: meta.lab || null, patient: meta.patient || null,
        reportDate: meta.reportDate || null, collected: meta.collected || null,
        resultCount: kept.length, avgConfidence: avgConf,
        keptOriginal: !!keepOriginal,
        files: fileMeta,
      });
      kept.forEach((r) => {
        const vid = MON.ensureVisit(d, r.date, r.testId);
        d.testLogs.push({
          id: (window.TC && TC.uid) ? TC.uid() : Math.random().toString(36).slice(2, 10),
          testId: r.testId, date: r.date, value: String(r.value).trim(), unit: r.unit || null,
          method: 'scan', visitId: vid, notes: null, createdAt: stamp,
          importId, confidence: Math.round(r.ocrConfidence * 100), matchScore: r.matchScore,
          originalName: r.rawName || null, originalUnit: r.unit || null,
        });
        // mirror Hb + ferritin into their dedicated logs so Today/Health trends refresh
        const nv = LABX.numOf(r.value);
        if (r.testId === 'haemoglobin' && !isNaN(nv)) { d.hbLog = d.hbLog || []; d.hbLog.push({ id: TC.uid(), date: r.date, value: nv, source: 'lab_report', importId }); }
        if (r.testId === 'ferritin' && !isNaN(nv)) { d.ferritinLog = d.ferritinLog || []; d.ferritinLog.push({ id: TC.uid(), date: r.date, value: nv, source: 'lab_report', importId }); }
      });
    });
    const dates = Array.from(new Set(kept.map((r) => r.date)));
    setSavedInfo({ count: kept.length, dates: dates.length, avgConf, kept: !!keepOriginal && files.length > 0 });
    setStage('saved');
  };

  /* ---------- render ---------- */
  const title = stage === 'review' ? 'Review extracted results'
    : stage === 'saved' ? 'Results imported'
    : stage === 'processing' ? 'Reading your report' : 'Import lab results';

  return (
    <MSheet title={title} onClose={close}>
      {stage === 'intake' && (
        <div>
          <div className="li-actions">
            <button className="li-action" onClick={() => camRef.current && camRef.current.click()}>
              <span className="li-action-ic"><Icon name="camera" size={23} color="var(--orange)" /></span>
              <span className="li-action-t">Take photo</span>
              <span className="li-action-s">Open camera</span>
            </button>
            <button className="li-action" onClick={() => fileRef.current && fileRef.current.click()}>
              <span className="li-action-ic"><Icon name="image" size={23} color="var(--orange)" /></span>
              <span className="li-action-t">Choose file</span>
              <span className="li-action-s">Photo · PDF</span>
            </button>
          </div>
          <div ref={dropRef} className="li-drop li-drop-sm" onClick={() => fileRef.current && fileRef.current.click()}>
            <span className="body" style={{ fontSize: 11.5, textAlign: 'center', maxWidth: 290 }}>…or drag files here · multiple welcome · JPG · PNG · HEIC · WEBP · PDF</span>
          </div>
          {/* capture="environment" opens the rear camera straight away on phones */}
          <input ref={camRef} type="file" accept="image/*" capture="environment" style={{ display: 'none' }} onChange={onFile} />
          <input ref={fileRef} type="file" multiple accept="image/*,.heic,.heif,.webp,application/pdf,.pdf" style={{ display: 'none' }} onChange={onFile} />

          {files.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 12 }}>
              {files.map((f) => (
                <div key={f.id} className="card-flat row" style={{ padding: 10, gap: 11 }}>
                  <span style={{ width: 40, height: 40, borderRadius: 9, flex: 'none', background: 'var(--orange-wash)', display: 'grid', placeItems: 'center', color: 'var(--orange)', fontWeight: 900, fontSize: 10.5 }}>{f.kind}</span>
                  <span style={{ flex: 1, minWidth: 0 }}>
                    <span style={{ display: 'block', fontWeight: 700, fontSize: 13, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{f.name}</span>
                    <span className="muted" style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-3)' }}>{fmtSize(f.size)}</span>
                  </span>
                  <button className="icon-btn" style={{ width: 30, height: 30, background: 'var(--surface-2)', flex: 'none' }} onClick={() => removeFile(f.id)} aria-label="Remove"><Icon name="x" size={14} color="var(--ink-2)" /></button>
                </div>
              ))}
            </div>
          )}

          <button className="row" onClick={() => setEnhance((v) => !v)} style={{ gap: 10, width: '100%', marginTop: 12, padding: '11px 13px', background: 'var(--surface-2)', border: '1.5px solid var(--hairline)', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left' }}>
            <span style={{ flex: 1, minWidth: 0 }}>
              <span style={{ display: 'block', fontWeight: 800, fontSize: 13, color: 'var(--ink)' }}>Enhance before reading</span>
              <span className="muted" style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink-3)' }}>Deskew tone · boost contrast · sharpen faint scans</span>
            </span>
            <span className={'switch' + (enhance ? ' on' : '')} style={{ flex: 'none' }}><span className="knob" /></span>
          </button>

          {showPaste ? (
            <textarea className="input textarea" rows={5} value={paste} onChange={(e) => setPaste(e.target.value)} placeholder={'Or paste the report text…\nSerum Ferritin  1180 ng/mL\nTSH  5.2 mIU/L'} style={{ fontFamily: 'inherit', lineHeight: 1.5, marginTop: 12 }} />
          ) : (
            <button className="link" style={{ marginTop: 12, fontSize: 12.5 }} onClick={() => setShowPaste(true)}><Icon name="edit" size={13} color="var(--orange)" /> Paste text instead</button>
          )}

          <button className="btn btn-primary" style={{ width: '100%', marginTop: 16 }} disabled={!files.length && !paste.trim()} onClick={run}>
            <Icon name="sparkle" size={17} color="#fff" fill="#fff" /> Read {files.length ? files.length + (files.length === 1 ? ' file' : ' files') : 'the report'}
          </button>
          <div className="row" style={{ gap: 7, marginTop: 11, alignItems: 'flex-start' }}>
            <Icon name="shield" size={13} color="var(--ink-3)" style={{ flex: 'none', marginTop: 1 }} />
            <span className="muted" style={{ fontSize: 11, lineHeight: 1.45, color: 'var(--ink-3)' }}>Reading happens on your device — your report isn’t sent to any server. Only printed values are extracted, and nothing saves until you confirm.</span>
          </div>
        </div>
      )}

      {stage === 'processing' && (
        <div style={{ padding: '14px 0 8px' }}>
          <div style={{ textAlign: 'center', marginBottom: 18 }}>
            <div className="leaf mon-pulse" style={{ width: 56, height: 56, margin: '0 auto 14px', background: 'var(--orange-wash)', color: 'var(--orange)', borderRadius: 18, display: 'grid', placeItems: 'center' }}><Icon name="sparkle" size={26} color="var(--orange)" fill="var(--orange)" /></div>
            <div className="h2" style={{ fontSize: 16 }}>{prog.label || 'Working…'}</div>
            <div className="body" style={{ fontSize: 12.5, marginTop: 3 }}>Accuracy first — this can take a moment</div>
          </div>
          <div style={{ height: 7, borderRadius: 999, background: 'var(--hairline)', overflow: 'hidden' }}><div style={{ width: Math.max(6, prog.pct) + '%', height: '100%', background: 'linear-gradient(90deg,var(--yellow),var(--orange))', borderRadius: 999, transition: 'width .4s ease' }} /></div>
          {files.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 14 }}>
              {files.map((f) => (
                <div key={f.id} className="row" style={{ gap: 9, fontSize: 12 }}>
                  <Icon name={f.status === 'done' ? 'check' : f.status === 'error' ? 'warn' : 'clock'} size={14} color={f.status === 'done' ? 'var(--success)' : f.status === 'error' ? 'var(--orange)' : 'var(--ink-3)'} />
                  <span style={{ flex: 1, minWidth: 0, fontWeight: 700, color: 'var(--ink-2)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{f.name}</span>
                  <span className="muted" style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 700 }}>{f.status === 'error' ? 'skipped' : f.status === 'done' ? (f.pages.length + 'p') : '…'}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {stage === 'error' && (
        <div>
          <MWarn>{err}</MWarn>
          <div className="row" style={{ gap: 10 }}>
            <button className="btn btn-ghost" style={{ flex: 1 }} onClick={() => setStage('intake')}>Back</button>
            <button className="btn btn-primary" style={{ flex: 1 }} onClick={run}>Try again</button>
          </div>
        </div>
      )}

      {stage === 'review' && (
        <div style={{ maxHeight: '64vh', overflowY: 'auto', scrollbarWidth: 'none', margin: '0 -2px', padding: '0 2px' }}>
          {readWarning && (
            <div className="row" style={{ gap: 9, padding: '11px 12px', marginBottom: 12, background: 'var(--orange-wash)', borderRadius: 12, alignItems: 'flex-start' }}>
              <Icon name="info" size={15} color="var(--orange)" style={{ flex: 'none', marginTop: 1 }} />
              <span className="body" style={{ fontSize: 12, color: 'var(--ink-2)', lineHeight: 1.45 }}>{readWarning}</span>
            </div>
          )}
          {readWarning && files.some((f) => f.thumb) && (
            <div className="row" style={{ gap: 8, marginBottom: 12, overflowX: 'auto', scrollbarWidth: 'none' }}>
              {files.filter((f) => f.thumb).map((f) => (
                <img key={f.id} src={f.thumb} alt={f.name} style={{ height: 124, borderRadius: 10, border: '1.5px solid var(--hairline)', flex: 'none' }} />
              ))}
            </div>
          )}
          {/* report metadata */}
          <div className="card-flat" style={{ padding: 13, marginBottom: 13 }}>
            <div className="between" style={{ marginBottom: 10 }}>
              <span style={{ fontWeight: 900, fontSize: 11, letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>Report details</span>
              <span className="chip-status" style={{ background: 'var(--green-wash)', color: 'var(--success)' }}>{rows.length} found</span>
            </div>
            <div className="grid-2" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <label style={{ minWidth: 0 }}><span className="li-lbl">Laboratory</span><input className="input" value={meta.lab || ''} onChange={(e) => setMeta({ ...meta, lab: e.target.value })} placeholder="—" /></label>
              <label style={{ minWidth: 0 }}><span className="li-lbl">Patient</span><input className="input" value={meta.patient || ''} onChange={(e) => setMeta({ ...meta, patient: e.target.value })} placeholder="—" /></label>
              <label style={{ minWidth: 0 }}><span className="li-lbl">Collected</span><input className="input" type="date" value={meta.collected || ''} onChange={(e) => { setMeta({ ...meta, collected: e.target.value }); }} /></label>
              <label style={{ minWidth: 0 }}><span className="li-lbl">Reported</span><input className="input" type="date" value={meta.reportDate || ''} onChange={(e) => setMeta({ ...meta, reportDate: e.target.value })} /></label>
            </div>
            {meta.collected && (
              <button className="link" style={{ marginTop: 9, fontSize: 11.5, padding: 0 }} onClick={() => applyDateAll(meta.collected)}><Icon name="calendar" size={12} color="var(--orange)" /> Use collected date for all results</button>
            )}
          </div>

          <ImportHighlights rows={rows} store={store} />

          {blockedRows.length > 0 && (
            <div className="row" style={{ gap: 8, padding: '10px 12px', marginBottom: 11, background: 'var(--orange-wash)', borderRadius: 12 }} role="alert">
              <Icon name="warn" size={15} color="var(--orange)" style={{ flex: 'none' }} />
              <span className="body" style={{ fontSize: 12, color: 'var(--orange)', fontWeight: 700 }}>{blockedRows.length} result{blockedRows.length === 1 ? '' : 's'} can’t be saved as read (impossible value) — fix or remove {blockedRows.length === 1 ? 'it' : 'them'} below.</span>
            </div>
          )}

          {needsReview.length > 0 && (
            <div className="row" style={{ gap: 8, padding: '10px 12px', marginBottom: 11, background: 'var(--yellow-wash)', borderRadius: 12 }}>
              <Icon name="warn" size={15} color="var(--waiting)" style={{ flex: 'none' }} />
              <span className="body" style={{ fontSize: 12, color: 'var(--waiting)', fontWeight: 700 }}>{needsReview.length} result{needsReview.length === 1 ? '' : 's'} need a quick check — low confidence or a flagged value.</span>
            </div>
          )}

          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {rows.map((r) => (
              <ResultRow key={r._id} row={r} onChange={(p) => patchRow(r._id, p)} onRemove={() => removeRow(r._id)} onRemap={(t) => remapRow(r._id, t)} />
            ))}
          </div>

          {addOpen ? (
            <RemapPicker rawName="" onClose={() => setAddOpen(false)} onPick={addRow} />
          ) : (
            <button className="btn btn-ghost" style={{ width: '100%', marginTop: 10 }} onClick={() => setAddOpen(true)}><Icon name="plus" size={16} color="var(--ink)" /> Add a missing result</button>
          )}

          {files.length > 0 && (
            <div className="li-keep">
              <button className="row li-keep-row" onClick={() => setKeepOriginal((v) => !v)}>
                <span className="li-keep-ic"><Icon name={keepOriginal ? 'archive' : 'shield'} size={16} color={keepOriginal ? 'var(--orange)' : 'var(--success)'} /></span>
                <span style={{ flex: 1, minWidth: 0 }}>
                  <span className="li-keep-t">Keep the original {files.length === 1 ? 'report' : 'reports'}</span>
                  <span className="li-keep-s">{keepOriginal ? 'Stored securely in your private records for future reference.' : 'Off — only the values below are saved; the image/PDF is discarded for privacy.'}</span>
                </span>
                <span className={'switch' + (keepOriginal ? ' on' : '')} style={{ flex: 'none' }}><span className="knob" /></span>
              </button>
            </div>
          )}

          <MedicalDisclaimer variant="card" style={{ margin: '14px 0 4px' }} />
          <div className="row" style={{ gap: 10, marginTop: 12, position: 'sticky', bottom: 0, background: 'var(--surface)', paddingTop: 6 }}>
            <button className="btn btn-ghost" style={{ flex: 1 }} onClick={() => setStage('intake')}>Back</button>
            <button className="btn btn-primary" style={{ flex: 1.6, opacity: saving ? 0.6 : 1 }} disabled={!kept.length || saving} onClick={save}><Icon name="check" size={17} color="#fff" sw={2.6} /> {saving ? 'Saving…' : ('Save ' + kept.length + ' result' + (kept.length === 1 ? '' : 's'))}</button>
          </div>
        </div>
      )}

      {stage === 'saved' && savedInfo && (
        <div style={{ textAlign: 'center', padding: '20px 0 8px' }}>
          <div style={{ width: 64, height: 64, margin: '0 auto 16px', borderRadius: 22, background: 'var(--green-wash)', display: 'grid', placeItems: 'center' }}><Icon name="check" size={32} color="var(--success)" sw={2.6} /></div>
          <div className="h2" style={{ fontSize: 18 }}>{savedInfo.count} result{savedInfo.count === 1 ? '' : 's'} saved</div>
          <div className="body" style={{ fontSize: 13, marginTop: 5, maxWidth: 270, margin: '5px auto 0' }}>Grouped into {savedInfo.dates} visit{savedInfo.dates === 1 ? '' : 's'} · average read confidence {savedInfo.avgConf}%. Your trends and history have been updated.</div>
          <div className="row" style={{ gap: 7, justifyContent: 'center', marginTop: 13 }}>
            <Icon name={savedInfo.kept ? 'archive' : 'shield'} size={13} color={savedInfo.kept ? 'var(--orange)' : 'var(--success)'} />
            <span className="muted" style={{ fontSize: 11.5, fontWeight: 700, color: savedInfo.kept ? 'var(--ink-3)' : 'var(--success)' }}>{savedInfo.kept ? 'Original report kept in your records' : 'Original discarded — only the values were saved'}</span>
          </div>
          <button className="btn btn-primary" style={{ width: '100%', marginTop: 20 }} onClick={close}>Done</button>
        </div>
      )}
    </MSheet>
  );
}

Object.assign(window, { LabImportSheet, ConfBar, ResultRow, RemapPicker, ImportHighlights });
