// Group-based test entry, PDF preview, job wizard, approve/reject modals
// Restructured per Paul's revised spec: tests are organised into Groups, and
// data entry happens at the Group level (not per-test).

const { useState: useS2, useMemo: useM2, useRef: useR2 } = React;

function getUserById(arr, id) { return arr.find((x) => x.id === id); }

// =====================================================================
// DEVICE TAG LIST — multi-select chips, options come from job.devices
// =====================================================================
function DeviceTagList({ label, options, value, onChange, tone, disabled }) {
  const [open, setOpen] = useS2(false);
  const [query, setQuery] = useS2('');
  const rootRef = useR2(null);

  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  const toggle = (d) => {
    if (disabled) return;
    onChange(value.includes(d) ? value.filter((x) => x !== d) : [...value, d]);
  };
  const remove = (d, e) => { e.stopPropagation(); if (!disabled) onChange(value.filter((x) => x !== d)); };
  const clearAll = (e) => { e.stopPropagation(); if (!disabled) onChange([]); };

  const q = query.trim().toLowerCase();
  const filtered = options.filter((d) => !q || d.toLowerCase().includes(q));
  const allFilteredOn = filtered.length > 0 && filtered.every((d) => value.includes(d));
  const selectAllFiltered = (e) => {
    e.stopPropagation();
    if (disabled) return;
    if (allFilteredOn) {
      onChange(value.filter((x) => !filtered.includes(x)));
    } else {
      onChange([...new Set([...value, ...filtered])]);
    }
  };
  const invertFiltered = (e) => {
    e.stopPropagation();
    if (disabled) return;
    const next = options.filter((d) => !value.includes(d));
    onChange(next);
  };

  return (
    <div className={`devicetags dt-dd ${tone || ''} ${disabled ? 'disabled' : ''} ${open ? 'open' : ''}`} ref={rootRef}>
      <label className="dt-label">
        {label}
        <span className="dt-count">{value.length}/{options.length}</span>
      </label>
      <button
        type="button"
        className="dt-dd-control"
        onClick={() => !disabled && setOpen((o) => !o)}
        disabled={disabled}
      >
        <div className="dt-dd-chips">
          {value.length === 0 && (
            <span className="dt-dd-placeholder">
              {disabled ? 'No devices selected' : 'Click to select devices…'}
            </span>
          )}
          {value.slice(0, 12).map((d) => (
            <span key={d} className={`dt-chip on ${tone || ''}`}>
              {d}
              {!disabled && (
                <span className="dt-chip-x" onClick={(e) => remove(d, e)} aria-label={`Remove ${d}`}>
                  <Icon name="x" size={9} />
                </span>
              )}
            </span>
          ))}
          {value.length > 12 && (
            <span className="dt-dd-more">+{value.length - 12} more</span>
          )}
        </div>
        <div className="dt-dd-trail">
          {!disabled && value.length > 0 && (
            <span className="dt-dd-clear" onClick={clearAll} title="Clear all" aria-label="Clear all">
              <Icon name="x" size={11} />
            </span>
          )}
          <Icon name="chevron-down" size={13} />
        </div>
      </button>

      {open && !disabled && (
        <div className="dt-dd-menu" role="listbox" aria-multiselectable="true">
          <div className="dt-dd-search">
            <Icon name="search" size={12} />
            <input
              autoFocus
              type="text"
              placeholder={`Filter ${options.length} device${options.length === 1 ? '' : 's'}…`}
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
            {query && (
              <button type="button" className="tree-search-clear" onClick={() => setQuery('')} aria-label="Clear filter">
                <Icon name="x" size={10} />
              </button>
            )}
          </div>
          <div className="dt-dd-bulk">
            <button type="button" className="tree-link" onClick={selectAllFiltered}>
              {allFilteredOn ? 'Deselect' : 'Select'} {q ? `all ${filtered.length} matching` : 'all'}
            </button>
            <span className="tree-sep" />
            <button type="button" className="tree-link" onClick={invertFiltered}>Invert</button>
            <span className="dt-dd-bulk-spacer" />
            <span className="dt-dd-bulk-count">{value.length} selected</span>
          </div>
          <div className="dt-dd-options">
            {filtered.length === 0 && (
              <div className="dt-dd-empty">No devices match “{query}”.</div>
            )}
            {filtered.map((d) => {
              const on = value.includes(d);
              return (
                <button type="button" key={d} role="option" aria-selected={on}
                  className={`dt-dd-opt ${on ? `on ${tone || ''}` : ''}`}
                  onClick={() => toggle(d)}>
                  <span className={`tree-cb ${on ? 'on' : ''}`}>
                    {on ? <Icon name="check" size={10} /> : null}
                  </span>
                  <span className="dt-dd-opt-id">{d}</span>
                </button>
              );
            })}
          </div>
          <div className="dt-dd-foot">
            <span>Showing {filtered.length} of {options.length}</span>
            <button type="button" className="tree-link" onClick={() => setOpen(false)}>Done</button>
          </div>
        </div>
      )}
    </div>
  );
}

// =====================================================================
// SEARCHABLE STANDARD-RESPONSE DROPDOWN (Paul: should be searchable)
// =====================================================================
function StandardResponseDropdown({ data, onInsert, disabled }) {
  const [open, setOpen] = useS2(false);
  const [q, setQ] = useS2('');
  const filtered = data.STANDARD_RESPONSES.filter((sr) =>
    !q.trim() || sr.title.toLowerCase().includes(q.toLowerCase()) ||
    sr.body.toLowerCase().includes(q.toLowerCase()) || sr.category.toLowerCase().includes(q.toLowerCase())
  );
  return (
    <div className="sr-dropdown">
      <button className="btn ghost sm" disabled={disabled} onClick={() => setOpen(!open)}>
        <Icon name="snippet" size={12} /> Insert standard response <Icon name="chevron-down" size={10} />
      </button>
      {open && (
        <div className="sr-pop">
          <input className="sr-search" autoFocus placeholder="Search…" value={q} onChange={(e) => setQ(e.target.value)} />
          <div className="sr-list">
            {filtered.map((sr) => (
              <div key={sr.id} className="sr-item" onClick={() => { onInsert(sr); setOpen(false); setQ(''); }}>
                <div className="sr-cat">{sr.category}</div>
                <div className="sr-title">{sr.title}</div>
                <div className="sr-body">{sr.body}</div>
              </div>
            ))}
            {filtered.length === 0 && <div className="sr-empty">No matches</div>}
          </div>
        </div>
      )}
    </div>
  );
}

