// Human review: one page per uploaded document, source PDF beside every extracted
// field.
//
// The older review surface (ManualMetadataReviewV2 in app.jsx) shows only the
// findings the metadata judge flagged, as a flat list, with no sight of the document
// they came from - a handler correcting a policy number has to open the PDF
// separately and find the line themselves. This page pairs the two: the real
// uploaded page on the left (rendered server-side by langgraph_app/review_api.py,
// which also reports where each value is printed), every extracted field editable on
// the right, and a click on either side highlighting the other.
//
// Edits are written back through POST /v1/medical/consensus/metadata-manual-review -
// the same endpoint the flagged-findings panel uses - so both surfaces land in the
// case manifest identically.

const { Button: DfrButton, IconButton: DfrIconButton, StatusPill: DfrStatusPill, Icon: DfrIcon } =
  window.EasyClaimDesignSystem_b5ac63;

const DFR_API_BASE = location.port === '5500' ? 'http://localhost:8000' : '';

// Zoom steps rather than free scrolling: every step is a fresh server render, and a
// continuous slider would fire a render per pixel of travel.
const DFR_ZOOMS = [0.75, 1, 1.25, 1.5, 2, 2.5, 3];
const DFR_DEFAULT_ZOOM_INDEX = 3;

const DFR_FILTERS = ['All', 'Needs review', 'Empty', 'Edited', 'Verified', 'Not located'];

function dfrToken() {
  return sessionStorage.getItem(window.EASYCLAIM_SESSION_TOKEN_KEY) || '';
}

async function dfrFetch(path, options) {
  const response = await fetch(`${DFR_API_BASE}${path}`, {
    ...(options || {}),
    headers: { Authorization: `Bearer ${dfrToken()}`, ...((options || {}).headers || {}) },
  });
  if (!response.ok) {
    let detail = '';
    try {
      const body = await response.json();
      detail = typeof body.detail === 'string' ? body.detail : JSON.stringify(body.detail || body);
    } catch (err) {
      detail = await response.text().catch(() => '');
    }
    throw new Error(detail || `Request failed (${response.status})`);
  }
  return response;
}

function dfrQuery(params) {
  return Object.entries(params)
    .filter(([, value]) => value !== undefined && value !== null && value !== '')
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
    .join('&');
}

// The page endpoint needs a bearer token, so the image cannot be an ordinary <img
// src> pointing at it - it is fetched and handed to the <img> as an object URL.
// Revoking is left to the cache eviction below so a page turned back to is instant.
function useDfrPageImage(caseId, document, page, zoom, rotate) {
  const [state, setState] = React.useState({ url: '', loading: false, error: '' });
  const cacheRef = React.useRef(new Map());

  React.useEffect(() => {
    if (!caseId || !document || !page) return undefined;
    const key = `${document}|${page}|${zoom}|${rotate}`;
    const cached = cacheRef.current.get(key);
    if (cached) {
      setState({ url: cached, loading: false, error: '' });
      return undefined;
    }
    let cancelled = false;
    setState((current) => ({ url: current.url, loading: true, error: '' }));
    dfrFetch(`/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review/page?${dfrQuery({
      document, page, scale: zoom, rotate,
    })}`)
      .then((response) => response.blob())
      .then((blob) => {
        if (cancelled) return;
        const url = URL.createObjectURL(blob);
        const cache = cacheRef.current;
        cache.set(key, url);
        // Keep a working set around the current page; drop the oldest beyond it.
        while (cache.size > 12) {
          const oldest = cache.keys().next().value;
          URL.revokeObjectURL(cache.get(oldest));
          cache.delete(oldest);
        }
        setState({ url, loading: false, error: '' });
      })
      .catch((err) => {
        if (!cancelled) setState({ url: '', loading: false, error: err.message });
      });
    return () => { cancelled = true; };
  }, [caseId, document, page, zoom, rotate]);

  React.useEffect(() => {
    const cache = cacheRef.current;
    return () => {
      cache.forEach((url) => URL.revokeObjectURL(url));
      cache.clear();
    };
  }, []);

  return state;
}

// Source boxes arrive in the unrotated page's 0-1000 space; the image on screen is
// the rotated render, so a viewer who rotated the page still needs the box over the
// same words.
function dfrRotateBox(box, rotate) {
  const [x1, y1, x2, y2] = box;
  const turn = ((rotate % 360) + 360) % 360;
  if (turn === 90) return [1000 - y2, x1, 1000 - y1, x2];
  if (turn === 180) return [1000 - x2, 1000 - y2, 1000 - x1, 1000 - y1];
  if (turn === 270) return [y1, 1000 - x2, y2, 1000 - x1];
  return [x1, y1, x2, y2];
}

function dfrIsBlank(value) {
  return value === null || value === undefined || String(value).trim() === '';
}

// Field state drives the dot colour, the filter chips and the progress bar, in that
// order of precedence: an explicit tick beats everything, then what the judge said,
// then whether the extractor found anything at all.
//
// A blank field is deliberately NOT an error. The extraction schema covers every
// document type the pipeline accepts - police reports, bills, post mortems, salary
// slips - and any one document fills only its own sections, so most fields on any
// given page are legitimately empty. Red is reserved for a blank the judge itself
// called out as a fact the document does state.
function dfrStatusOf(field, entry) {
  if (entry.verified) return 'ok';
  if (field.flagged) return field.correction_type === 'missing' ? 'missing' : 'low';
  if (entry.edited) return 'edited';
  if (dfrIsBlank(entry.value)) return 'empty';
  return 'pending';
}

// A field only needs a handler's decision if it holds something, or if the judge
// raised it. Counting the empty remainder would put 100% out of reach on every case.
function dfrNeedsDecision(field, entry) {
  return field.flagged || !dfrIsBlank(entry.value);
}

const DFR_STATUS_COLOURS = {
  ok: { dot: 'var(--green-500)', bg: 'var(--status-success-bg)', fg: 'var(--status-success-fg)' },
  low: { dot: 'var(--amber-500)', bg: 'var(--status-warning-bg)', fg: 'var(--status-warning-fg)' },
  missing: { dot: 'var(--red-500)', bg: 'var(--status-danger-bg)', fg: 'var(--status-danger-fg)' },
  edited: { dot: 'var(--indigo-500)', bg: 'var(--status-info-bg)', fg: 'var(--status-info-fg)' },
  empty: { dot: 'var(--slate-200)', bg: 'var(--bg-surface-sunken)', fg: 'var(--text-tertiary)' },
  pending: { dot: 'var(--slate-300)', bg: 'var(--status-pending-bg)', fg: 'var(--status-pending-fg)' },
};

// A handler types into a text box; the manifest expects the type the extractor wrote.
// Retyping `18240.00` into a numeric field must not turn it into a string, and
// clearing a field must not turn a null into `""`.
function dfrCoerce(text, original) {
  const trimmed = String(text);
  if (trimmed.trim() === '') return original === null || original === undefined ? null : '';
  if (typeof original === 'number') {
    const parsed = Number(trimmed);
    return Number.isFinite(parsed) ? parsed : trimmed;
  }
  if (typeof original === 'boolean') {
    if (/^(true|yes)$/i.test(trimmed.trim())) return true;
    if (/^(false|no)$/i.test(trimmed.trim())) return false;
  }
  return trimmed;
}

function dfrDisplay(value) {
  if (value === null || value === undefined) return '';
  if (typeof value === 'boolean') return value ? 'true' : 'false';
  return String(value);
}

