/* =====================================================================
   AI-COS — Universal record detail drawer + registry
   window.openDetail(type, data) opens a rich detail page for any list row.
   ===================================================================== */
let _detailListener = null;
function openDetail(type, data) { if (window._openDetail) window._openDetail(type, data); else if (_detailListener) _detailListener(type, data); }

/* ---- small building blocks ---- */
function KV({ items }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 20px' }}>
      {items.map(([k, v, mono], i) => (
        <div key={i}><div className="eyebrow" style={{ fontSize: 9 }}>{k}</div><div style={{ fontSize: 12.5, fontWeight: 650, marginTop: 2 }} className={mono ? 'mono' : ''}>{v}</div></div>
      ))}
    </div>
  );
}
function Timeline({ steps, current }) {
  return (
    <div style={{ position: 'relative' }}>
      {steps.map((s, i) => {
        const done = i < current, now = i === current;
        return (
          <div key={i} className="row" style={{ gap: 12, alignItems: 'flex-start', paddingBottom: i < steps.length - 1 ? 14 : 0, position: 'relative' }}>
            {i < steps.length - 1 && <span style={{ position: 'absolute', left: 12, top: 26, bottom: 0, width: 2, background: done ? 'var(--ok)' : 'var(--border-2)' }} />}
            <span style={{ width: 26, height: 26, borderRadius: '50%', flex: 'none', zIndex: 1, display: 'grid', placeItems: 'center', background: done ? 'var(--ok)' : now ? 'var(--primary-600)' : '#eef1f6', color: done || now ? '#fff' : 'var(--text-3)' }}>
              {done ? <Icon name="check" size={13} /> : <span style={{ fontSize: 10, fontWeight: 700 }}>{i + 1}</span>}
            </span>
            <div style={{ paddingTop: 3 }}><div style={{ fontSize: 12.5, fontWeight: now ? 700 : 600, color: now ? 'var(--ink)' : done ? 'var(--text)' : 'var(--text-3)' }}>{s}</div>{now && <div className="faint" style={{ fontSize: 11 }}>Current stage</div>}</div>
          </div>
        );
      })}
    </div>
  );
}
function Card({ title, icon, children }) {
  return (
    <div className="card">
      {title && <div className="card-head"><Icon name={icon || 'grid'} size={15} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 13.5 }}>{title}</div></div>}
      <div className="card-body">{children}</div>
    </div>
  );
}

/* ---- customer communications / notes log (CRM-07) ---- */
function custComms(c, DB) {
  const T = DB.TODAY, day = 864e5;
  return [
    { d: DB.rel(new Date(T - 2 * day)), type: 'Call', who: 'Savad Ahammed', text: `Discussed the upcoming AMC renewal and 6-monthly calibration plan with ${c.contact}.` },
    { d: DB.rel(new Date(T - 6 * day)), type: 'Meeting', who: 'Lina Haddad', text: `Site visit — reviewed ${c.assets} monitors and gas-detection coverage; no concerns raised.` },
    { d: DB.rel(new Date(T - 14 * day)), type: 'Email', who: 'System', text: `Prequalification expiry reminder sent — renew before ${DB.fmt(c.prequalExp)}.` },
    { d: DB.rel(new Date(T - 23 * day)), type: 'Commitment', who: 'Savad Ahammed', text: 'Agreed a 2-day turnaround for emergency bump tests under the contract.' },
  ];
}
const COMM_TONE = { Call: 'info', Meeting: 'ai', Email: 'neutral', Commitment: 'warn' };