// =====================================================================
// GROUP TEST ENTRY — the rewritten test-entry page
// (one page per group, lists all sub-tests within, group-level evidence/summary)
// =====================================================================
function TestEntry({ data, jobId, testId: groupId, currentUser, onBack, onSave, onComplete, readonly = false }) {
  const job = data.JOBS.find((j) => j.id === jobId);
  if (!job) return null;
  const group = data.TEST_GROUPS.find((g) => g.id === groupId);
  if (!group) return null;
  const result = job.groupResults.find((r) => r.groupId === groupId);
  if (!result) return null;
  const cust = data.CUSTOMERS.find((c) => c.id === job.customerId);
  const tester = getUserById(data.USERS, result.testerId || job.engineerIds[0]);

  const [devicesTested, setDevicesTested] = useS2(result.devicesTested.length ? result.devicesTested : job.devices.slice());
  const [tests, setTests] = useS2(result.tests.map((t) => ({ ...t })));
  const [obsSummary, setObsSummary] = useS2(result.observationsSummary);
  const [groupOutcome, setGroupOutcome] = useS2(result.groupOutcome);
  const [evidence, setEvidence] = useS2(result.evidence.map((e) => ({ ...e })));
  const [activeTestForSr, setActiveTestForSr] = useS2(null);
  const [dragIdx, setDragIdx] = useS2(null);

  const lockEdit = readonly || result.status === 'Complete' ||
    job.status === 'For Approval' || job.status === 'Approved' || job.status === 'Complete';

  const updateTest = (idx, patch) => {
    setTests(tests.map((t, i) => i === idx ? { ...t, ...patch } : t));
  };

  const moveEvidence = (from, to) => {
    if (from === to || to < 0 || to >= evidence.length) return;
    const next = evidence.slice();
    const [it] = next.splice(from, 1);
    next.splice(to, 0, it);
    setEvidence(next.map((e, i) => ({ ...e, sort: i })));
  };

  return (
    <div className="group-entry">
      <button className="btn ghost sm" onClick={onBack} style={{ marginBottom: 8 }}>
        <Icon name="arrow-left" size={12} /> Back to job
      </button>

      <div className="page-header" style={{ alignItems: 'flex-start' }}>
        <div>
          <div className="sub" style={{ fontSize: 11, marginBottom: 4 }}>
            {job.reportNumber} · Group {group.number}
          </div>
          <h1 style={{ fontSize: 20 }}>{group.name}</h1>
          <div className="sub" style={{ marginTop: 4 }}>
            <span className="tag" style={{ marginRight: 6 }}>{group.category}</span>
            <span className="tag" style={{ marginRight: 6 }}>{group.as6171Id}</span>
            {group.tests.length} test{group.tests.length === 1 ? '' : 's'} in this group
          </div>
        </div>
        <div className="actions">
          <StatusPill status={result.status} />
          {!lockEdit && (
            <>
              <button className="btn" onClick={onSave}><Icon name="check" size={12} /> Save draft</button>
              <button className="btn primary" disabled={!groupOutcome} onClick={onComplete}>
                <Icon name="check-circle" size={12} /> Mark group complete
              </button>
            </>
          )}
        </div>
      </div>

      {/* TEST CONTEXT — read-only summary at the top */}
      <div className="card ctx-card">
        <div className="card-header"><h3>Test context</h3></div>
        <div className="card-body">
          <div className="ctx-grid">
            <div><span className="k">Customer</span><span className="v">{cust.name}</span></div>
            <div><span className="k">Customer job ref</span><span className="v">{job.customerJobRef}</span></div>
            <div><span className="k">Part number</span><span className="v">{job.partNumber}</span></div>
            <div><span className="k">Manufacturer</span><span className="v">{job.manufacturer}</span></div>
            <div><span className="k">Description</span><span className="v">{job.partDescription}</span></div>
            <div><span className="k">Date / Lot code</span><span className="v">{job.dateCode} / {job.lotCode}</span></div>
            <div><span className="k">Tester</span><span className="v">
              <Avatar user={tester} size={16} style={{ display: 'inline-block', verticalAlign: 'middle', marginRight: 4 }} />
              {tester.name} ({tester.initials})
            </span></div>
            <div><span className="k">Group identifier</span><span className="v">{group.number}</span></div>
          </div>

          <div style={{ marginTop: 14 }}>
            <DeviceTagList
              label="Devices selected for testing in this group"
              options={job.devices}
              value={devicesTested}
              onChange={setDevicesTested}
              tone="tested"
              disabled={lockEdit}
            />
            <div className="hint">Tag list pulled from job devices. Used for audit — identifies which physical devices were used in this group.</div>
          </div>
        </div>
      </div>

      {/* TESTS WITHIN GROUP */}
      <div className="card" style={{ marginTop: 14 }}>
        <div className="card-header">
          <h3>Tests in this group ({group.tests.length})</h3>
          <span style={{ fontSize: 11, color: 'var(--body-text)' }}>Outcomes are recorded as device tag lists — only devices listed above as tested can be selected.</span>
        </div>
        <div className="card-body" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {group.tests.map((t, idx) => {
            const r = tests[idx];
            return (
              <div key={t.id} className="subtest-card">
                <div className="st-head">
                  <div>
                    <div className="st-num">{t.number}</div>
                    <div className="st-title">{t.title}</div>
                  </div>
                </div>
                <div className="st-body">
                  <DeviceTagList label="Acceptable"     options={devicesTested} value={r.acceptable}
                    onChange={(v) => updateTest(idx, { acceptable: v })} tone="acceptable" disabled={lockEdit} />
                  <DeviceTagList label="Suspect"        options={devicesTested} value={r.suspect}
                    onChange={(v) => updateTest(idx, { suspect: v })} tone="suspect" disabled={lockEdit} />
                  <DeviceTagList label="Not accepted"   options={devicesTested} value={r.notAccepted}
                    onChange={(v) => updateTest(idx, { notAccepted: v })} tone="notaccepted" disabled={lockEdit} />
                  <DeviceTagList label="Not available"  options={devicesTested} value={r.notAvailable}
                    onChange={(v) => updateTest(idx, { notAvailable: v })} tone="notavailable" disabled={lockEdit} />
                  <div className="field" style={{ marginTop: 6 }}>
                    <div className="lbl-row">
                      <label>Comments</label>
                      <StandardResponseDropdown data={data} disabled={lockEdit}
                        onInsert={(sr) => updateTest(idx, { comments: (r.comments ? r.comments + '\n\n' : '') + sr.body })} />
                    </div>
                    <textarea rows={2} value={r.comments} disabled={lockEdit}
                      onChange={(e) => updateTest(idx, { comments: e.target.value })} />
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* OBSERVATIONS SUMMARY + EVIDENCE + GROUP STATUS */}
      <div className="two-col-3-2" style={{ marginTop: 14 }}>
        <div className="card">
          <div className="card-header">
            <h3>Observations summary</h3>
            <StandardResponseDropdown data={data} disabled={lockEdit}
              onInsert={(sr) => setObsSummary((obsSummary ? obsSummary + '\n\n' : '') + sr.body)} />
          </div>
          <div className="card-body">
            <textarea rows={5} value={obsSummary} onChange={(e) => setObsSummary(e.target.value)} disabled={lockEdit} />
            <div className="hint">A short summary of findings across all tests in this group. Appears in the report under the group heading.</div>
          </div>
        </div>

        <div className="card">
          <div className="card-header"><h3>Group status</h3></div>
          <div className="card-body">
            <div className="group-status-seg">
              {['Passed','Failed','N/A'].map((opt) => (
                <button key={opt}
                  className={`opt ${opt.toLowerCase().replace('/','')} ${groupOutcome === opt ? 'selected' : ''}`}
                  disabled={lockEdit}
                  onClick={() => setGroupOutcome(opt)}>
                  <span className="swatch" />{opt}
                </button>
              ))}
            </div>
            <div className="hint" style={{ marginTop: 10 }}>
              Required before this group can be marked complete. Drives the per-group outcome shown in the report summary.
            </div>
          </div>
        </div>
      </div>

      {/* EVIDENCE — drag-and-drop reorderable, with output-size selector */}
      <div className="card" style={{ marginTop: 14 }}>
        <div className="card-header">
          <h3>Evidence — figures &amp; images</h3>
          <span style={{ fontSize: 11, color: 'var(--body-text)' }}>{evidence.length} item{evidence.length === 1 ? '' : 's'} · drag to reorder</span>
        </div>
        <div className="card-body">
          {evidence.length > 0 && (
            <div className="evidence-grid-v2">
              {evidence.map((f, i) => (
                <div key={f.id}
                  className={`ev-card size-${f.size} ${dragIdx === i ? 'dragging' : ''} ${dragIdx !== null && dragIdx !== i ? 'drop-target' : ''}`}
                  draggable={!lockEdit}
                  onDragStart={(e) => { setDragIdx(i); e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', String(i)); } catch (_) {} }}
                  onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }}
                  onDragEnter={(e) => { e.currentTarget.classList.add('drop-hover'); }}
                  onDragLeave={(e) => { e.currentTarget.classList.remove('drop-hover'); }}
                  onDrop={(e) => { e.preventDefault(); e.currentTarget.classList.remove('drop-hover'); moveEvidence(dragIdx, i); setDragIdx(null); }}
                  onDragEnd={() => setDragIdx(null)}>
                  <div className="ev-drag" title="Drag to reorder"><Icon name="grip" size={14} /></div>
                  <SyntheticImage seed={i + 7} label={f.label} className="ev-img" />
                  <div className="ev-meta">
                    <div className="ev-sort">#{i + 1}</div>
                    <input className="ev-caption" value={f.caption} disabled={lockEdit}
                      onChange={(e) => setEvidence(evidence.map((x, j) => j === i ? { ...x, caption: e.target.value } : x))} />
                    <div className="ev-size-row">
                      <label>Output size in report</label>
                      <select value={f.size} disabled={lockEdit}
                        onChange={(e) => setEvidence(evidence.map((x, j) => j === i ? { ...x, size: e.target.value } : x))}>
                        <option value="full">Full width</option>
                        <option value="half">Half (2 per row)</option>
                        <option value="third">Third (3 per row)</option>
                      </select>
                    </div>
                  </div>
                  {!lockEdit && (
                    <div className="ev-actions">
                      <button className="btn ghost sm" onClick={() => moveEvidence(i, i - 1)} title="Move up"><Icon name="arrow-up" size={11} /></button>
                      <button className="btn ghost sm" onClick={() => moveEvidence(i, i + 1)} title="Move down"><Icon name="arrow-down" size={11} /></button>
                      <button className="btn ghost sm" onClick={() => setEvidence(evidence.filter((_, j) => j !== i))} title="Delete"><Icon name="trash" size={11} /></button>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
          {!lockEdit && (
            <div className="dropzone">
              <div className="big">Drag images here, or click to upload</div>
              JPG / PNG up to 20 MB. Filenames retained as default captions. Output size (full / half / third) controls layout in the report.
              <div style={{ marginTop: 10 }}>
                <button className="btn"><Icon name="upload" size={12} /> Choose files</button>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// =====================================================================
// PDF PREVIEW — rewritten cover, summary by group, per-page header & footer
// =====================================================================
function PdfPreview({ data, jobId, onClose }) {
  const job = data.JOBS.find((j) => j.id === jobId);
  if (!job) return null;
  const cust = data.CUSTOMERS.find((c) => c.id === job.customerId);
  const groups = job.groupResults.map((r) => ({
    ...r,
    g: data.TEST_GROUPS.find((g) => g.id === r.groupId),
  })).sort((a, b) => a.g.number.localeCompare(b.g.number));

  // pages: 1=cover, 2=summary, 3..n+2=group pages
  const groupPages = groups.map((gr) => ({ label: `Group ${gr.g.number} — ${gr.g.name}` }));
  const pages = [{ label: 'Cover' }, { label: 'Summary' }, ...groupPages];
  const totalPages = pages.length;
  const [page, setPage] = useS2(1);

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal lg" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 1100 }}>
        <div className="modal-header">
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <Icon name="pdf" size={18} color="var(--light-blue)" />
            <h3>PDF preview — {job.reportNumber}</h3>
            <span className="tag">DRAFT</span>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn"><Icon name="download" size={12} /> Download draft</button>
            <button className="btn ghost sm" onClick={onClose}><Icon name="x" size={14} /></button>
          </div>
        </div>
        <div className="modal-body" style={{ padding: 0 }}>
          <div className="pdf-shell">
            <div className="pdf-pager">
              {pages.map((p, i) => (
                <button key={i} className={`p ${page === i + 1 ? 'active' : ''}`} onClick={() => setPage(i + 1)}>{i + 1}. {p.label}</button>
              ))}
            </div>
            <PdfPage data={data} job={job} cust={cust} groups={groups} page={page} totalPages={totalPages} />
          </div>
        </div>
      </div>
    </div>
  );
}

function PdfPageHeader({ data, job, cust }) {
  // Paul: header should include report number, customer name, logo, part + manufacturer, date code
  return (
    <div className="pdf-header v2">
      <div className="left">
        <img src="assets/force-logo.png" alt="Force Technologies" className="pdf-hdr-logo" />
      </div>
      <div className="ctr">
        <div className="line1">{cust.name} · {job.partNumber} ({job.manufacturer})</div>
        <div className="line2">Report {job.reportNumber} · Date code {job.dateCode}</div>
      </div>
    </div>
  );
}

function PdfPageFooter({ data, job, page, total, preparedBy, approvedBy }) {
  // Paul: every page footer should include signature+name of prepared & approved, date of gen, page number, disclaimer
  return (
    <div className="pdf-footer v2">
      <div className="sig-row">
        <div className="sig">
          <div className="sig-label">Prepared by</div>
          <div className="sig-mark">{preparedBy?.signature || preparedBy?.name}</div>
          <div className="sig-name">{preparedBy?.name} ({preparedBy?.initials})</div>
        </div>
        <div className="sig">
          <div className="sig-label">Approved by</div>
          <div className="sig-mark">{approvedBy?.signature || approvedBy?.name}</div>
          <div className="sig-name">{approvedBy?.name} ({approvedBy?.initials})</div>
        </div>
        <div className="dategen">
          <div className="sig-label">Date of generation</div>
          <div className="dategen-v">{data.fmtDate(new Date())}</div>
          <div className="page-num">Page {page} of {total}</div>
        </div>
      </div>
      <div className="disclaim">{data.LAB.disclaimer}</div>
    </div>
  );
}

function PdfPage({ data, job, cust, groups, page, totalPages }) {
  const prepared = getUserById(data.USERS, job.engineerIds[0]);
  const approved = getUserById(data.USERS, job.approverIds[0]);
  const isCover = page === 1;

  return (
    <div className="pdf-page v2">
      {!isCover && <PdfPageHeader data={data} job={job} cust={cust} />}
      <div className="pdf-content v2">
        {isCover && <PdfCover data={data} job={job} cust={cust} prepared={prepared} approved={approved} />}
        {page === 2 && <PdfSummary data={data} job={job} groups={groups} />}
        {page >= 3 && page <= 2 + groups.length && (
          <PdfGroupPage data={data} job={job} group={groups[page - 3]} />
        )}
      </div>
      {!isCover && <PdfPageFooter data={data} job={job} page={page} total={totalPages} preparedBy={prepared} approvedBy={approved} />}
    </div>
  );
}

function PdfCover({ data, job, cust, prepared, approved }) {
  // Paul:
  //   - no header on cover page
  //   - title = "Laboratory Analysis Report" (no Risk/Model)
  //   - full logo in large size
  //   - issuing laboratory = full address
  //   - HTML box at bottom for disclaimer + (future) accreditation logos
  return (
    <div className="pdf-cover-v2">
      <div className="cover-logo">
        <img src="assets/force-logo.png" alt="Force Technologies" />
      </div>
      <div className="cover-title-block">
        <div className="cover-eyebrow">AS6171A · Counterfeit Risk Mitigation</div>
        <h1 className="cover-title">Laboratory Analysis Report</h1>
        <div className="cover-rep-no">Report {job.reportNumber}</div>
      </div>

      <div className="cover-meta-grid">
        <div className="block">
          <h4>Customer</h4>
          <div className="r"><span className="k">Name</span><span className="v">{cust.name}</span></div>
          <div className="r"><span className="k">Job reference</span><span className="v">{job.customerJobRef}</span></div>
          <div className="r"><span className="k">Contact</span><span className="v">{cust.contact}</span></div>
          <div className="r"><span className="k">Address</span><span className="v">{cust.address}</span></div>
        </div>
        <div className="block">
          <h4>Component under test</h4>
          <div className="r"><span className="k">Part number</span><span className="v">{job.partNumber}</span></div>
          <div className="r"><span className="k">Description</span><span className="v">{job.partDescription}</span></div>
          <div className="r"><span className="k">Manufacturer</span><span className="v">{job.manufacturer}</span></div>
          <div className="r"><span className="k">Date / Lot code</span><span className="v">{job.dateCode} / {job.lotCode}</span></div>
          <div className="r"><span className="k">Quantity</span><span className="v">{job.quantityReceived} received · {job.sampleQty} sampled</span></div>
          <div className="r"><span className="k">Received</span><span className="v">{data.fmtDate(job.receivedDate)}</span></div>
        </div>
      </div>

      <div className="cover-people">
        <div>
          <div className="lbl">Prepared by</div>
          <div className="nm">{prepared.name}</div>
          <div className="rl">Test Engineer ({prepared.initials})</div>
        </div>
        <div>
          <div className="lbl">Approved by</div>
          <div className="nm">{approved.name}</div>
          <div className="rl">Approver ({approved.initials})</div>
        </div>
        <div>
          <div className="lbl">Date issued</div>
          <div className="nm">{data.fmtDate(job.issuedAt || job.approvedAt || job.targetIssueDate)}</div>
        </div>
      </div>

      <div className="cover-lab">
        <div className="lbl">Issuing laboratory</div>
        <div className="lab-name">{data.LAB.name}</div>
        <div className="lab-addr">{data.LAB.address}</div>
        <div className="lab-meta">{data.LAB.phone} · {data.LAB.email} · {data.LAB.web}</div>
      </div>

      {/* Disclaimer + accreditation box — populated from admin in future */}
      <div className="cover-disclaimer-box">
        <div className="dl-row">
          <div className="dl-text">
            <div className="dl-h">Disclaimer &amp; conditions of issue</div>
            <div className="dl-p">{data.LAB.disclaimer}</div>
          </div>
          <div className="dl-logos">
            <div className="lab-placeholder" title="Accreditation logo placeholder">UKAS</div>
            <div className="lab-placeholder" title="Accreditation logo placeholder">ISO 9001</div>
            <div className="lab-placeholder" title="Accreditation logo placeholder">SAE</div>
          </div>
        </div>
      </div>
    </div>
  );
}

function PdfSummary({ data, job, groups }) {
  // Paul:
  //   - ordered by designated number not appearance
  //   - no codes like EV-CDM, full test name only
  //   - no Category column
  //   - per group: number passed/failed/suspect/N/A + observation + tester initials
  const ordered = groups.slice().sort((a, b) => a.g.number.localeCompare(b.g.number));
  return (
    <div>
      <h2 className="pdf-h2">Executive summary</h2>
      <p style={{ fontSize: 9.5, lineHeight: 1.55 }}>
        The {job.sampleQty} devices received from {data.CUSTOMERS.find((c) => c.id === job.customerId).name} (customer reference {job.customerJobRef}) were subjected to AS6171A Counterfeit Risk Mitigation testing across {ordered.length} test groups. The aggregate outcomes are summarised below; per-group findings and supporting figures follow on subsequent pages.
      </p>

      <h3 className="pdf-h3">Group results</h3>
      <table className="pdf-tbl summary-v2">
        <thead>
          <tr>
            <th style={{ width: 50 }}>Number</th>
            <th>Test group</th>
            <th className="num" style={{ width: 36 }}>Pass</th>
            <th className="num" style={{ width: 36 }}>Fail</th>
            <th className="num" style={{ width: 50 }}>Suspect</th>
            <th className="num" style={{ width: 36 }}>N/A</th>
            <th>Observation</th>
            <th style={{ width: 60 }}>Tester</th>
          </tr>
        </thead>
        <tbody>
          {ordered.map((gr) => {
            const counts = gr.tests.reduce((acc, t) => {
              acc.acc += t.acceptable.length;
              acc.sus += t.suspect.length;
              acc.fail += t.notAccepted.length;
              acc.na += t.notAvailable.length;
              return acc;
            }, { acc: 0, sus: 0, fail: 0, na: 0 });
            const tester = getUserById(data.USERS, gr.testerId);
            return (
              <tr key={gr.groupId}>
                <td style={{ fontVariantNumeric: 'tabular-nums' }}>{gr.g.number}</td>
                <td><strong>{gr.g.name}</strong></td>
                <td className="num">{counts.acc}</td>
                <td className="num">{counts.fail}</td>
                <td className="num"><strong style={{ color: counts.sus > 0 ? '#8b5500' : 'inherit' }}>{counts.sus}</strong></td>
                <td className="num">{counts.na}</td>
                <td style={{ fontSize: 8.5, lineHeight: 1.4 }}>{gr.observationsSummary || '—'}</td>
                <td style={{ fontVariantNumeric: 'tabular-nums', fontWeight: 600 }}>{tester ? tester.initials : '—'}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function PdfGroupPage({ data, job, group }) {
  if (!group) return null;
  const tester = getUserById(data.USERS, group.testerId || job.engineerIds[0]);
  // Group evidence — render in rows according to size (full / half / third)
  const evidence = (group.evidence || []).slice().sort((a, b) => a.sort - b.sort);
  return (
    <div>
      <h2 className="pdf-h2">{group.g.number} — {group.g.name}</h2>
      <div className="pdf-meta-grid" style={{ background: 'var(--canvas)', padding: 8, borderRadius: 3, border: '1px solid var(--line)' }}>
        <div><span className="k">Category: </span><span className="v">{group.g.category}</span></div>
        <div><span className="k">Identifier: </span><span className="v">{group.g.as6171Id}</span></div>
        <div><span className="k">Tester: </span><span className="v">{tester.name} ({tester.initials})</span></div>
        <div><span className="k">Status: </span><span className="v" style={{ fontWeight: 700, color: group.groupOutcome === 'Passed' ? 'var(--green)' : group.groupOutcome === 'Failed' ? 'var(--red)' : 'var(--body-text)' }}>{group.groupOutcome || '—'}</span></div>
        <div style={{ gridColumn: 'span 2' }}><span className="k">Devices tested: </span><span className="v" style={{ fontVariantNumeric: 'tabular-nums' }}>{group.devicesTested.join(', ') || '—'}</span></div>
      </div>

      <h3 className="pdf-h3">Tests performed</h3>
      <table className="pdf-tbl">
        <thead><tr><th style={{ width: 50 }}>Number</th><th>Test</th><th className="num" style={{ width: 50 }}>Pass</th><th className="num" style={{ width: 50 }}>Suspect</th><th className="num" style={{ width: 50 }}>Fail</th><th className="num" style={{ width: 36 }}>N/A</th></tr></thead>
        <tbody>
          {group.g.tests.map((t, i) => {
            const r = group.tests[i] || { acceptable: [], suspect: [], notAccepted: [], notAvailable: [] };
            return (
              <React.Fragment key={t.id}>
                <tr>
                  <td style={{ fontVariantNumeric: 'tabular-nums' }}>{t.number}</td>
                  <td>{t.title}</td>
                  <td className="num">{r.acceptable.length}</td>
                  <td className="num"><strong style={{ color: r.suspect.length > 0 ? '#8b5500' : 'inherit' }}>{r.suspect.length}</strong></td>
                  <td className="num"><strong style={{ color: r.notAccepted.length > 0 ? 'var(--red)' : 'inherit' }}>{r.notAccepted.length}</strong></td>
                  <td className="num">{r.notAvailable.length}</td>
                </tr>
                {r.comments && (
                  <tr><td></td><td colSpan={5} style={{ fontSize: 8.5, color: 'var(--body-text)', fontStyle: 'italic' }}>“{r.comments}”</td></tr>
                )}
              </React.Fragment>
            );
          })}
        </tbody>
      </table>

      <h3 className="pdf-h3">Observations</h3>
      <p style={{ fontSize: 9, lineHeight: 1.55, whiteSpace: 'pre-line' }}>{group.observationsSummary || '—'}</p>

      {evidence.length > 0 && (
        <>
          <h3 className="pdf-h3">Evidence</h3>
          <div className="pdf-evgrid">
            {evidence.map((f) => (
              <div key={f.id} className={`pdf-evcell size-${f.size}`}>
                <SyntheticImage seed={f.label.charCodeAt(0) + f.sort * 3} label={f.label} className="pdf-ev-img" />
                <div className="fig">Fig. {f.label} — {f.caption}</div>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

// =====================================================================
// NEW JOB WIZARD — treeview group/test picker, multi-select engineers/approvers
// =====================================================================
function GroupTestTree({ groups, selectedGroups, selectedTests, onChange }) {
  const [expanded, setExpanded] = useS2(() => new Set(groups.map((g) => g.id)));
  const [query, setQuery] = useS2('');

  const toggleExpanded = (gid) => {
    setExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(gid)) next.delete(gid); else next.add(gid);
      return next;
    });
  };
  const expandAll = () => setExpanded(new Set(groups.map((g) => g.id)));
  const collapseAll = () => setExpanded(new Set());

  const toggleGroup = (g) => {
    const allTestIds = g.tests.map((t) => t.id);
    const groupOn = selectedGroups.includes(g.id);
    if (groupOn) {
      onChange({
        selectedGroups: selectedGroups.filter((x) => x !== g.id),
        selectedTests: selectedTests.filter((tid) => !allTestIds.includes(tid)),
      });
    } else {
      onChange({
        selectedGroups: [...selectedGroups, g.id],
        selectedTests: [...new Set([...selectedTests, ...allTestIds])],
      });
    }
  };
  const toggleTest = (g, t) => {
    const on = selectedTests.includes(t.id);
    let nextTests = on ? selectedTests.filter((x) => x !== t.id) : [...selectedTests, t.id];
    const groupOn = g.tests.some((x) => nextTests.includes(x.id));
    let nextGroups = selectedGroups.filter((x) => x !== g.id);
    if (groupOn) nextGroups.push(g.id);
    onChange({ selectedGroups: nextGroups, selectedTests: nextTests });
  };

  // Filtering: match group name/number OR any child test title/number
  const q = query.trim().toLowerCase();
  const matches = (s) => s.toLowerCase().includes(q);
  const filtered = !q ? groups : groups
    .map((g) => {
      const groupHit = matches(g.name) || matches(g.number) || matches(g.category);
      const hitTests = g.tests.filter((t) => matches(t.title) || matches(t.number));
      if (groupHit) return { ...g, tests: g.tests, _allHit: true };
      if (hitTests.length) return { ...g, tests: hitTests };
      return null;
    })
    .filter(Boolean);

  // When searching, force-expand any group that has a hit
  const effectiveExpanded = q
    ? new Set(filtered.map((g) => g.id))
    : expanded;

  const totalSelectedTests = selectedTests.length;
  const totalGroupsWithSelection = groups.filter((g) =>
    selectedGroups.includes(g.id) || g.tests.some((t) => selectedTests.includes(t.id))
  ).length;

  const Highlight = ({ text }) => {
    if (!q) return text;
    const i = text.toLowerCase().indexOf(q);
    if (i < 0) return text;
    return (
      <>
        {text.slice(0, i)}
        <mark className="tree-mark">{text.slice(i, i + q.length)}</mark>
        {text.slice(i + q.length)}
      </>
    );
  };

  return (
    <div className="tree">
      <div className="tree-toolbar">
        <div className="tree-search">
          <Icon name="search" size={13} />
          <input
            type="text"
            placeholder="Filter groups or tests…"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
          />
          {query && (
            <button type="button" className="tree-search-clear" onClick={() => setQuery('')} aria-label="Clear">
              <Icon name="x" size={12} />
            </button>
          )}
        </div>
        <div className="tree-toolbar-actions">
          <button type="button" className="tree-link" onClick={expandAll}>Expand all</button>
          <span className="tree-sep" />
          <button type="button" className="tree-link" onClick={collapseAll}>Collapse all</button>
        </div>
        <div className="tree-summary">
          <span className="tree-summary-num">{totalGroupsWithSelection}</span> group{totalGroupsWithSelection === 1 ? '' : 's'} ·
          <span className="tree-summary-num"> {totalSelectedTests}</span> test{totalSelectedTests === 1 ? '' : 's'} selected
        </div>
      </div>

      <div className="tree-body" role="tree" aria-label="Groups and tests">
        {filtered.length === 0 && (
          <div className="tree-empty">No groups or tests match “{query}”.</div>
        )}
        {filtered.map((g) => {
          const groupOn = selectedGroups.includes(g.id);
          const anyTestOn = g.tests.some((t) => selectedTests.includes(t.id));
          const allTestsOn = g.tests.length > 0 && g.tests.every((t) => selectedTests.includes(t.id));
          const partial = (anyTestOn || groupOn) && !allTestsOn;
          const open = effectiveExpanded.has(g.id);
          const selectedInGroup = g.tests.filter((t) => selectedTests.includes(t.id)).length;

          return (
            <div key={g.id} className={`tree-group ${open ? 'open' : 'closed'}`} role="treeitem" aria-expanded={open}>
              <div className="tree-node tree-node-group">
                <button
                  type="button"
                  className="tree-twisty"
                  onClick={() => toggleExpanded(g.id)}
                  aria-label={open ? 'Collapse' : 'Expand'}
                >
                  <Icon name={open ? 'chevron-down' : 'chevron-right'} size={13} />
                </button>
                <button
                  type="button"
                  className={`tree-cb ${allTestsOn ? 'on' : partial ? 'partial' : ''}`}
                  onClick={() => toggleGroup(g)}
                  aria-label={allTestsOn ? 'Deselect group' : 'Select group'}
                >
                  {allTestsOn ? <Icon name="check" size={10} /> : partial ? <span className="dash" /> : null}
                </button>
                <button type="button" className="tree-row" onClick={() => toggleExpanded(g.id)}>
                  <span className="tree-num">{g.number}</span>
                  <span className="tree-name"><Highlight text={g.name} /></span>
                  <span className="tree-meta">
                    <span className={`tree-pip ${g.category === 'Destructive' ? 'destructive' : 'nondestructive'}`}>
                      {g.category}
                    </span>
                    <span className="tree-count">
                      {selectedInGroup}/{g.tests.length}
                    </span>
                  </span>
                </button>
              </div>

              {open && (
                <ul className="tree-children" role="group">
                  {g.tests.map((t) => {
                    const on = selectedTests.includes(t.id);
                    return (
                      <li key={t.id} className={`tree-leaf ${on ? 'on' : ''}`} role="treeitem" aria-selected={on}>
                        <span className="tree-rail" aria-hidden="true" />
                        <button
                          type="button"
                          className={`tree-cb ${on ? 'on' : ''}`}
                          onClick={() => toggleTest(g, t)}
                          aria-label={on ? 'Deselect test' : 'Select test'}
                        >
                          {on ? <Icon name="check" size={10} /> : null}
                        </button>
                        <button type="button" className="tree-row" onClick={() => toggleTest(g, t)}>
                          <span className="tree-num">{t.number}</span>
                          <span className="tree-name"><Highlight text={t.title} /></span>
                        </button>
                      </li>
                    );
                  })}
                </ul>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

function MultiSelectUsers({ data, role, value, onChange, placeholder }) {
  const eligible = data.USERS.filter((u) => u.active && (role ? u.role === role : true));
  const [open, setOpen] = useS2(false);
  const [query, setQuery] = useS2('');
  const rootRef = useR2(null);

  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  const toggle = (id) => onChange(value.includes(id) ? value.filter((x) => x !== id) : [...value, id]);
  const remove = (id, e) => { e.stopPropagation(); onChange(value.filter((x) => x !== id)); };
  const clear = (e) => { e.stopPropagation(); onChange([]); };

  const q = query.trim().toLowerCase();
  const filtered = eligible.filter((u) =>
    !q || u.name.toLowerCase().includes(q) || u.initials.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
  );
  const selectedUsers = value.map((id) => eligible.find((u) => u.id === id)).filter(Boolean);

  return (
    <div className={`ms-dd ${open ? 'open' : ''}`} ref={rootRef}>
      <button type="button" className="ms-dd-control" onClick={() => setOpen((o) => !o)}>
        <div className="ms-dd-chips">
          {selectedUsers.length === 0 && (
            <span className="ms-dd-placeholder">{placeholder || `Select ${role ? role.toLowerCase() + 's' : 'users'}…`}</span>
          )}
          {selectedUsers.map((u) => (
            <span key={u.id} className="ms-dd-chip">
              <Avatar user={u} size={16} />
              <span className="ms-dd-chip-name">{u.name}</span>
              <span className="ms-dd-chip-x" onClick={(e) => remove(u.id, e)} aria-label={`Remove ${u.name}`}>
                <Icon name="x" size={10} />
              </span>
            </span>
          ))}
        </div>
        <div className="ms-dd-trail">
          {selectedUsers.length > 0 && (
            <span className="ms-dd-clear" onClick={clear} title="Clear all" aria-label="Clear all">
              <Icon name="x" size={11} />
            </span>
          )}
          <Icon name={open ? 'chevron-down' : 'chevron-down'} size={13} />
        </div>
      </button>

      {open && (
        <div className="ms-dd-menu" role="listbox" aria-multiselectable="true">
          <div className="ms-dd-search">
            <Icon name="search" size={12} />
            <input
              autoFocus
              type="text"
              placeholder="Search by name or initials…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
            />
          </div>
          <div className="ms-dd-options">
            {filtered.length === 0 && (
              <div className="ms-dd-empty">No matches.</div>
            )}
            {filtered.map((u) => {
              const on = value.includes(u.id);
              return (
                <button type="button" key={u.id} role="option" aria-selected={on}
                  className={`ms-dd-opt ${on ? 'on' : ''}`}
                  onClick={() => toggle(u.id)}>
                  <span className={`tree-cb ${on ? 'on' : ''}`}>
                    {on ? <Icon name="check" size={10} /> : null}
                  </span>
                  <Avatar user={u} size={22} />
                  <span className="ms-dd-opt-meta">
                    <span className="ms-dd-opt-name">{u.name}</span>
                    <span className="ms-dd-opt-sub">{u.initials} · {u.role}</span>
                  </span>
                </button>
              );
            })}
          </div>
          <div className="ms-dd-foot">
            <span>{value.length} selected</span>
            <button type="button" className="tree-link" onClick={() => setOpen(false)}>Done</button>
          </div>
        </div>
      )}
    </div>
  );
}

function NewJobModal({ data, onClose, onCreate }) {
  const [step, setStep] = useS2(1);
  const [customerId, setCustomerId] = useS2('c1');
  const [partNumber, setPartNumber] = useS2('');
  const [partDescription, setPartDescription] = useS2('');
  const [manufacturer, setManufacturer] = useS2('');
  const [dateCode, setDateCode] = useS2('');
  const [lotCode, setLotCode] = useS2('');
  const [qty, setQty] = useS2(10);
  const [sampleQty, setSampleQty] = useS2(10);
  const [target, setTarget] = useS2('2026-05-15');
  const [templateId, setTemplateId] = useS2('tpl-mr-m2-full');
  const [selectedGroups, setSelectedGroups] = useS2([]);
  const [selectedTests, setSelectedTests] = useS2([]);
  const [engineerIds, setEngineerIds] = useS2(['u1']);
  const [approverIds, setApproverIds] = useS2(['u3']);

  // Apply a template: pre-select its groups + all child tests
  const applyTemplate = (tid) => {
    setTemplateId(tid);
    const tpl = data.TEST_PLAN_TEMPLATES.find((t) => t.id === tid);
    if (!tpl) return;
    const groups = data.TEST_GROUPS.filter((g) => tpl.groupIds.includes(g.id));
    const allTestIds = groups.flatMap((g) => g.tests.map((t) => t.id));
    setSelectedGroups(tpl.groupIds);
    setSelectedTests(allTestIds);
  };

  // Initialize template selection on mount
  useM2(() => { applyTemplate('tpl-mr-m2-full'); return null; }, []);

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal lg" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 900 }}>
        <div className="modal-header">
          <h3>New Job — AS6171A Counterfeit Risk Mitigation</h3>
          <button className="btn ghost sm" onClick={onClose}><Icon name="x" size={14} /></button>
        </div>
        <div style={{ display: 'flex', borderBottom: '1px solid var(--line)', background: 'var(--canvas)' }}>
          {['Customer & Component', 'Test Plan', 'Assignment', 'Review'].map((label, i) => {
            const idx = i + 1;
            const active = step === idx;
            return (
              <div key={i} style={{
                padding: '10px 16px', fontSize: 12, fontWeight: 600,
                color: active ? 'var(--dark-blue)' : 'var(--body-text)',
                borderBottom: active ? '2px solid var(--light-blue)' : '2px solid transparent',
                display: 'flex', gap: 8, alignItems: 'center'
              }}>
                <span style={{
                  width: 18, height: 18, borderRadius: '50%',
                  background: active ? 'var(--dark-blue)' : 'var(--line)',
                  color: active ? 'white' : 'var(--body-text)',
                  display: 'grid', placeItems: 'center', fontSize: 10, fontWeight: 700
                }}>{idx}</span>
                {label}
              </div>
            );
          })}
        </div>

        <div className="modal-body">
          {step === 1 && (
            <div>
              <h4 style={{ margin: '0 0 12px', color: 'var(--dark-blue)', fontSize: 13 }}>Customer &amp; component</h4>
              <div className="field-grid">
                <div className="field"><label>Customer<span className="req">*</span></label>
                  <select value={customerId} onChange={(e) => setCustomerId(e.target.value)}>
                    {data.CUSTOMERS.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
                  </select>
                </div>
                <div className="field"><label>Customer job reference<span className="req">*</span></label>
                  <input type="text" placeholder="e.g. LKH-PO-22094-A" />
                </div>
              </div>
              <div className="field-grid">
                <div className="field"><label>Part number<span className="req">*</span></label>
                  <input type="text" value={partNumber} onChange={(e) => setPartNumber(e.target.value)} placeholder="LM393DR" />
                </div>
                <div className="field"><label>Manufacturer<span className="req">*</span></label>
                  <input type="text" value={manufacturer} onChange={(e) => setManufacturer(e.target.value)} placeholder="Texas Instruments" />
                </div>
              </div>
              <div className="field"><label>Part description</label>
                <input type="text" value={partDescription} onChange={(e) => setPartDescription(e.target.value)} placeholder="e.g. Dual Differential Comparator, SOIC-8" />
              </div>
              <div className="field-grid cols-3">
                <div className="field"><label>Date code</label><input type="text" value={dateCode} onChange={(e) => setDateCode(e.target.value)} placeholder="2417" /></div>
                <div className="field"><label>Lot code</label><input type="text" value={lotCode} onChange={(e) => setLotCode(e.target.value)} placeholder="TI-2417-A88" /></div>
                <div className="field"><label>Quantity received<span className="req">*</span></label><input type="number" value={qty} onChange={(e) => setQty(+e.target.value)} /></div>
              </div>
              <div className="field"><label>Sample quantity</label><input type="number" value={sampleQty} onChange={(e) => setSampleQty(+e.target.value)} /></div>
            </div>
          )}

          {step === 2 && (
            <div>
              <h4 style={{ margin: '0 0 12px', color: 'var(--dark-blue)', fontSize: 13 }}>Test plan</h4>
              <div className="field">
                <label>Start from template</label>
                <select value={templateId} onChange={(e) => applyTemplate(e.target.value)}>
                  {data.TEST_PLAN_TEMPLATES.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
                </select>
                <div className="hint">Selecting a template pre-selects its groups and tests below. You can then deviate from it by ticking / unticking nodes.</div>
              </div>
              <div className="field">
                <label>Groups &amp; tests (treeview)</label>
                <GroupTestTree
                  groups={data.TEST_GROUPS}
                  selectedGroups={selectedGroups}
                  selectedTests={selectedTests}
                  onChange={({ selectedGroups, selectedTests }) => { setSelectedGroups(selectedGroups); setSelectedTests(selectedTests); }}
                />
                <div className="hint">Checking a group automatically checks all its tests. Checking an individual test selects just that test; the group still appears in the report.</div>
              </div>
              <div className="field"><label>Target issue date</label><input type="date" value={target} onChange={(e) => setTarget(e.target.value)} /></div>
            </div>
          )}

          {step === 3 && (
            <div>
              <h4 style={{ margin: '0 0 12px', color: 'var(--dark-blue)', fontSize: 13 }}>Assignment</h4>
              <div className="field">
                <label>Test engineers<span className="req">*</span> <span className="hint-inline">(multi-select — any selected engineer can pick up work)</span></label>
                <MultiSelectUsers data={data} role="Test Engineer" value={engineerIds} onChange={setEngineerIds} />
              </div>
              <div className="field">
                <label>Approvers<span className="req">*</span> <span className="hint-inline">(multi-select — any selected approver can review)</span></label>
                <MultiSelectUsers data={data} role="Approver" value={approverIds} onChange={setApproverIds} />
              </div>
              <div className="field"><label>Notes for engineer(s)</label><textarea rows={3} placeholder="Any context, special handling instructions, or customer requests." /></div>
            </div>
          )}

          {step === 4 && (
            <div>
              <h4 style={{ margin: '0 0 12px', color: 'var(--dark-blue)', fontSize: 13 }}>Review &amp; create</h4>
              <div className="pdf-cover-block" style={{ background: 'var(--canvas)', borderLeftColor: 'var(--light-blue)', fontSize: 11.5 }}>
                <h3>Job summary</h3>
                <div className="row"><span className="k">Customer</span><span className="v">{data.CUSTOMERS.find((c) => c.id === customerId).name}</span></div>
                <div className="row"><span className="k">Part / Manufacturer</span><span className="v">{partNumber || '—'} / {manufacturer || '—'}</span></div>
                <div className="row"><span className="k">Date / Lot</span><span className="v">{dateCode || '—'} / {lotCode || '—'}</span></div>
                <div className="row"><span className="k">Test plan</span><span className="v">{selectedGroups.length} group{selectedGroups.length === 1 ? '' : 's'}, {selectedTests.length} test{selectedTests.length === 1 ? '' : 's'}</span></div>
                <div className="row"><span className="k">Sample</span><span className="v">{sampleQty} device{sampleQty === 1 ? '' : 's'}</span></div>
                <div className="row"><span className="k">Target issue</span><span className="v">{target}</span></div>
                <div className="row"><span className="k">Engineers</span><span className="v">{engineerIds.map((id) => data.USERS.find((u) => u.id === id).name).join(', ')}</span></div>
                <div className="row"><span className="k">Approvers</span><span className="v">{approverIds.map((id) => data.USERS.find((u) => u.id === id).name).join(', ')}</span></div>
              </div>
              <div style={{ fontSize: 11, color: 'var(--body-text)', marginTop: 10 }}>
                Creating the job will assign report number <strong style={{ color: 'var(--dark-blue)' }}>FT-CR-2026-0187</strong>, register {sampleQty} devices (D-01 → D-{String(sampleQty).padStart(2,'0')}), and create planned records for {selectedGroups.length} groups.
              </div>
            </div>
          )}
        </div>
        <div className="modal-footer">
          {step > 1 && <button className="btn" onClick={() => setStep(step - 1)}><Icon name="arrow-left" size={12} /> Back</button>}
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          {step < 4 && <button className="btn primary" onClick={() => setStep(step + 1)}>Next <Icon name="arrow-right" size={12} /></button>}
          {step === 4 && <button className="btn primary" onClick={onCreate}><Icon name="check" size={12} /> Create job</button>}
        </div>
      </div>
    </div>
  );
}

// =====================================================================
// APPROVE / REJECT MODALS
// =====================================================================
function RejectModal({ onCancel, onConfirm }) {
  const [reason, setReason] = useS2('');
  return (
    <div className="modal-backdrop" onClick={onCancel}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 540 }}>
        <div className="modal-header"><h3>Return for revision</h3>
          <button className="btn ghost sm" onClick={onCancel}><Icon name="x" size={14} /></button>
        </div>
        <div className="modal-body">
          <p style={{ fontSize: 12.5, color: 'var(--body-text)', marginTop: 0 }}>
            The report will return to the engineer's queue with status <strong>Rejected</strong>. They'll receive a notification with your feedback.
          </p>
          <div className="field">
            <label>Feedback for the engineer<span className="req">*</span></label>
            <textarea rows={5} value={reason} onChange={(e) => setReason(e.target.value)} placeholder="Explain what needs to change. Be specific — reference group numbers and figure IDs where relevant." />
          </div>
        </div>
        <div className="modal-footer">
          <button className="btn" onClick={onCancel}>Cancel</button>
          <button className="btn danger" disabled={!reason.trim()} onClick={() => onConfirm(reason)}><Icon name="x-circle" size={12} /> Reject &amp; return</button>
        </div>
      </div>
    </div>
  );
}

function ApproveModal({ onCancel, onConfirm }) {
  return (
    <div className="modal-backdrop" onClick={onCancel}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 480 }}>
        <div className="modal-header"><h3>Approve report</h3>
          <button className="btn ghost sm" onClick={onCancel}><Icon name="x" size={14} /></button>
        </div>
        <div className="modal-body">
          <p style={{ fontSize: 12.5, color: 'var(--body-text)', marginTop: 0 }}>
            By approving, you confirm the report is ready for issue. The status will change to <strong>Approved</strong>; you (or another approver) can issue the PDF to the customer from the job page.
          </p>
          <label className="cb"><input type="checkbox" defaultChecked /><span className="box"></span>I have reviewed the report content and per-group sections</label>
          <div style={{ height: 6 }} />
          <label className="cb"><input type="checkbox" defaultChecked /><span className="box"></span>All equipment calibration is current</label>
        </div>
        <div className="modal-footer">
          <button className="btn" onClick={onCancel}>Cancel</button>
          <button className="btn success" onClick={onConfirm}><Icon name="check" size={12} /> Approve</button>
        </div>
      </div>
    </div>
  );
}

window.TestEntry = TestEntry;
window.PdfPreview = PdfPreview;
window.NewJobModal = NewJobModal;
window.RejectModal = RejectModal;
window.ApproveModal = ApproveModal;