const DFR_SOURCE_RECORDS = {
  certificate: {
    label: 'Medical certificate', path: 'medical_leave_records[]', type: 'Medical Certificate',
    fields: ['certificate_number', 'claimant_name', 'mc_from', 'mc_to', 'issued_by', 'certificate_date', 'days_as_stated', 'notes'],
  },
  expense: {
    label: 'Receipt or bill', path: 'expense_records[]', type: 'Receipt',
    fields: ['document_type', 'document_number', 'document_date', 'related_invoice_numbers', 'provider_name', 'billed_to', 'payer_name', 'patient_name', 'patient_identification_number', 'description', 'total_charges', 'patient_charge', 'patient_excess_amount', 'patient_excess_basis', 'amount_paid', 'balance_due', 'payment_status'],
  },
  clinical: {
    label: 'Clinical event', path: 'clinical_events[]', type: 'Medical Report',
    fields: ['claimant_name', 'claimant_identification_number', 'date', 'event', 'hospital_name'],
  },
  specialist: {
    label: 'Specialist report', path: 'specialist_reports[]', type: 'Specialist Report',
    fields: ['claimant_name', 'claimant_identification_number', 'examining_side', 'specialist_name', 'specialist_discipline', 'hospital_or_clinic_name', 'examination_date', 'report_date', 'specialist_findings'],
  },
  defendant: {
    label: 'Insured or defendant', path: 'defendants[]',
    fields: ['party_id', 'name', 'identification_number', 'vehicle_number', 'address', 'particulars', 'role_or_relationship'],
  },
  liability: {
    label: 'Liability opinion allocation', path: 'solicitor_proposed_liability[]',
    fields: ['proposed_percentage_or_share', 'reasoning'],
  },
};

function dfrSourceRecordFields(document, kind, values, sourcePage, pageCount, sourceType = '') {
  const spec = DFR_SOURCE_RECORDS[kind];
  const source = Number(sourcePage);
  if (!spec || !Number.isInteger(source) || source < 1 || source > pageCount) {
    throw new Error('Choose a record type and a source page within this document.');
  }
  const value = Object.fromEntries(spec.fields.map((key) => [key, String(values[key] || '').trim() || null]));
  if (kind === 'expense') value.related_invoice_numbers = [...new Set(String(values.related_invoice_numbers || '').split(/[,;\n]/).map((number) => number.trim()).filter(Boolean))];
  if (kind === 'certificate' && (!value.claimant_name || !value.mc_from || !value.mc_to || !value.issued_by)) {
    throw new Error('Enter the patient, both certificate dates and the issuing hospital or clinic.');
  }
  if (kind === 'expense' && (!value.document_type || !value.provider_name || !value.description)) {
    throw new Error('Enter the original receipt/bill type, issuer and description.');
  }
  if (kind === 'clinical' && (!value.claimant_name || !value.date || !value.event || !value.hospital_name)) {
    throw new Error('Enter the patient, event date, event description and hospital or clinic from the source page.');
  }
  if (kind === 'specialist' && (!value.claimant_name || !value.specialist_name || !value.specialist_findings || !/^(plaintiff|defendant)$/i.test(value.examining_side || ''))) {
    throw new Error('Enter the patient, specialist, findings and commissioning side (Plaintiff or Defendant). Leave the examination date blank if it is not stated.');
  }
  if (kind === 'defendant' && (!value.party_id || !value.name)) {
    throw new Error('Enter a new document-local party reference and the person’s name.');
  }
  if (kind === 'liability' && (!value.proposed_percentage_or_share || !value.reasoning)) {
    throw new Error('Copy the allocation and its attributed reasoning from the source opinion.');
  }
  const fields = [{ document, field_path: spec.path, action: 'update', value, source_page: source }];
  const clinicalContainer = /(?:medical|specialist)\s+report/i.test(sourceType);
  if (spec.type) fields.push({
    document, field_path: 'document_inventory[]', action: 'update', source_page: source,
    value: { document_type: kind === 'expense' ? value.document_type : kind === 'specialist' ? `${spec.type} - ${value.examining_side}` : clinicalContainer && ['certificate', 'clinical'].includes(kind) ? sourceType : spec.type, document_title: value.certificate_number || value.document_number || spec.label, page_from: source, page_to: source },
  });
  return fields;
}

function DfrSplitter({ dragging, stacked, size, onPointerDown, onDoubleClick, onKeyDown }) {
  return (
    <div
      role="separator"
      tabIndex={0}
      aria-label="Resize document and fields"
      aria-orientation={stacked ? 'horizontal' : 'vertical'}
      aria-valuenow={size}
      onPointerDown={onPointerDown}
      onDoubleClick={onDoubleClick}
      onKeyDown={onKeyDown}
      title="Drag to resize · double-click to reset · use arrow keys"
      style={{
        width: 14, flexShrink: 0, cursor: stacked ? 'row-resize' : 'col-resize', display: 'flex',
        alignItems: 'center', justifyContent: 'center',
        background: dragging ? 'var(--teal-50)' : 'var(--bg-surface)',
        borderLeft: '1px solid var(--border-subtle)',
        borderRight: `1px solid ${dragging ? 'var(--teal-500)' : 'var(--border-subtle)'}`,
      }}
    >
      <span style={{ width: 3, height: 30, borderRadius: 2, background: dragging ? 'var(--teal-500)' : 'var(--slate-200)' }} />
    </div>
  );
}

function dfrDocumentKey(doc) {
  return doc.source_document_id || doc.filename;
}