/* ---- Vendor/customer documents — upload, list, download, delete (cal-docs) ---- */
const DOC_TYPES = ['CR Certificate', 'Trade License', 'VAT Certificate', 'Contract / Agreement', 'ISO Certificate', 'Other'];
function CustomerDocs({ customer }) {
  const [docs, setDocs] = useState(null);
  const [busy, setBusy] = useState(false);
  const [docType, setDocType] = useState('CR Certificate');
  const [err, setErr] = useState(null);
  const fileRef = useRef(null);
  const load = () => calDocs({ action: 'list', ownerType: 'customer', ownerId: customer.id })
    .then((r) => { setDocs(r.docs || []); setErr(null); })
    .catch((e) => { setDocs([]); setErr(e.message); });
  useEffect(() => { load(); }, [customer.id]);

  const onPick = (e) => {
    const f = e.target.files && e.target.files[0];
    if (!f) return;
    if (f.size > 3 * 1024 * 1024) { setErr('“' + f.name + '” is larger than 3 MB — please upload a smaller file (compress the PDF or split it).'); e.target.value = ''; return; }
    setErr(null); setBusy(true);
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const data = String(reader.result).split(',')[1] || '';
        await calDocs({ action: 'upload', ownerType: 'customer', ownerId: customer.id, docType, name: f.name, mime: f.type, size: f.size, data });
        window.toast('“' + f.name + '” saved to ' + customer.name + '\'s profile.', 'ok');
        load();
      } catch (ex) { setErr(ex.message); window.toast('Upload failed — ' + ex.message, 'bad'); }
      finally { setBusy(false); if (fileRef.current) fileRef.current.value = ''; }
    };
    reader.onerror = () => { setErr('Could not read that file. Please try again.'); setBusy(false); };
    reader.readAsDataURL(f);
  };
  const download = async (d) => {
    try {
      const r = await calDocs({ action: 'get', id: d.id });
      const bin = atob(r.data); const arr = new Uint8Array(bin.length);
      for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
      const url = URL.createObjectURL(new Blob([arr], { type: r.mime || 'application/octet-stream' }));
      const a = document.createElement('a'); a.href = url; a.download = r.name || d.name; a.click();
      URL.revokeObjectURL(url);
    } catch (ex) { window.toast('Could not download the document — ' + ex.message, 'bad'); }
  };
  const del = async (d) => {
    if (!window.confirm('Delete “' + d.name + '”? This permanently removes it from the vendor profile.')) return;
    try { await calDocs({ action: 'delete', id: d.id }); window.toast('Document deleted', 'ok'); load(); }
    catch (ex) { window.toast(ex.message, 'bad'); }
  };
  const fmtSize = (n) => !n ? '' : n >= 1048576 ? (n / 1048576).toFixed(1) + ' MB' : Math.max(1, Math.round(n / 1024)) + ' KB';
  const sel = { fontFamily: 'inherit', fontSize: 12, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--border-2)', background: 'var(--surface)', fontWeight: 600 };

  return (
    <div>
      <div className="row" style={{ gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        <select value={docType} onChange={(e) => setDocType(e.target.value)} style={sel}>{DOC_TYPES.map((t) => <option key={t}>{t}</option>)}</select>
        <input ref={fileRef} type="file" style={{ display: 'none' }} onChange={onPick} />
        <Btn size="sm" variant="primary" icon="download" onClick={() => !busy && fileRef.current && fileRef.current.click()} style={busy ? { opacity: .6 } : null}>{busy ? 'Uploading…' : 'Upload document'}</Btn>
        <span className="faint" style={{ fontSize: 11 }}>PDF, image or Office file · up to 3 MB</span>
      </div>
      {err && <div style={{ background: 'var(--bad-bg)', border: '1px solid var(--bad-line)', color: 'var(--bad)', borderRadius: 8, padding: '8px 11px', fontSize: 12, marginBottom: 10 }}>{err}</div>}
      {docs === null ? (
        <div className="faint" style={{ fontSize: 12.5, padding: '6px 0' }}>Loading documents…</div>
      ) : docs.length === 0 ? (
        <div className="faint" style={{ fontSize: 12.5, padding: '6px 0' }}>No documents yet. Upload the Commercial Registration (CR) certificate and any supporting files — they're kept here for retrieval and traceability.</div>
      ) : (
        <div>
          {docs.map((d) => (
            <div key={d.id} className="row" style={{ gap: 10, padding: '9px 0', borderBottom: '1px solid var(--border)', alignItems: 'center' }}>
              <span className="kpi-ico" style={{ width: 30, height: 30, background: 'var(--bg)', color: 'var(--primary-600)', flex: 'none' }}><Icon name="documents" size={15} /></span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12.5, fontWeight: 650, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.name}</div>
                <div className="faint" style={{ fontSize: 11, marginTop: 1 }}>{d.doc_type || 'Other'}{d.size ? ' · ' + fmtSize(d.size) : ''}{d.uploaded_by ? ' · ' + d.uploaded_by : ''}{d.uploaded_at ? ' · ' + window.DB.fmt(new Date(d.uploaded_at)) : ''}</div>
              </div>
              <Badge kind={d.doc_type === 'CR Certificate' ? 'info' : 'neutral'} style={{ flex: 'none' }}>{d.doc_type === 'CR Certificate' ? 'CR' : (d.doc_type || 'Doc').split(' ')[0]}</Badge>
              <Btn size="sm" variant="ghost" icon="download" onClick={() => download(d)}>Download</Btn>
              <Btn size="sm" variant="ghost" style={{ color: 'var(--bad)' }} onClick={() => del(d)}>Delete</Btn>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ---- per-type renderers ---- */
const DETAIL = {
  cylinder(c, DB) {
    const pct = Math.round((c.pressure / c.p0) * 100);
    return {
      icon: 'cylinder', color: 'var(--primary-600)', title: c.id, sub: `${c.mix} · ${c.conc}`, status: c.status,
      fields: [['Gas mixture', c.mix], ['Certified concentration', c.conc, 1], ['Lot / batch', c.lot, 1], ['Supplier', c.supplier], ['Fill date', DB.fmt(c.fill)], ['Expiry date', DB.fmt(c.exp)]],
      blocks: [
        { h: 'Pressure & content', icon: 'gauge', node: (
          <div><div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}><span className="faint" style={{ fontSize: 12 }}>Current pressure</span><b className="mono">{c.pressure} / {c.p0} psi</b></div><Bar pct={pct} color={pct < 25 ? 'var(--bad)' : pct < 50 ? 'var(--warn)' : 'var(--ok)'} /><div className="faint" style={{ fontSize: 11, marginTop: 6 }}>{pct}% remaining · {c.status === 'Low pressure' ? 'reorder suggested' : c.status === 'Expired' ? 'hard-blocked from worksheets' : 'available for compatible monitors'}</div></div>
        ) },
        { h: 'Usage log', icon: 'history', list: [['Today 09:42', 'WS-26-3187 · H₂S span', '−40 psi'], ['Yesterday', 'WS-26-3180 · span', '−35 psi'], ['04 Jun', 'WS-26-3171 · span', '−38 psi']] },
      ],
      note: c.status === 'Expired' ? 'Expired calibration gas invalidates results — this cylinder is hard-blocked from every worksheet (CYL-03).' : null,
    };
  },
  labasset(a, DB) {
    return {
      icon: 'gauge', color: 'var(--primary-600)', title: a.id, sub: a.name, status: a.status,
      fields: [['Category', a.category], ['Serial', a.serial, 1], ['Location', a.location], ['Criticality', a.criticality], ['Last calibration', DB.fmt(a.last)], ['Next due', `${DB.fmt(a.due)} · ${a.dueRel}`]],
      blocks: [{ h: 'Calibration & PM history', icon: 'history', list: [[DB.fmt(a.last), 'Calibrated · in-cal certificate', 'Pass'], ['Quarterly', 'PM service completed', 'OK'], ['—', `Frequency: every ${a.freq} months`, '']] }],
      note: a.status === 'Overdue' ? 'Overdue reference standards are blocked from worksheet selection until re-calibrated (CYL-05).' : null,
    };
  },
  asset(m, DB) {
    const c = DB.custById(m.customer);
    const certs = DB.certificates.filter((x) => x.monitor === m.id);
    return {
      icon: 'cpu', color: 'var(--primary-600)', title: m.id, sub: `${m.model} · S/N ${m.serial}`, status: m.status,
      fields: [['Model', m.model], ['Serial', m.serial, 1], ['Customer', c.name], ['Sensors', m.sensors.join(' · ')], ['Last calibration', DB.fmt(m.last)], ['Next due', `${DB.fmt(m.due)} · ${m.dueRel}`]],
      blocks: [
        { h: 'Sensor configuration', icon: 'cpu', node: <div className="wrap" style={{ gap: 6 }}>{m.sensors.map((s) => <span key={s} className="tag">{s} sensor</span>)}</div> },
        { h: 'Certificates', icon: 'certificate', list: certs.length ? certs.map((x) => [x.id, x.method, x.status]) : [['—', 'No certificates on record yet', '']] },
      ],
    };
  },
  customer(c, DB) {
    const mons = DB.monitors.filter((m) => m.customer === c.id);
    const certs = DB.certificates.filter((x) => x.customer === c.id);
    const corr = (DB.reminderLog || []).filter((l) => l.customer === c.id);
    const crExp = c.crExpiry ? new Date(c.crExpiry) : null;
    const crStat = crExp ? (crExp < DB.TODAY ? 'expired' : (crExp - DB.TODAY) / 864e5 < 60 ? 'expiring' : 'valid') : null;
    const crText = crExp ? DB.fmt(crExp) + (crStat === 'expired' ? ' · EXPIRED' : crStat === 'expiring' ? ' · expiring soon' : '') : '—';
    return {
      icon: 'building', color: c.color || 'var(--primary-600)', title: c.name,
      sub: [c.code, c.sector, c.site].filter(Boolean).join(' · ') || 'Vendor',
      status: crStat === 'expired' ? 'Expired' : (c.tier || null),
      fields: [
        ['Vendor code', c.code || '—', 1],
        ['CR number', c.crNumber || '—', 1],
        ['CR expiry', crText],
        ['Sector', c.sector || '—'],
        ['Agreement', c.tier || '—'],
        ['Primary contact', c.contact || '—'],
        ['Contact email', c.email || '—'],
        ['Assets under management', String(mons.length)],
      ],
      blocks: [
        { h: 'Instruments (' + mons.length + ')', icon: 'cpu', node: (
          mons.length ? (
            <div>
              {mons.map((m, i) => {
                const st = m.status === 'Active' ? 'Valid' : m.status;
                const due = m.due ? new Date(m.due) : null;
                const overdue = st !== 'Retired' && due && due < DB.TODAY;
                return (
                  <div key={m.id} className="row" style={{ gap: 10, padding: '8px 0', borderBottom: i < mons.length - 1 ? '1px solid var(--border)' : 'none', cursor: 'pointer' }} onClick={() => window.openDetail('asset', m)}>
                    <span className="mono cell-strong" style={{ fontSize: 11.5, width: 116, flex: 'none' }}>{m.id}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 12.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.model}</div>
                      <div className="faint" style={{ fontSize: 11 }}>{(m.sensors || []).join(' · ')}</div>
                    </div>
                    <span style={{ fontSize: 11.5, fontWeight: 600, flex: 'none', color: overdue ? 'var(--bad)' : 'var(--text-2)', marginRight: 4 }}>{st === 'Retired' ? '—' : due ? 'Due ' + DB.fmt(due) : '—'}</span>
                    <StatusBadge status={st} />
                  </div>
                );
              })}
            </div>
          ) : <div className="faint" style={{ fontSize: 12.5, padding: '4px 0' }}>No instruments registered for this vendor yet.</div>
        ) },
        { h: 'Certificates (' + certs.length + ')', icon: 'certificate', node: (
          certs.length ? (
            <div>
              {certs.slice(0, 10).map((x, i) => {
                const exp = x.certExpiry || x.due; const expD = exp ? new Date(exp) : null;
                const expired = x.status === 'Valid' && expD && expD < DB.TODAY;
                return (
                  <div key={x.id} className="row" style={{ gap: 10, padding: '8px 0', borderBottom: i < Math.min(certs.length, 10) - 1 ? '1px solid var(--border)' : 'none' }}>
                    <span className="mono cell-strong" style={{ fontSize: 11.5, color: 'var(--primary-700)', width: 132, flex: 'none' }}>{x.id}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 12.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{x.model || x.monitor}</div>
                      <div className="faint" style={{ fontSize: 11 }}>{expD ? 'Valid until ' + DB.fmt(expD) : ''}{expired ? ' · EXPIRED' : ''}</div>
                    </div>
                    <StatusBadge status={expired ? 'Expired' : x.status} />
                  </div>
                );
              })}
            </div>
          ) : <div className="faint" style={{ fontSize: 12.5, padding: '4px 0' }}>No certificates issued for this vendor yet.</div>
        ) },
        { h: 'Vendor documents', icon: 'documents', node: <CustomerDocs customer={c} /> },
        { h: 'Correspondence', icon: 'history', node: (
          corr.length ? (
            <div>
              {corr.slice(0, 8).map((m, i) => (
                <div key={m.id || i} className="row" style={{ gap: 11, padding: '9px 0', alignItems: 'flex-start', borderBottom: i < Math.min(corr.length, 8) - 1 ? '1px solid var(--border)' : 'none' }}>
                  <Badge kind={m.status === 'Sent' ? 'ok' : m.status === 'Queued' ? 'warn' : m.status === 'Failed' ? 'bad' : 'neutral'} style={{ flex: 'none', width: 70, justifyContent: 'center' }}>{m.status || 'Sent'}</Badge>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, lineHeight: 1.45 }}>{m.subject || m.kind || 'Email'}</div>
                    <div className="faint" style={{ fontSize: 11, marginTop: 2 }}>{m.to || c.email || ''}{m.by ? ' · ' + m.by : ''}{m.date ? ' · ' + DB.fmt(new Date(m.date)) : ''}</div>
                  </div>
                </div>
              ))}
            </div>
          ) : <div className="faint" style={{ fontSize: 12.5, padding: '4px 0' }}>No correspondence yet. Reminders and notifications sent to this vendor are logged here for traceability.</div>
        ) },
      ].filter(Boolean),
      note: crStat === 'expired' ? 'This vendor\'s Commercial Registration has expired — request a renewed CR certificate before processing new work.'
        : crStat === 'expiring' ? 'This vendor\'s CR expires within 60 days — request a renewal to keep the profile current.' : null,
    };
  },
  contract(ct, DB) {
    const c = DB.custById(ct.customer);
    return {
      icon: 'briefcase', color: c.color, title: ct.id, sub: `${c.name} · ${ct.period}`, status: ct.status,
      preview: <ContractPreview ct={ct} />,
      fields: [['Customer', c.name], ['Covered assets', `${ct.assets}`], ['Frequency', ct.freq], ['Annual value', `${ct.value.toLocaleString()} OMR`], ['Period', ct.period], ['Renewal', `${DB.fmt(ct.renew)} · ${DB.rel(ct.renew)}`]],
      note: 'Covered equipment is auto-planned across the contract period — no manual scheduling (CRM-05).',
    };
  },
  capa(x, DB) {
    const stages = ['Containment', 'Root cause', 'Action', 'Verification', 'Closed'];
    const cur = Math.max(0, stages.indexOf(x.stage));
    return {
      icon: 'check-shield', color: x.severity === 'Major' ? 'var(--bad)' : 'var(--warn)', title: x.id, sub: x.title, status: x.status,
      fields: [['Source', x.source], ['Severity', x.severity], ['Owner', DB.staffById(x.owner).name], ['Due date', `${DB.fmt(x.due)} · ${DB.rel(x.due)}`]],
      blocks: [{ h: 'CAPA workflow', icon: 'refresh', node: <Timeline steps={stages} current={x.status === 'Closed' ? 5 : cur} /> }],
      note: 'AI proposes root-cause candidates from recurring-issue detection — confirmed or replaced by the Quality Manager (AIX-03).',
    };
  },
  inventory(it, DB) {
    return {
      icon: 'box', color: 'var(--primary-600)', title: it.id, sub: it.name, status: it.status,
      fields: [['Category', it.cat], ['On hand', `${it.qty} ${it.uom}`], ['Minimum level', `${it.min} ${it.uom}`], ['Preferred vendor', it.vendor], ['Batch / lot', it.batch, 1], ['Lot traceable', 'Yes — recall-ready']],
      blocks: [{ h: 'Recent movements', icon: 'history', list: [['Today', 'Issued to JOB-26-0411', '−1'], ['03 Jun', 'GRN received', '+12'], ['28 May', 'Issued to repair', '−2']] }],
      note: it.status === 'Low' ? 'Below minimum stock — AI suggests a reorder; sensor-failure prediction may raise demand next cycle (AIX-07).' : null,
    };
  },
  invoice(iv, DB) {
    const c = DB.custById(iv.customer);
    const net = iv.amount / (1 + iv.vat / 100);
    return {
      icon: 'receipt', color: 'var(--primary-600)', title: iv.id, sub: `${c.name}`, status: iv.status,
      preview: <InvoicePreview iv={iv} />,
      fields: [['Customer', c.name], ['Issue date', DB.fmt(iv.date)], ['Due date', DB.fmt(iv.due)], ['Payment status', iv.status]],
      blocks: [{ h: 'Amounts (OMR)', icon: 'receipt', node: (
        <div>{[['Net', net.toFixed(3)], [`VAT (${iv.vat}%)`, (iv.amount - net).toFixed(3)], ['Total', iv.amount.toFixed(3)]].map(([k, v], i) => (
          <div key={i} className="row" style={{ justifyContent: 'space-between', padding: '5px 0', borderTop: i === 2 ? '1px solid var(--border)' : 'none', marginTop: i === 2 ? 4 : 0 }}><span className={i === 2 ? 'cell-strong' : 'faint'} style={{ fontSize: 12.5 }}>{k}</span><b className="mono" style={{ fontSize: i === 2 ? 14 : 12.5 }}>{v}</b></div>
        ))}</div>
      ) }],
      note: 'Issued invoices are immutable — corrections are made via credit note. Numbering is gap-free per series (NFR-17).',
    };
  },
  document(d, DB) {
    return {
      icon: 'documents', color: 'var(--primary-600)', title: d.code, sub: d.title, status: d.status,
      fields: [['Type', d.type], ['Level', d.level], ['Version', d.ver], ['Owner', DB.staffById(d.owner).name], ['Next review', `${DB.fmt(d.reviewDate)} · ${d.reviewRel}`], ['Read-acknowledgment', `${d.ack}%`]],
      blocks: [
        { h: 'Approval workflow', icon: 'refresh', node: <Timeline steps={['Draft', 'Review', 'Approve', 'Publish']} current={d.status === 'Published' ? 4 : d.status === 'In review' ? 1 : 0} /> },
        { h: 'Revision history', icon: 'history', list: [[d.ver, 'Current — published', 'Active'], ['v' + (parseInt(d.ver.replace(/\D/g, '')) || 1 - 1 || ''), 'Superseded — watermarked', 'Obsolete']] },
      ],
    };
  },
  dcr(d, DB) {
    return { icon: 'refresh', color: 'var(--primary-600)', title: d.id, sub: `Change to ${d.doc}`, status: d.status,
      fields: [['Target document', d.doc, 1], ['Requested by', DB.staffById(d.by).name], ['Date', DB.fmt(d.date)], ['Status', d.status]],
      blocks: [{ h: 'Requested change', icon: 'documents', node: <div className="muted" style={{ fontSize: 13 }}>{d.desc}</div> }] };
  },
  method(m, DB) {
    return {
      icon: 'flask', color: 'var(--ai)', title: m.id, sub: `${m.title} · ${m.rev}`, status: m.status,
      fields: [['Title', m.title], ['Revision', m.rev], ['Owner', DB.staffById(m.owner).name], ['Points', m.points], ['Validated', m.validated ? 'Yes' : 'Pending'], ['MU budget', m.mu ? 'Linked' : 'Not linked']],
      blocks: [{ h: 'Method lifecycle', icon: 'refresh', node: <Timeline steps={['Draft', 'Validated', 'Active', 'Obsolete']} current={m.status === 'Active' ? 2 : m.status === 'In validation' ? 0 : 0} /> }],
      note: 'Each worksheet binds to a specific method revision; in-flight jobs finish on their bound revision (LAB-01).',
    };
  },
  pt(p, DB) {
    return { icon: 'trend', color: 'var(--primary-600)', title: p.id, sub: `${p.provider} · ${p.scheme}`, status: p.result,
      fields: [['Provider', p.provider], ['Scheme', p.scheme], ['Parameter', p.param, 1], ['Date', DB.fmt(p.date)], ['z-score', p.z.toFixed(1)], ['Evaluation', p.result]],
      note: p.capa ? `Questionable result — ${p.capa} opened automatically to investigate bias (LAB-04).` : 'Satisfactory — evidence of result validity maintained.' };
  },
  hse(x, DB) {
    return { icon: 'helmet', color: x.type === 'Incident' ? 'var(--bad)' : 'var(--warn)', title: x.id, sub: x.title, status: x.status,
      fields: [['Type', x.type], ['Severity', x.severity], ['Date', DB.fmt(x.date)], ['Linked CAPA', x.capa || '—', 1]],
      blocks: [{ h: 'Investigation', icon: 'refresh', node: <Timeline steps={['Reported', 'Investigation', 'Root cause', 'Closed']} current={x.status === 'Closed' ? 4 : 1} /> }],
      note: 'HSE findings open NCRs in the same shared register as quality — one improvement loop for the whole system.' };
  },
  risk(r, DB) {
    return { icon: 'shield', color: 'var(--primary-600)', title: r.id, sub: r.area, status: r.status,
      fields: [['Area / task', r.area], ['Next review', `${DB.fmt(r.review)} · ${DB.rel(r.review)}`]],
      blocks: [{ h: 'Controls', icon: 'check-shield', node: <div className="muted" style={{ fontSize: 13 }}>{r.controls}</div> }] };
  },
  pm(p, DB) {
    return { icon: 'wrench', color: p.crit === 'Critical' ? 'var(--bad)' : 'var(--primary-600)', title: p.id, sub: p.equip, status: p.status,
      fields: [['Equipment', p.equip], ['Task', p.task], ['Criticality', p.crit], ['Assignee', DB.staffById(p.assignee).name], ['Due', `${DB.fmt(p.due)} · ${DB.rel(p.due)}`]],
      note: p.status === 'Overdue' ? 'Overdue critical PM blocks this equipment from worksheet selection until serviced (PM-04).' : null };
  },
  role(r, DB) {
    const people = DB.staff.filter((s) => s.role.includes(r.role.split(' ')[0]) || r.role.includes(s.role.split(' ')[0]));
    return { icon: 'users', color: r.color, title: r.role, sub: `${r.count} user${r.count > 1 ? 's' : ''}`, status: 'Active',
      fields: [['Permissions', r.perms], ['Users assigned', `${r.count}`]],
      blocks: [{ h: 'Members', icon: 'users', node: people.length ? <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>{people.map((s) => <div key={s.id} className="row" style={{ gap: 9 }}><Avatar id={s.id} size="sm" /><span style={{ fontSize: 12.5, fontWeight: 600 }}>{s.name}</span></div>)}</div> : <div className="faint" style={{ fontSize: 12.5 }}>Assigned across the organization</div> }],
      note: 'Segregation of duties is enforced — e.g. a technician can never approve their own work (ISO 17025 §7.7).' };
  },
  fieldjob(f, DB) {
    const c = DB.custById(f.customer);
    return { icon: 'truck', color: c.color, title: f.id, sub: f.site, status: f.status,
      fields: [['Customer', c.name], ['Site', f.site], ['Technician', DB.staffById(f.tech).name], ['GPS stamp', f.gps, 1], ['Items', `${f.items}`], ['Synced', `${f.synced} / ${f.items}`]],
      blocks: [{ h: 'Offline job pack', icon: 'download', node: <div><div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}><span className="faint" style={{ fontSize: 12 }}>Sync progress</span><b className="mono">{Math.round((f.synced / f.items) * 100)}%</b></div><Bar pct={(f.synced / f.items) * 100} color="var(--ok)" /><div className="faint" style={{ fontSize: 11, marginTop: 6 }}>{f.offline ? 'Encrypted local store · single-owner pack' : 'Fully synced'}</div></div> }],
      note: 'Same authorization, standards-validity and environment gates apply on the tablet; idempotent sync with conflict detection (NFR-13).' };
  },
  ptw(p, DB) {
    return { icon: 'flag', color: 'var(--primary-600)', title: p.id, sub: p.job, status: p.status,
      fields: [['Work / location', p.job], ['Permit type', p.type], ['Validity', p.validity], ['Status', p.status]],
      note: 'Permit-to-work is linked to field jobs with validity windows (HSE-04).' };
  },
  sds(s, DB) {
    return { icon: 'flask', color: 'var(--warn)', title: s.id, sub: s.chemical, status: s.review <= DB.TODAY ? 'Due soon' : 'Active',
      fields: [['Chemical', s.chemical], ['Supplier', s.supplier], ['Next review', `${DB.fmt(s.review)} · ${DB.rel(s.review)}`]],
      note: 'Calibration gases are tracked in the SDS register with review dates (HSE-05).' };
  },
  person(c, DB) {
    const s = DB.staffById(c.staff);
    const gl = { S: 'Sign', R: 'Review', E: 'Execute', '-': '—' };
    return { icon: 'users', color: s.color, title: s.name, sub: s.role, status: 'Authorized',
      fields: [['Role', s.role], ['OEM certifications', c.oem], ['Authorization expiry', `${DB.fmt(c.exp)} · ${DB.rel(c.exp)}`]],
      blocks: [{ h: 'Method grants', icon: 'check-shield', node: <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>{Object.entries(c.grants).map(([m, g]) => <div key={m} className="row" style={{ justifyContent: 'space-between' }}><span className="mono" style={{ fontSize: 12 }}>{m}</span><Badge kind={g === 'S' ? 'ok' : g === 'R' ? 'info' : g === 'E' ? 'warn' : 'neutral'}>{gl[g]}</Badge></div>)}</div> }],
      note: 'Authorization to execute, review and sign are separate grants; expiry automatically removes the grant (CTY-01/02).' };
  },
  cert(ct, DB) {
    const c = DB.custById(ct.customer);
    return { icon: 'certificate', color: ct.bump ? 'var(--warn)' : 'var(--primary-600)', title: ct.id, sub: `${ct.model} · ${c.name}`, status: ct.status,
      preview: <CertPreview ct={ct} />,
      fields: [['Equipment', ct.model], ['Asset ID', ct.monitor, 1], ['Customer', c.name], ['Method', ct.method], ['Uncertainty', ct.mu], ['Issued', DB.fmt(ct.issued)]],
      blocks: [{ h: 'Issuance lifecycle', icon: 'history', list: [['Composed', 'auto from worksheet', 'OK'], ['Hashed', 'tamper-evident', 'OK'], ['Published', 'portal + email', 'OK']] }],
      note: ct.status === 'Superseded' ? 'Superseded by a later issue; original immutable. QR verification returns Superseded and offers the current version.' : 'One approval composed, numbered, rendered, hashed, archived & published this certificate (CTX-04).' };
  },
  quotation(qt, DB) {
    const c = DB.custById(qt.customer);
    return { icon: 'receipt', color: 'var(--warn)', title: qt.id, sub: qt.title, status: qt.status,
      preview: <QuotationPreview qt={qt} />,
      fields: [['Customer', c.name], ['Items', `${qt.items}`], ['Value', `${qt.value.toLocaleString()} OMR`], ['Valid until', `${DB.fmt(qt.validity)} · ${DB.rel(qt.validity)}`], ['Status', qt.status], ['Created', DB.fmt(qt.date)]],
      note: 'On acceptance, converts to a one-off job or AMC contract; covered equipment is auto-scheduled (CRM-03/05).' };
  },
  audit(a, DB) {
    return { icon: 'history', color: 'var(--primary-600)', title: a.entity, sub: a.action, status: 'Logged',
      fields: [['Timestamp', a.ts], ['User', a.user === 'system' ? 'System (automated)' : DB.staffById(a.user)?.name], ['Action', a.action], ['Entity', a.entity, 1]],
      note: 'The audit trail is immutable — every create, change, approval and AI confirmation is recorded for assessment (ADM).' };
  },
};

function RecordPage({ type, data, onBack, onNav }) {
  const DB = window.DB;
  const r = (DETAIL[type] || DETAIL.labasset)(data, DB);
  const hasPreview = !!r.preview;
  const flows = {
    quotation: [['Accept & convert to job', 'check', 'primary', () => window.toast('Quotation accepted → JOB-26-0413 created → draft invoice queued')], ['Revise & resend', 'refresh', 'ghost', () => window.toast('Revision drafted under version control', 'info')]],
    contract: [['Generate AMC invoice', 'receipt', 'primary', () => window.toast('Draft invoice generated from AMC schedule')], ['View covered assets', 'cpu', 'ghost', () => { onBack(); window._navTo && window._navTo('assets'); }]],
    cert: [['Print / PDF', 'documents', 'primary', () => window.printCertificate && window.printCertificate(data)], ['Open certificates', 'certificate', 'ghost', () => { onBack(); window._navTo && window._navTo('certs'); }]],
    invoice: [['Record payment', 'check', 'primary', () => window.toast('Payment recorded · invoice marked Paid')], ['Send to customer', 'ext', 'ghost', () => window.toast('Invoice emailed to customer', 'info')]],
    capa: [['Open in NCR & CAPA', 'arrowR', 'primary', () => { onBack(); window._navTo && window._navTo('qms'); }]],
    customer: [['Edit vendor', 'signature', 'primary', () => window.openCustomerForm && window.openCustomerForm(data)], ['Book a calibration job', 'workflow', 'ghost', () => { onBack(); window.newJobFlow && window.newJobFlow(); }]],
  }[type];
  const sideCard = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      {flows && (
        <div className="card card-pad">
          <div className="eyebrow" style={{ marginBottom: 10 }}>Actions</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {flows.map(([label, icon, variant, fn], i) => <Btn key={i} variant={variant} icon={icon} onClick={fn}>{label}</Btn>)}
          </div>
        </div>
      )}
      <Card title="Details" icon={r.icon}><KV items={r.fields} /></Card>
      {(r.blocks || []).map((b, i) => (
        <Card key={i} title={b.h} icon={b.icon}>
          {b.node ? b.node : (
            <div>{b.list.map((row, j) => (
              <div key={j} className="lrow" style={{ padding: '9px 0' }}>
                <span className="mono cell-strong" style={{ fontSize: 11.5, width: 104, flex: 'none' }}>{row[0]}</span>
                <span style={{ flex: 1, fontSize: 12.5 }}>{row[1]}</span>
                {row[2] && <Badge kind={/pass|ok|active|valid/i.test(row[2]) ? 'ok' : /obsolete|superseded/i.test(row[2]) ? 'neutral' : 'info'}>{row[2]}</Badge>}
              </div>
            ))}</div>
          )}
        </Card>
      ))}
      {r.note && <AINote title="Standards & AI context">{r.note}</AINote>}
    </div>
  );
  return (
    <div className="page page-wide rise">
      <div className="row" style={{ gap: 10, marginBottom: 16, flexWrap: 'wrap' }}>
        <button className="icon-btn" onClick={onBack}><Icon name="chevL" size={17} /></button>
        <span className="kpi-ico" style={{ width: 36, height: 36, background: 'var(--bg)', color: r.color }}><Icon name={r.icon} size={18} /></span>
        <div style={{ minWidth: 0 }}>
          <div className="row" style={{ gap: 9, flexWrap: 'wrap' }}><span className="h-page" style={{ fontSize: 20, whiteSpace: 'nowrap' }}>{r.title}</span>{r.status && <StatusBadge status={r.status} />}</div>
          <div className="h-sub">{r.sub}</div>
        </div>
        <span className="spacer" />
        {type === 'asset' && <Btn icon="qr" size="sm" onClick={() => window.printAssetLabel && window.printAssetLabel(data)}>Print label</Btn>}
        {type === 'cert' && <Btn icon="documents" size="sm" onClick={() => window.printCertificate && window.printCertificate(data)}>Print / PDF</Btn>}
        {type === 'customer' && <Btn icon="download" size="sm" onClick={() => {
          const rows = DB.monitors.filter((m) => m.customer === data.id).map((m) => ({ id: m.id, model: m.model, serial: m.serial, status: m.status === 'Active' ? 'Valid' : m.status, due: m.due ? DB.fmt(new Date(m.due)) : '' }));
          if (!rows.length) { window.toast('No instruments to export for this vendor yet.', 'info'); return; }
          csvExport((data.code || data.short || 'vendor') + '-assets.csv', ['id', 'model', 'serial', 'status', 'due'], rows);
        }}>Export assets</Btn>}
        <Btn variant="primary" icon="ext" size="sm" onClick={() => {
          if (type === 'customer' && window.openCustomerForm) return window.openCustomerForm(data);
          if (type === 'asset' && window.openAssetForm) return window.openAssetForm(data);
          if (type === 'cylinder' && window.openCylinderForm) return window.openCylinderForm(data);
          window.toast('Editing isn\'t available for this record type.', 'info');
        }}>Edit</Btn>
      </div>
      {hasPreview ? (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 360px', gap: 20, alignItems: 'start' }}>
          <div>{r.preview}</div>
          {sideCard}
        </div>
      ) : (
        <div style={{ maxWidth: 760 }}>{sideCard}</div>
      )}
    </div>
  );
}

Object.assign(window, { openDetail, RecordPage });