function DfrDocumentTabs({ documents, activeName, onSelect, disabled }) {
  return (
    <div className="dfr-document-tabs" aria-label="Documents" style={{ display: 'flex', gap: 2, padding: '0 20px', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-subtle)', overflowX: 'auto', overflowY: 'hidden', flexShrink: 0 }}>
      {documents.map((doc) => {
        const on = dfrDocumentKey(doc) === activeName;
        return (
          <button
            key={dfrDocumentKey(doc)}
            disabled={disabled}
            onClick={() => onSelect(doc)}
            title={doc.source_filename}
            style={{
              display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 1,
              padding: '11px 16px 9px', border: 'none', background: 'transparent', cursor: 'pointer',
              textAlign: 'left', whiteSpace: 'nowrap', marginBottom: -1,
              color: on ? 'var(--text-primary)' : 'var(--text-secondary)',
              borderBottom: `2px solid ${on ? 'var(--brand-primary)' : 'transparent'}`,
            }}
          >
            <span style={{ display: 'flex', alignItems: 'center', gap: 6, font: 'var(--text-label-md)' }}>
              <span
                title={doc.reviewed ? 'Signed off' : 'Not reviewed yet'}
                style={{
                  width: 14, height: 14, borderRadius: '50%', flexShrink: 0,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  font: 'var(--text-caption)', lineHeight: '14px',
                  background: doc.reviewed ? 'var(--teal-600)' : 'transparent',
                  border: doc.reviewed ? 'none' : '1px dashed var(--border-strong)',
                  color: '#fff',
                }}
              >
                {doc.reviewed ? '✓' : ''}
              </span>
              {doc.document_type || 'Document'}
            </span>
            <span style={{ font: 'var(--text-caption)', color: 'var(--text-tertiary)' }}>
              {doc.source_filename}{doc.page_count ? ` · ${doc.page_count} pp` : ''}
            </span>
          </button>
        );
      })}
    </div>
  );
}

function DfrViewerToolbar({
  page, pageCount, onPrev, onNext, zoom, onZoomIn, onZoomOut,
  thumbsOn, onToggleThumbs, activeLabel, notLocated, fitWidth,
}) {
  return (
    <div className="dfr-viewer-toolbar" data-fit-width={fitWidth} style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, padding: '8px 20px', borderBottom: '1px solid var(--border-subtle)', background: 'var(--bg-surface)', flexShrink: 0 }}>
      <span className="dfr-thumbnails-toggle">
        <DfrIconButton
          icon="panel-left"
          label="Toggle page thumbnails"
          variant="secondary"
          onClick={onToggleThumbs}
          style={thumbsOn ? { background: 'var(--teal-50)', borderColor: 'var(--teal-500)', color: 'var(--teal-700)' } : undefined}
        />
      </span>
      <span style={{ width: 1, height: 20, background: 'var(--border-subtle)', margin: '0 6px' }} />
      <DfrIconButton icon="chevron-left" label="Previous page" variant="secondary" onClick={onPrev} />
      <span style={{ font: 'var(--text-mono-sm)', color: 'var(--text-secondary)', minWidth: 92, textAlign: 'center' }}>
        Page {page} / {pageCount || '—'}
      </span>
      <DfrIconButton icon="chevron-right" label="Next page" variant="secondary" onClick={onNext} />
      <span style={{ width: 1, height: 20, background: 'var(--border-subtle)', margin: '0 6px' }} />
      <DfrIconButton icon="zoom-out" label="Zoom out" variant="secondary" onClick={onZoomOut} />
      <span className="dfr-zoom-level" style={{ font: 'var(--text-mono-sm)', color: 'var(--text-secondary)', minWidth: 52, textAlign: 'center' }}>{Math.round(zoom * 100)}%</span>
      <DfrIconButton icon="zoom-in" label="Zoom in" variant="secondary" onClick={onZoomIn} />
      <span style={{ flex: 1 }} />
      {activeLabel && (
        <span style={{
          display: 'inline-flex', alignItems: 'center', gap: 7, padding: '4px 10px',
          borderRadius: 'var(--radius-pill)',
          background: notLocated ? 'var(--status-warning-bg)' : 'var(--teal-50)',
          border: `1px solid ${notLocated ? 'var(--status-warning-fg)' : 'var(--teal-200)'}`,
        }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: notLocated ? 'var(--amber-500)' : 'var(--teal-500)' }} />
          <span style={{ font: 'var(--text-label-sm)', color: notLocated ? 'var(--status-warning-fg)' : 'var(--teal-700)' }}>
            {notLocated ? `Not found on the page: ${activeLabel}` : `Showing: ${activeLabel}`}
          </span>
        </span>
      )}
    </div>
  );
}

function DfrThumbnails({ caseId, document, pageCount, page, onSelect }) {
  return (
    <div className="dfr-thumbnails" style={{ width: 112, flexShrink: 0, borderRight: '1px solid var(--border-subtle)', background: 'var(--bg-surface)', overflowY: 'auto', padding: '14px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>
      {Array.from({ length: pageCount }, (_, index) => index + 1).map((number) => (
        <DfrThumbnail
          key={number}
          caseId={caseId}
          document={document}
          page={number}
          active={number === page}
          onSelect={() => onSelect(number)}
        />
      ))}
    </div>
  );
}

function DfrThumbnail({ caseId, document, page, active, onSelect }) {
  // Thumbnails render at a fixed small scale, so they are their own cache entries
  // and never evict the full-size page the viewer is looking at.
  const { url, loading } = useDfrPageImage(caseId, document, page, 0.75, 0);
  return (
    <button
      onClick={onSelect}
      style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5, border: 'none', background: 'transparent', cursor: 'pointer', padding: 0 }}
    >
      <span style={{
        width: 66, height: 88, background: '#fff', borderRadius: 3, overflow: 'hidden',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        border: `1px solid ${active ? 'var(--teal-500)' : 'var(--border-default)'}`,
        boxShadow: active ? 'var(--shadow-focus-ring)' : 'var(--shadow-xs)',
      }}>
        {url ? <img src={url} alt={`Page ${page}`} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
          : <span style={{ font: 'var(--text-caption)', color: 'var(--text-tertiary)' }}>{loading ? '…' : '—'}</span>}
      </span>
      <span style={{ font: 'var(--text-mono-sm)', color: active ? 'var(--teal-700)' : 'var(--text-tertiary)' }}>{page}</span>
    </button>
  );
}

function DfrPageCanvas({ caseId, document, page, zoom, rotate, boxes, onDismiss, fitWidth }) {
  const { url, loading, error } = useDfrPageImage(caseId, document, page, zoom, rotate);
  return (
    <div className="dfr-page-canvas" style={{ flex: 1, minWidth: 0, overflow: 'auto', padding: 28, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', background: 'var(--bg-surface-sunken)' }}>
      {error ? (
        <div style={{ maxWidth: 420, padding: 18, borderRadius: 'var(--radius-md)', background: 'var(--status-danger-bg)', color: 'var(--status-danger-fg)', font: 'var(--text-body-sm)' }}>
          The source page could not be loaded: {error}
        </div>
      ) : (
        <div
          className="dfr-page-sheet"
          data-fit-width={fitWidth}
          onClick={onDismiss}
          style={{ position: 'relative', display: 'inline-block', background: '#fff', boxShadow: 'var(--shadow-md)', border: '1px solid var(--border-default)', minWidth: 200, minHeight: 260 }}
        >
          {url && <img src={url} alt={`Page ${page}`} style={{ display: 'block', maxWidth: 'none' }} />}
          {loading && (
            <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(255,255,255,.6)', font: 'var(--text-body-sm)', color: 'var(--text-secondary)' }}>
              Rendering page {page}…
            </div>
          )}
          {boxes.map((box, index) => {
            const [x1, y1, x2, y2] = dfrRotateBox(box, rotate);
            return (
              <span
                key={index}
                style={{
                  position: 'absolute',
                  left: `${x1 / 10}%`, top: `${y1 / 10}%`,
                  width: `${(x2 - x1) / 10}%`, height: `${(y2 - y1) / 10}%`,
                  background: 'rgba(10,132,132,.22)',
                  boxShadow: '0 0 0 2px var(--teal-500)',
                  borderRadius: 2,
                  pointerEvents: 'none',
                }}
              />
            );
          })}
        </div>
      )}
    </div>
  );
}

function DfrFieldCard({ field, entry, active, onSelect, onChange, onVerify, onClear, onUnavailable, disabled }) {
  const status = dfrStatusOf(field, entry);
  const colour = DFR_STATUS_COLOURS[status];
  const note = entry.action === 'unavailable' ? 'Marked unavailable'
    : entry.edited ? 'Edited by you'
      : field.flag_reason || (dfrIsBlank(entry.value) ? 'Not extracted' : '');
  const referenceOptions = Array.isArray(field.reference_options)
    ? [...field.reference_options]
    : null;
  if (referenceOptions && !dfrIsBlank(entry.value)
      && !referenceOptions.some((option) => option.value === entry.value)) {
    referenceOptions.push({ value: entry.value, label: String(entry.value) + ' (unresolved)' });
  }
  const inputStyle = {
    width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 'var(--radius-sm)',
    border: '1px solid ' + (status === 'missing' ? 'var(--red-500)' : 'var(--border-default)'),
    background: entry.action === 'unavailable' ? 'var(--bg-surface-sunken)' : 'var(--bg-surface)',
    color: 'var(--text-primary)', outline: 'none', font: 'var(--text-body-md)', fontFamily: 'var(--font-body)',
  };
  return (
    <div
      className="dfr-field-card"
      onClick={onSelect}
      style={{
        padding: '11px 12px 10px', borderRadius: 'var(--radius-md)', cursor: 'pointer',
        border: `1px solid ${active ? 'var(--teal-500)' : 'var(--border-subtle)'}`,
        background: active ? 'var(--teal-50)' : 'var(--bg-surface)',
        boxShadow: active ? 'var(--shadow-focus-ring)' : 'none',
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 7 }}>
        <span style={{ width: 7, height: 7, borderRadius: '50%', background: colour.dot, flexShrink: 0 }} />
        <span style={{ font: 'var(--text-label-md)', color: 'var(--text-secondary)' }}>{field.label}</span>
        <span style={{ flex: 1 }} />
        {field.located && (
          <span title={`Found on page ${field.page}`} style={{ font: 'var(--text-mono-sm)', padding: '2px 7px', borderRadius: 'var(--radius-pill)', background: 'var(--bg-surface-sunken)', color: 'var(--text-tertiary)' }}>
            p.{field.page}
          </span>
        )}
        <button
          onClick={(event) => { event.stopPropagation(); onVerify(); }}
          disabled={disabled}
          aria-label={`${entry.verified ? 'Unmark' : 'Mark'} ${field.label} verified`}
          title={entry.verified ? 'Unmark verified' : 'Mark verified'}
          style={{
            width: 24, height: 24, flexShrink: 0, borderRadius: 'var(--radius-sm)', cursor: 'pointer',
            border: `1px solid ${entry.verified ? 'var(--teal-600)' : 'var(--border-default)'}`,
            background: entry.verified ? 'var(--teal-600)' : 'var(--bg-surface)',
            color: entry.verified ? '#fff' : 'var(--slate-300)', font: 'var(--text-label-sm)', lineHeight: '22px', padding: 0,
          }}
        >
          ✓
        </button>
      </div>
      {referenceOptions ? (
        <select
          value={dfrDisplay(entry.value)}
          onChange={(event) => onChange(event.target.value)}
          onFocus={onSelect}
          disabled={disabled || entry.action === 'unavailable'}
          style={inputStyle}
        >
          <option value="">Select party</option>
          {referenceOptions.map((option) => (
            <option key={option.value} value={option.value}>{option.label}</option>
          ))}
        </select>
      ) : (
        <input
          value={dfrDisplay(entry.value)}
          onChange={(event) => onChange(event.target.value)}
          onFocus={onSelect}
          disabled={disabled || entry.action === 'unavailable'}
          placeholder={dfrIsBlank(entry.value) ? 'Enter value' : ''}
          style={inputStyle}
        />
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 6, flexWrap: 'wrap' }}>
        <span className="dfr-field-path" title={field.field_path} style={{ font: 'var(--text-caption)', color: 'var(--text-tertiary)' }}>{field.field_path}</span>
        {note && (
          <span style={{
            font: 'var(--text-caption)', padding: '1px 7px', borderRadius: 'var(--radius-pill)',
            background: entry.edited ? 'var(--status-info-bg)' : colour.bg,
            color: entry.edited ? 'var(--status-info-fg)' : colour.fg,
          }}>
            {note}
          </span>
        )}
        {active && (
          <React.Fragment>
            <span style={{ flex: 1 }} />
            <button disabled={disabled} onClick={(event) => { event.stopPropagation(); onClear(); }} style={dfrQuietButton}>Clear</button>
            <button disabled={disabled} onClick={(event) => { event.stopPropagation(); onUnavailable(); }} style={dfrQuietButton}>Not in document</button>
          </React.Fragment>
        )}
      </div>
      {field.recommended_value !== null && field.recommended_value !== undefined && String(field.recommended_value) !== dfrDisplay(entry.value) && (
        <button
          disabled={disabled}
          onClick={(event) => { event.stopPropagation(); onChange(String(field.recommended_value)); }}
          style={{ ...dfrQuietButton, marginTop: 7, borderColor: 'var(--teal-500)', color: 'var(--teal-700)' }}
        >
          Use suggested: {String(field.recommended_value).slice(0, 60)}
        </button>
      )}
    </div>
  );
}

const dfrQuietButton = {
  padding: '3px 9px', borderRadius: 'var(--radius-pill)', cursor: 'pointer',
  font: 'var(--text-label-sm)', border: '1px solid var(--border-default)',
  background: 'var(--bg-surface)', color: 'var(--text-secondary)',
};

// Fold a successful write back into local state: the saved value becomes the new
// baseline, so the field stops counting as an unsaved edit and shows as verified.
function dfrCommitSaved(entries, payload, failedPaths) {
  const next = { ...entries };
  payload.forEach((item) => {
    if (failedPaths.has(item.field_path)) return;
    const value = item.action === 'unavailable' ? next[item.field_path].original
      : item.action === 'remove' ? null : item.value;
    next[item.field_path] = { ...next[item.field_path], value, original: value, edited: false, verified: true, actionSaved: true };
  });
  return next;
}

function dfrCanRetryMedicalAssessment(caseReview) {
  return Boolean(caseReview && caseReview.accepted);
}

function DocumentFieldReview({ caseId, policyNumber, initialDocument, onBack, onSaved, onAccepted, onSkip, resumeError, beforeLeaveRef, reviewAvatar }) {
  const [overview, setOverview] = React.useState(null);
  const [activeDoc, setActiveDoc] = React.useState(initialDocument || '');
  const [detail, setDetail] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState('');

  const [entries, setEntries] = React.useState({});
  const [activePath, setActivePath] = React.useState('');
  const [filter, setFilter] = React.useState('All');
  const [collapsed, setCollapsed] = React.useState({});

  const [page, setPage] = React.useState(1);
  const [zoomIndex, setZoomIndex] = React.useState(DFR_DEFAULT_ZOOM_INDEX);
  const [rotate] = React.useState(0);
  const [thumbsOn, setThumbsOn] = React.useState(true);

  const [panelWidth, setPanelWidth] = React.useState(600);
  const [dragging, setDragging] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [accepting, setAccepting] = React.useState(false);
  const [saveResult, setSaveResult] = React.useState(null);
  // Case-level sign-off progress, kept beside `overview` because completing a
  // document updates it without re-fetching the whole case.
  const [caseReview, setCaseReview] = React.useState(null);
  const [recordKind, setRecordKind] = React.useState('');
  const [recordValues, setRecordValues] = React.useState({});
  const [recordPage, setRecordPage] = React.useState('');
  const [fitWidth, setFitWidth] = React.useState(true);
  const [panelPercent, setPanelPercent] = React.useState(70);
  const [stacked, setStacked] = React.useState((window.innerWidth || 1440) <= 1172);
  const actionRef = React.useRef(false);

  // --- data ---------------------------------------------------------------
  React.useEffect(() => {
    if (!caseId) {
      setError('No case was selected for review.');
      setLoading(false);
      return undefined;
    }
    let cancelled = false;
    setLoading(true);
    dfrFetch(`/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review`)
      .then((response) => response.json())
      .then((body) => {
        if (cancelled) return;
        setOverview(body);
        setCaseReview(body.review || null);
        const first = (body.documents || []).find((doc) => dfrDocumentKey(doc) === initialDocument || doc.filename === initialDocument)
          || (body.documents || []).find((doc) => doc.has_metadata)
          || (body.documents || [])[0];
        setActiveDoc(first ? dfrDocumentKey(first) : '');
        if (!first) setLoading(false);
      })
      .catch((err) => { if (!cancelled) { setError(err.message); setLoading(false); } });
    return () => { cancelled = true; };
  }, [caseId, initialDocument]);

  React.useEffect(() => {
    if (!activeDoc) return undefined;
    let cancelled = false;
    setLoading(true);
    setError('');
    setDetail(null);
    dfrFetch(`/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review/fields?${dfrQuery({ document: activeDoc })}`)
      .then((response) => response.json())
      .then((body) => {
        if (cancelled) return;
        setDetail(body);
        const next = {};
        (body.groups || []).forEach((group) => group.fields.forEach((field) => {
          next[field.field_path] = {
            value: field.value,
            original: field.value,
            edited: false,
            // A field a handler already saved on a previous visit comes back ticked,
            // so re-opening the page does not ask for the same decisions again.
            verified: Boolean(field.reviewed_action),
            action: field.reviewed_action === 'unavailable' ? 'unavailable' : null,
            actionSaved: true,
          };
        }));
        setEntries(next);
        // A section the extractor filled nothing in starts folded. On a police
        // report that is most of the medical and quantum schema, and leaving it all
        // open buries the handful of sections that actually carry the document.
        setCollapsed(Object.fromEntries((body.groups || [])
          .filter((group) => group.fields.every((field) => !field.flagged && dfrIsBlank(field.value)))
          .map((group) => [group.key, true])));
        setActivePath('');
        setPage(1);
        setSaveResult(null);
        setRecordKind('');
        setRecordValues({});
        setLoading(false);
      })
      .catch((err) => { if (!cancelled) { setError(err.message); setLoading(false); } });
    return () => { cancelled = true; };
  }, [caseId, activeDoc]);

  // --- splitter -----------------------------------------------------------
  const clampWidth = React.useCallback((width) => {
    const reviewWidth = document.querySelector('.dfr-root')?.clientWidth || (window.innerWidth || 1440) - 72;
    return Math.round(Math.max(360, Math.min(width, Math.max(360, reviewWidth - 424))));
  }, []);

  React.useEffect(() => {
    const onResize = () => {
      const reviewWidth = document.querySelector('.dfr-root')?.clientWidth || (window.innerWidth || 1440) - 72;
      setStacked(reviewWidth <= 1100);
      setPanelWidth((width) => clampWidth(width));
    };
    window.addEventListener('resize', onResize);
    onResize();
    return () => window.removeEventListener('resize', onResize);
  }, [clampWidth]);

  function startDrag(event) {
    event.preventDefault();
    const main = event.currentTarget.closest('.dfr-main');
    const startX = event.clientX;
    const startY = event.clientY;
    const startWidth = panelWidth;
    const startPercent = panelPercent;
    const move = (moveEvent) => {
      if (moveEvent.pointerId !== event.pointerId) return;
      if (stacked) {
        const delta = ((moveEvent.clientY - startY) / (main?.clientHeight || 1)) * 100;
        setPanelPercent(Math.max(55, Math.min(78, startPercent + delta)));
      } else {
        setPanelWidth(clampWidth(startWidth + (startX - moveEvent.clientX)));
      }
    };
    const up = () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('pointercancel', up);
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
      setDragging(false);
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    window.addEventListener('pointercancel', up);
    document.body.style.cursor = stacked ? 'row-resize' : 'col-resize';
    document.body.style.userSelect = 'none';
    setDragging(true);
  }

  function resizeWithKeyboard(event) {
    const direction = stacked
      ? (event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0)
      : (event.key === 'ArrowLeft' ? 1 : event.key === 'ArrowRight' ? -1 : 0);
    if (!direction) return;
    event.preventDefault();
    if (stacked) setPanelPercent((percent) => Math.max(55, Math.min(78, percent + direction * 5)));
    else setPanelWidth((width) => clampWidth(width + direction * 24));
  }

  // --- derived ------------------------------------------------------------
  const documents = (overview && overview.documents) || [];
  const activeDocMeta = documents.find((doc) => dfrDocumentKey(doc) === activeDoc) || null;
  const pageCount = (detail && detail.document.page_count) || (activeDocMeta && activeDocMeta.page_count) || 0;
  const allFields = React.useMemo(
    () => ((detail && detail.groups) || []).flatMap((group) => group.fields),
    [detail],
  );
  const fieldByPath = React.useMemo(() => {
    const map = {};
    allFields.forEach((field) => { map[field.field_path] = field; });
    return map;
  }, [allFields]);

  const activeReviewed = Boolean(activeDocMeta && activeDocMeta.reviewed);
  const nextUnreviewed = documents.find((doc) => !doc.reviewed && dfrDocumentKey(doc) !== activeDoc) || null;

  const activeField = fieldByPath[activePath] || null;
  const activeBoxes = activeField && activeField.page === page ? activeField.boxes || [] : [];

  const totals = React.useMemo(() => {
    let verified = 0;
    let flagged = 0;
    let edited = 0;
    let decidable = 0;
    let empty = 0;
    allFields.forEach((field) => {
      const entry = entries[field.field_path] || {};
      const status = dfrStatusOf(field, entry);
      if (dfrNeedsDecision(field, entry)) decidable += 1;
      if (entry.verified) verified += 1;
      if (entry.edited) edited += 1;
      if (status === 'low' || status === 'missing') flagged += 1;
      if (status === 'empty') empty += 1;
    });
    return { verified, flagged, edited, empty, decidable, total: allFields.length };
  }, [allFields, entries]);

  function passesFilter(field) {
    const entry = entries[field.field_path] || {};
    const status = dfrStatusOf(field, entry);
    if (filter === 'Needs review') return status === 'low' || status === 'missing';
    if (filter === 'Empty') return status === 'empty';
    if (filter === 'Edited') return Boolean(entry.edited);
    if (filter === 'Verified') return Boolean(entry.verified);
    if (filter === 'Not located') return !field.located;
    return true;
  }

  function selectField(field) {
    setActivePath(field.field_path);
    if (field.page && field.page !== page) setPage(field.page);
  }

  function patchEntry(path, patch) {
    if (actionRef.current) return;
    setEntries((current) => ({ ...current, [path]: { ...current[path], ...patch } }));
  }

  function editValue(field, text) {
    const entry = entries[field.field_path] || {};
    const value = dfrCoerce(text, entry.original);
    patchEntry(field.field_path, {
      value,
      edited: JSON.stringify(value) !== JSON.stringify(entry.original),
      action: null,
      source_page: field.page || page,
    });
    setActivePath(field.field_path);
  }

  // Save edits before leaving this document or starting assessment. Signing off
  // remains an explicit action; navigation and skipping only persist corrections.
  async function savePendingEdits() {
    const payload = allFields
      .map((field) => {
        const entry = entries[field.field_path] || {};
        if (entry.action === 'unavailable' && !entry.actionSaved) {
          return { document: activeDoc, field_path: field.field_path, action: 'unavailable', value: null, source_page: entry.source_page || field.page || page };
        }
        if (!entry.edited) return null;
        return {
          document: activeDoc,
          field_path: field.field_path,
          action: dfrIsBlank(entry.value) && entry.original !== null ? 'remove' : 'update',
          value: entry.value,
          source_page: entry.source_page || field.page || page,
        };
      })
      .filter(Boolean);

    if (!payload.length) return 0;
    const response = await dfrFetch('/v1/medical/consensus/metadata-manual-review', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ case_id: caseId, fields: payload, completed_documents: [] }),
    });
    const body = await response.json();
    const failed = body.failed_fields || [];
    if (failed.length) {
      setEntries((current) => dfrCommitSaved(current, payload, new Set(failed.map((item) => item.field_path))));
      if (onSaved) onSaved(body);
      throw new Error(`${(body.updated_fields || []).length} field(s) saved, but ${failed.length} could not be applied: ${failed.map((item) => `${item.field_path} (${item.error})`).join('; ')}. Save the remaining corrections before continuing.`);
    }
    if (!body.success) throw new Error('Corrections could not be saved. Please retry before continuing.');
    setEntries((current) => dfrCommitSaved(current, payload, new Set()));
    if (onSaved) onSaved(body);
    return (body.updated_fields || []).length;
  }

  async function leaveDocument(nextDocument, navigate = onBack) {
    if (actionRef.current) return false;
    actionRef.current = true;
    setSaving(true);
    setSaveResult(null);
    try {
      await savePendingEdits();
      if (nextDocument && dfrDocumentKey(nextDocument) !== activeDoc) {
        setDetail(null);
        setLoading(true);
        setActiveDoc(dfrDocumentKey(nextDocument));
      } else if (!nextDocument && navigate) await navigate();
      return true;
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
      return false;
    } finally {
      actionRef.current = false;
      setSaving(false);
    }
  }

  async function saveSourceRecord() {
    if (actionRef.current) return;
    actionRef.current = true;
    setSaving(true);
    setSaveResult(null);
    try {
      const inventory = {};
      for (const group of detail.groups || []) for (const field of group.fields || []) {
        const match = field.field_path.match(/^document_inventory\[(\d+)\]\.(document_type|page_from|page_to)$/);
        if (match) (inventory[match[1]] ||= {})[match[2]] = field.value;
      }
      const types = [...new Set(Object.values(inventory).filter((item) => Number(item.page_from) <= Number(recordPage) && Number(item.page_to) >= Number(recordPage)).map((item) => item.document_type))];
      const sourceType = types.length === 1 ? types[0] : detail.document.document_type;
      const fields = dfrSourceRecordFields(activeDoc, recordKind, recordValues, recordPage, pageCount, sourceType);
      await savePendingEdits();
      const body = await dfrFetch('/v1/medical/consensus/metadata-manual-review', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ case_id: caseId, fields, completed_documents: [] }),
      }).then((response) => response.json());
      if (!body.success || (body.failed_fields || []).length) throw new Error((body.failed_fields || []).map((item) => item.error).join('; ') || 'The source record could not be saved.');
      const refreshed = await dfrFetch(`/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review/fields?${dfrQuery({ document: activeDoc })}`).then((response) => response.json());
      setDetail(refreshed);
      setEntries(Object.fromEntries((refreshed.groups || []).flatMap((group) => group.fields.map((field) => [field.field_path, {
        value: field.value, original: field.value, edited: false, verified: Boolean(field.reviewed_action), actionSaved: true,
      }]))));
      setRecordKind('');
      setRecordValues({});
      setSaveResult({ ok: true, message: 'Source record saved with its original page. Review the added fields before signing off.' });
      if (onSaved) onSaved(body);
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
    } finally {
      actionRef.current = false;
      setSaving(false);
    }
  }

  async function completeDocument() {
    if (actionRef.current) return;
    actionRef.current = true;
    setSaving(true);
    setSaveResult(null);
    try {
      const savedCount = await savePendingEdits();

      const completion = await dfrFetch(
        `/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review/complete-document`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ document: activeDoc }),
        },
      ).then((response) => response.json());

      setCaseReview(completion.review);
      setOverview((current) => (current ? {
        ...current,
        review: completion.review,
        documents: current.documents.map((doc) => (dfrDocumentKey(doc) === activeDoc
          ? { ...doc, reviewed: true } : doc)),
      } : current));
      setSaveResult({
        ok: true,
        message: savedCount
          ? `${savedCount} correction(s) saved and this document signed off.`
          : 'Document signed off with no corrections.',
      });
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
    } finally {
      actionRef.current = false;
      setSaving(false);
    }
  }

  async function acceptCase() {
    if (actionRef.current) return;
    actionRef.current = true;
    setAccepting(true);
    setSaveResult(null);
    try {
      await savePendingEdits();
      const body = await dfrFetch(
        `/v1/medical/consensus/cases/${encodeURIComponent(caseId)}/review/accept`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          // Sent so the server can refuse an accept for a case whose documents
          // changed while this page was open.
          body: JSON.stringify({ expected_documents: documents.map(dfrDocumentKey) }),
        },
      ).then((response) => response.json());
      setCaseReview(body.review);
      if (onAccepted) await onAccepted(body);
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
    } finally {
      actionRef.current = false;
      setAccepting(false);
    }
  }

  async function skipReview() {
    if (actionRef.current) return;
    actionRef.current = true;
    setAccepting(true);
    setSaveResult(null);
    try {
      await savePendingEdits();
      if (onSkip) await onSkip(caseId);
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
    } finally {
      actionRef.current = false;
      setAccepting(false);
    }
  }

  async function retryMedicalAssessment() {
    if (actionRef.current) return;
    actionRef.current = true;
    setAccepting(true);
    setSaveResult(null);
    try {
      await savePendingEdits();
      if (onAccepted) await onAccepted({ review: caseReview });
    } catch (err) {
      setSaveResult({ ok: false, message: err.message });
    } finally {
      actionRef.current = false;
      setAccepting(false);
    }
  }

  React.useEffect(() => {
    if (!beforeLeaveRef) return undefined;
    beforeLeaveRef.current = () => leaveDocument(null, () => {});
    return () => { beforeLeaveRef.current = null; };
  });

  React.useEffect(() => {
    if (!Object.values(entries).some((entry) => entry.edited || (entry.action === 'unavailable' && !entry.actionSaved))) return undefined;
    const preventUnload = (event) => { event.preventDefault(); event.returnValue = ''; };
    window.addEventListener('beforeunload', preventUnload);
    return () => window.removeEventListener('beforeunload', preventUnload);
  }, [entries]);

  // --- render -------------------------------------------------------------
  const zoom = DFR_ZOOMS[zoomIndex];
  const progress = totals.decidable ? Math.round((Math.min(totals.verified, totals.decidable) / totals.decidable) * 100) : 0;

  return (
    <div className="dfr-root" style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, background: 'var(--bg-page)' }}>
      <div className="dfr-case-bar" style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '10px 28px', background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-subtle)', flexShrink: 0 }}>
        <DfrButton variant="ghost" icon="chevron-left" onClick={() => leaveDocument()} disabled={saving || accepting}>Back</DfrButton>
        <span className="dfr-case-id" style={{ font: 'var(--text-mono-md)', color: 'var(--text-primary)' }} title={caseId}>{caseId}</span>
        <span className="dfr-case-divider" style={{ width: 1, height: 16, background: 'var(--border-subtle)' }} />
        <span className="dfr-case-meta" style={{ font: 'var(--text-body-sm)', color: 'var(--text-secondary)' }}>
          {[policyNumber || (overview && overview.policy_number), overview && overview.claim_category,
            documents.length ? `${documents.length} document${documents.length === 1 ? '' : 's'}` : null]
            .filter(Boolean).join(' · ')}
        </span>
        <span className="dfr-case-spacer" style={{ flex: 1 }} />
        {totals.flagged > 0 && (
          <span className="dfr-flagged-count"><DfrStatusPill status="warning">
            {totals.flagged} field{totals.flagged === 1 ? '' : 's'} flagged here
          </DfrStatusPill></span>
        )}
        {caseReview && (
          <DfrStatusPill status={caseReview.accepted ? 'success' : caseReview.all_documents_reviewed ? 'info' : 'pending'}>
            {caseReview.accepted
              ? 'Case accepted'
              : `${caseReview.reviewed_documents} of ${caseReview.total_documents} documents reviewed`}
          </DfrStatusPill>
        )}
        {reviewAvatar && <div className="dfr-case-avatar">{reviewAvatar}</div>}
      </div>

      <DfrDocumentTabs documents={documents} activeName={activeDoc} onSelect={leaveDocument} disabled={saving || accepting || loading} />

      <div className="dfr-main" style={{ flex: 1, display: 'flex', minHeight: 0, '--dfr-fields-height': `${panelPercent}%` }}>
        <section className="dfr-viewer" aria-label="Source document" style={{ flex: '1 1 auto', minWidth: 410, display: 'flex', flexDirection: 'column', background: 'var(--bg-surface-sunken)' }}>
          <DfrViewerToolbar
            page={page}
            pageCount={pageCount}
            onPrev={() => setPage((current) => Math.max(1, current - 1))}
            onNext={() => setPage((current) => Math.min(pageCount || current, current + 1))}
            zoom={zoom}
            onZoomIn={() => { setFitWidth(false); setZoomIndex((index) => Math.min(DFR_ZOOMS.length - 1, index + 1)); }}
            onZoomOut={() => { if (zoomIndex <= 1) setFitWidth(true); setZoomIndex((index) => Math.max(0, index - 1)); }}
            fitWidth={fitWidth}
            thumbsOn={thumbsOn}
            onToggleThumbs={() => setThumbsOn((on) => !on)}
            activeLabel={activeField ? activeField.label : ''}
            notLocated={Boolean(activeField && !activeField.located)}
          />
          <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
            {thumbsOn && pageCount > 0 && (
              <DfrThumbnails caseId={caseId} document={activeDoc} pageCount={pageCount} page={page} onSelect={setPage} />
            )}
            {activeDoc ? (
              <DfrPageCanvas
                caseId={caseId}
                document={activeDoc}
                page={page}
                zoom={zoom}
                rotate={rotate}
                boxes={activeBoxes}
                fitWidth={fitWidth}
                onDismiss={() => setActivePath('')}
              />
            ) : (
              <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', font: 'var(--text-body-sm)', color: 'var(--text-tertiary)' }}>
                This case has no stored source documents.
              </div>
            )}
          </div>
        </section>

        <div className="dfr-splitter"><DfrSplitter dragging={dragging} stacked={stacked} size={stacked ? Math.round(panelPercent) : panelWidth} onPointerDown={startDrag} onKeyDown={resizeWithKeyboard} onDoubleClick={() => { setPanelWidth(clampWidth(600)); setPanelPercent(70); }} /></div>

        <aside className="dfr-fields" aria-label="Extracted fields" style={{ width: panelWidth, flexShrink: 0, background: 'var(--bg-surface)', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
          <div className="dfr-fields-header" style={{ padding: '18px 22px 14px', borderBottom: '1px solid var(--border-subtle)', flexShrink: 0 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12 }}>
              <span style={{ font: 'var(--text-display-sm)', color: 'var(--text-primary)' }}>Extracted fields</span>
              <span style={{ font: 'var(--text-mono-sm)', color: 'var(--text-secondary)' }} title={`${totals.total} extracted field(s), ${totals.empty} left empty by the extractor`}>
                {totals.verified} / {totals.decidable} verified
              </span>
            </div>
            {detail && detail.document.confidence_score !== null && detail.document.confidence_score !== undefined && (
              <div style={{ marginTop: 6, font: 'var(--text-caption)', color: 'var(--text-tertiary)' }}>
                Extraction confidence {detail.document.confidence_score}%
                {detail.document.verdict ? ` · judge verdict ${detail.document.verdict}` : ''}
              </div>
            )}
            <div className="dfr-progress-bar" style={{ marginTop: 12, height: 5, borderRadius: 999, background: 'var(--slate-100)', overflow: 'hidden' }}>
              <div style={{ width: `${progress}%`, height: '100%', background: 'var(--brand-primary)', borderRadius: 999, transition: 'width 360ms var(--ease-standard)' }} />
            </div>
            <div className="dfr-filters" style={{ display: 'flex', gap: 8, marginTop: 14, flexWrap: 'wrap' }}>
              {DFR_FILTERS.map((name) => (
                <button
                  key={name}
                  onClick={() => setFilter(name)}
                  style={{
                    padding: '5px 11px', borderRadius: 'var(--radius-pill)', cursor: 'pointer', font: 'var(--text-label-sm)',
                    border: `1px solid ${filter === name ? 'var(--teal-500)' : 'var(--border-default)'}`,
                    background: filter === name ? 'var(--teal-50)' : 'var(--bg-surface)',
                    color: filter === name ? 'var(--teal-700)' : 'var(--text-secondary)',
                  }}
                >
                  {name}
                </button>
              ))}
            </div>
          </div>

          <div className="dfr-field-list" style={{ flex: 1, overflowY: 'auto', padding: '6px 0 10px' }}>
            {!loading && detail && (
              <div style={{ margin: '10px 22px', padding: 12, border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)' }}>
                <button disabled={saving || accepting} onClick={() => { setRecordKind(recordKind ? '' : 'certificate'); setRecordPage(String(page)); }} style={{ padding: '7px 10px', cursor: 'pointer' }}>Add missing source record</button>
                {recordKind && <div style={{ marginTop: 12, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 10 }}>
                  <label>Record type<select aria-label="Source record type" value={recordKind} disabled={saving} onChange={(event) => { setRecordKind(event.target.value); setRecordValues({}); }} style={{ width: '100%' }}>
                    {Object.entries(DFR_SOURCE_RECORDS).map(([kind, spec]) => <option key={kind} value={kind}>{spec.label}</option>)}
                  </select></label>
                  <label>Original PDF page<input aria-label="Original PDF page" type="number" min="1" max={pageCount} disabled={saving} value={recordPage} onChange={(event) => setRecordPage(event.target.value)} style={{ width: '100%' }} /></label>
                  {DFR_SOURCE_RECORDS[recordKind].fields.map((key) => <label key={key} style={{ font: 'var(--text-caption)' }}>{key === 'mc_from' ? 'Medical leave from' : key === 'mc_to' ? 'Medical leave to' : key === 'related_invoice_numbers' ? 'Related invoice numbers (separate with commas)' : key.replaceAll('_', ' ')}
                    <input aria-label={`Source record ${key}`} disabled={saving} value={recordValues[key] || ''} onChange={(event) => setRecordValues((current) => ({ ...current, [key]: event.target.value }))} style={{ width: '100%', boxSizing: 'border-box', padding: 7 }} />
                  </label>)}
                  <div style={{ gridColumn: '1 / -1', font: 'var(--text-caption)', color: 'var(--text-secondary)' }}>Copy only information visible on this original page. Keep different patients and separate original documents in separate records.</div>
                  <button disabled={saving || accepting} onClick={saveSourceRecord}>Save source record</button>
                </div>}
              </div>
            )}
            {loading && <div style={{ padding: 22, font: 'var(--text-body-sm)', color: 'var(--text-secondary)' }}>Loading extracted fields…</div>}
            {error && !loading && (
              <div style={{ margin: 18, padding: 14, borderRadius: 'var(--radius-md)', background: 'var(--status-danger-bg)', color: 'var(--status-danger-fg)', font: 'var(--text-body-sm)' }}>{error}</div>
            )}
            {detail && (detail.document_findings || []).map((finding, index) => (
              <React.Fragment key={index}>
                <div className="dfr-document-finding" style={{ margin: '10px 18px', padding: 12, borderRadius: 'var(--radius-md)', background: 'var(--status-warning-bg)', color: 'var(--status-warning-fg)', font: 'var(--text-body-sm)' }}>
                  {finding.reason || 'The judge raised an issue that could not be tied to a specific field.'}
                </div>
                <details className="dfr-document-finding-mobile">
                  <summary>Document finding {index + 1}</summary>
                  <p>{finding.reason || 'The judge raised an issue that could not be tied to a specific field.'}</p>
                </details>
              </React.Fragment>
            ))}
            {detail && (detail.groups || []).map((group) => {
              const visible = group.fields.filter(passesFilter);
              if (!visible.length) return null;
              const isCollapsed = collapsed[group.key];
              const groupFlagged = visible.filter((field) => {
                const status = dfrStatusOf(field, entries[field.field_path] || {});
                return status === 'low' || status === 'missing';
              }).length;
              const groupFilled = visible.filter((field) => !dfrIsBlank((entries[field.field_path] || {}).value)).length;
              return (
                <div key={group.key} className="dfr-field-group" style={{ padding: '14px 22px 6px' }}>
                  <div
                    onClick={() => setCollapsed((current) => ({ ...current, [group.key]: !current[group.key] }))}
                    style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 10, cursor: 'pointer' }}
                  >
                    <span style={{ font: 'var(--text-label-md)', letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--text-tertiary)' }}>{group.label}</span>
                    <span style={{ flex: 1, height: 1, background: 'var(--border-subtle)' }} />
                    <span style={{ font: 'var(--text-caption)', color: groupFlagged ? 'var(--status-warning-fg)' : 'var(--text-tertiary)' }}>
                      {groupFlagged
                        ? `${groupFlagged} need${groupFlagged === 1 ? 's' : ''} review`
                        : groupFilled
                          ? `${groupFilled} of ${visible.length} filled`
                          : `${visible.length} field${visible.length === 1 ? '' : 's'} · none extracted`}
                    </span>
                    <DfrIcon name={isCollapsed ? 'chevron-right' : 'chevron-down'} size={14} color="var(--text-tertiary)" />
                  </div>
                  {!isCollapsed && (
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                      {visible.map((field, index) => {
                        const previous = visible[index - 1];
                        const startsRow = field.row_index !== null && field.row_index !== undefined
                          && (!previous || previous.row_index !== field.row_index);
                        return (
                          <React.Fragment key={field.field_path}>
                            {startsRow && (
                              <div style={{ font: 'var(--text-label-sm)', color: 'var(--text-tertiary)', marginTop: index ? 6 : 0 }}>
                                {field.row_label || (group.label + ' ' + (field.row_index + 1))}
                              </div>
                            )}
                            <DfrFieldCard
                              field={field}
                              entry={entries[field.field_path] || {}}
                              active={activePath === field.field_path}
                              disabled={saving || accepting}
                              onSelect={() => selectField(field)}
                              onChange={(text) => editValue(field, text)}
                              onVerify={() => patchEntry(field.field_path, { verified: !(entries[field.field_path] || {}).verified })}
                              onClear={() => editValue(field, '')}
                              onUnavailable={() => patchEntry(field.field_path, { action: 'unavailable', actionSaved: false, verified: true, source_page: field.page || page })}
                            />
                          </React.Fragment>
                        );
                      })}
                    </div>
                  )}
                </div>
              );
            })}
            {detail && !allFields.length && !loading && (
              <div style={{ padding: 22, font: 'var(--text-body-sm)', color: 'var(--text-secondary)' }}>
                This document produced no extracted fields to review.
              </div>
            )}
            {/* Case actions follow the field list so short viewports keep room for editing. */}
            <div className="dfr-case-actions" style={{ borderTop: '1px solid var(--border-subtle)', padding: '12px 22px', display: 'flex', alignItems: 'center', gap: 10, background: 'var(--bg-surface-sunken)', flexWrap: 'wrap' }}>
              <span style={{ font: 'var(--text-caption)', color: 'var(--text-tertiary)', flex: 1, minWidth: 180 }}>
                {!caseReview ? ''
                  : caseReview.accepted ? `Accepted by ${caseReview.accepted_by || 'a reviewer'}.`
                    : caseReview.all_documents_reviewed ? 'All documents reviewed — the case can be accepted.'
                      : `Still to review: ${(caseReview.outstanding_documents || []).join(', ')}`}
              </span>
              <DfrButton
                variant="primary"
                icon={dfrCanRetryMedicalAssessment(caseReview) ? 'rotate-cw' : 'check'}
                onClick={dfrCanRetryMedicalAssessment(caseReview) ? retryMedicalAssessment : acceptCase}
                disabled={accepting || saving || loading || !caseReview || (!caseReview.accepted && !caseReview.all_documents_reviewed)}
              >
                {accepting
                  ? (dfrCanRetryMedicalAssessment(caseReview) ? 'Retrying…' : 'Accepting…')
                  : dfrCanRetryMedicalAssessment(caseReview) ? 'Retry medical assessment' : 'Accept case'}
              </DfrButton>
              <DfrButton variant="secondary" onClick={skipReview} disabled={accepting || saving || loading}>
                {accepting ? 'Resuming...' : 'Skip review and use current metadata'}
              </DfrButton>
            </div>
          </div>

          <div className="dfr-document-actions" style={{ borderTop: '1px solid var(--border-subtle)', padding: '12px 22px', display: 'flex', alignItems: 'center', gap: 10, background: 'var(--bg-surface)', flexShrink: 0, flexWrap: 'wrap' }}>
            {(saveResult || resumeError || totals.edited > 0) && (
              <span className="dfr-save-feedback" role={saveResult && !saveResult.ok ? 'alert' : 'status'} style={{ font: 'var(--text-caption)', color: saveResult ? (saveResult.ok ? 'var(--status-success-fg)' : 'var(--status-danger-fg)') : 'var(--text-secondary)' }}>
                {saveResult ? saveResult.message : resumeError || `${totals.edited} unsaved edit${totals.edited === 1 ? '' : 's'} on this document.`}
              </span>
            )}
            {nextUnreviewed && (
              <DfrButton variant="secondary" icon="chevron-right" iconPosition="right" onClick={() => leaveDocument(nextUnreviewed)} disabled={saving || accepting || loading}>
                Next document
              </DfrButton>
            )}
            <DfrButton
              variant={activeReviewed ? 'secondary' : 'primary'}
              icon="check"
              onClick={completeDocument}
              disabled={saving || accepting || !detail}
            >
              {saving
                ? 'Saving…'
                : activeReviewed
                  ? (totals.edited ? 'Save and re-confirm' : 'Reviewed')
                  : (totals.edited ? 'Save and mark reviewed' : 'Mark reviewed')}
            </DfrButton>
          </div>
        </aside>
      </div>
    </div>
  );
}

window.DocumentFieldReview = DocumentFieldReview;
