/* =====================================================================
   AI-COS — Supporting modules (part 1):
   Standards & Cylinders · Equipment & Assets · CRM · Scheduling · QMS
   ===================================================================== */
function PageHead({ title, sub, children }) {
  return (
    <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 18 }}>
      <div><div className="h-page">{title}</div><div className="h-sub">{sub}</div></div>
      <div className="row" style={{ gap: 8 }}>{children}</div>
    </div>
  );
}
function pressurePct(c) { return Math.round((c.pressure / c.p0) * 100); }

/* ---------------- Standards & Cylinders ---------------------------- */
function CylindersModule({ onNav }) {
  const DB = window.DB;
  const [tab, setTab] = useState('cyl');
  return (
    <div className="page page-wide rise">
      <PageHead title="Reference Standards &amp; Cylinders" sub="Calibration gases, reference instruments &amp; the lab asset register — validity hard-gates the worksheet (ISO 17025 §6.4/6.5)">
        <Btn icon="filter" size="sm" onClick={() => window.toast('Filters applied', 'info')}>Filter</Btn><Btn variant="primary" icon="plus" onClick={() => window.openForm({ title: 'Record cylinder GRN', sub: 'Goods receipt — activation requires supplier certificate (CYL-01)', icon: 'cylinder', fields: [{ label: 'Gas mixture', type: 'text', placeholder: 'e.g. H₂S in N₂' }, { label: 'Certified concentration', type: 'text', placeholder: '25 ppm' }, { label: 'Lot / cylinder no.', type: 'text' }, { label: 'Supplier', type: 'select', options: ['Air Products', 'BOC Linde'] }, { label: 'Fill date', type: 'date' }, { label: 'Expiry date', type: 'date' }, { label: 'Initial pressure (psi)', type: 'number', value: '2000' }, { label: 'Supplier certificate', type: 'text', placeholder: 'Attach / ref' }], submitLabel: 'Activate cylinder', done: 'GRN recorded · cylinder activated for compatible models' })}>Record GRN</Btn>
      </PageHead>

      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)', marginBottom: 16 }}>
        <Kpi label="Active cylinders" value={DB.cylinders.filter(c => c.status === 'Active').length} icon="cylinder" tone="ok" />
        <Kpi label="Low pressure" value={DB.cylinders.filter(c => c.status === 'Low pressure').length} icon="gauge" tone="warn" delta="reorder suggested" />
        <Kpi label="Expired · blocked" value={DB.cylinders.filter(c => c.status === 'Expired').length} icon="x-circle" tone="bad" delta="hard-blocked" />
        <Kpi label="Reference instruments" value={DB.labAssets.filter(a => a.category === 'Reference' || a.category === 'Environmental').length} icon="gauge" tone="info" />
      </div>

      <div className="tabs" style={{ marginBottom: 16 }}>
        {[['cyl', 'Calibration gas cylinders'], ['ref', 'Reference instruments'], ['reg', 'Lab asset register']].map(([id, l]) => <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{l}</div>)}
      </div>

      {tab === 'cyl' && (
        <div className="card">
          <table className="tbl">
            <thead><tr><th>Cylinder</th><th>Mixture</th><th>Concentration</th><th>Lot</th><th>Supplier</th><th>Pressure</th><th>Expiry</th><th>Status</th></tr></thead>
            <tbody>
              {DB.cylinders.map((c) => {
                const pct = pressurePct(c);
                return (
                  <tr key={c.id} className="clickable" onClick={() => window.openDetail('cylinder', c)}>
                    <td className="mono cell-strong" style={{ fontSize: 12 }}>{c.id}</td>
                    <td style={{ fontSize: 12.5 }}>{c.mix}</td>
                    <td className="mono" style={{ fontSize: 12 }}>{c.conc}</td>
                    <td className="mono faint" style={{ fontSize: 11.5 }}>{c.lot}</td>
                    <td className="faint" style={{ fontSize: 12 }}>{c.supplier}</td>
                    <td style={{ width: 120 }}><div className="row" style={{ gap: 7 }}><div style={{ width: 56 }}><Bar pct={pct} color={pct < 25 ? 'var(--bad)' : pct < 50 ? 'var(--warn)' : 'var(--ok)'} /></div><span className="mono faint" style={{ fontSize: 11 }}>{c.pressure}</span></div></td>
                    <td><span style={{ fontSize: 12, fontWeight: 600, color: c.exp <= DB.TODAY ? 'var(--bad)' : (c.exp - DB.TODAY) / 864e5 < 30 ? 'var(--warn)' : 'var(--text)' }}>{DB.fmt(c.exp)}</span></td>
                    <td><StatusBadge status={c.status} /></td>
                  </tr>
                );
              })}
            </tbody>
          </table>
          <div className="card-body" style={{ borderTop: '1px solid var(--border)' }}>
            <AINote title="CYL-2455 (SO₂) expired — auto-blocked from selection" clause="CYL-03" action="Suggest reorder">
              An expired calibration gas invalidates results. The cylinder is hard-blocked from every worksheet; 1 low-pressure cylinder (CYL-2454) is approaching the reorder threshold.
            </AINote>
          </div>
        </div>
      )}

      {(tab === 'ref' || tab === 'reg') && (
        <div className="card">
          <table className="tbl">
            <thead><tr><th>Asset ID</th><th>Name</th><th>Category</th><th>Serial</th><th>Location</th><th>Criticality</th><th>Next cal due</th><th>Status</th></tr></thead>
            <tbody>
              {DB.labAssets.filter((a) => tab === 'reg' ? true : (a.category === 'Reference' || a.category === 'Environmental')).map((a) => (
                <tr key={a.id} className="clickable" onClick={() => window.openDetail('labasset', a)}>
                  <td className="mono cell-strong" style={{ fontSize: 12 }}>{a.id}</td>
                  <td style={{ fontSize: 12.5 }}>{a.name}</td>
                  <td><span className="tag">{a.category}</span></td>
                  <td className="mono faint" style={{ fontSize: 11.5 }}>{a.serial}</td>
                  <td className="faint" style={{ fontSize: 12 }}><span className="row" style={{ gap: 5 }}><Icon name="pin" size={12} />{a.location}</span></td>
                  <td><Badge kind={a.criticality === 'Critical' ? 'bad' : a.criticality === 'High' ? 'warn' : 'neutral'}>{a.criticality}</Badge></td>
                  <td><span style={{ fontSize: 12, fontWeight: 600, color: a.status === 'Overdue' ? 'var(--bad)' : a.status === 'Due soon' ? 'var(--warn)' : 'var(--text)' }}>{DB.fmt(a.due)} · {a.dueRel}</span></td>
                  <td><StatusBadge status={a.status} /></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* ---------------- Equipment & Assets (customer monitors) ----------- */
/* =====================================================================
   Asset Management (M2) — customers + gas-detector register, full CRUD
   ===================================================================== */
const ASSET_STATUSES = ['Valid', 'Due soon', 'Overdue', 'In calibration', 'Failed', 'Under repair', 'Retired'];
const _din = (d) => { if (!d) return ''; const x = new Date(d); return isNaN(x) ? '' : x.toISOString().slice(0, 10); };
const _statusOf = (m) => m.status === 'Active' ? 'Valid' : m.status; // legacy data normaliser

function openAssetForm(rec, opts) {
  const DB = window.DB; const e = rec || {};
  opts = opts || {};
  const prefillCust = opts.customerId ? (DB.custById(opts.customerId) || {}).name : null;
  // Smart flow: an instrument belongs to a customer. With none registered,
  // send the user to add one first instead of showing an empty picker.
  if (!rec && !DB.customers.length) {
    window.toast('An instrument belongs to a customer — let\'s add the customer first, then register the instrument.', 'info', { label: 'Add customer', onClick: () => window.openCustomerForm && window.openCustomerForm() });
    if (window.openCustomerForm) window.openCustomerForm();
    return;
  }
  const NEW_CUST = '＋ Register a new customer…';
  const custNames = DB.customers.map((c) => c.name).concat(NEW_CUST);
  openForm({
    title: rec ? 'Edit asset — ' + e.id : 'Register asset', icon: 'cpu', wide: true,
    sub: 'Customer gas detector under calibration management',
    submitLabel: rec ? 'Save changes' : 'Register asset', done: rec ? 'Asset updated' : 'Asset registered',
    fields: [
      { type: 'section', label: 'Ownership' },
      { key: 'customer', label: 'Customer', type: 'select', options: custNames, value: e.customer ? (DB.custById(e.customer) || {}).name : (prefillCust || custNames[0]), full: true },
      { type: 'section', label: 'Instrument' },
      { key: 'manufacturer', label: 'Manufacturer', type: 'select', options: calOpt('manufacturers'), value: e.manufacturer || calOpt('manufacturers')[0], half: true },
      { key: 'model', label: 'Make / model', type: 'select', options: calOpt('models'), value: e.model || calOpt('models')[0], half: true },
      { key: 'equipmentType', label: 'Equipment type', type: 'select', options: calOpt('equipmentTypes'), value: e.equipmentType || calOpt('equipmentTypes')[0], half: true },
      { key: 'serial', label: 'Serial number', type: 'text', value: e.serial, half: true },
      { key: 'health', label: 'Asset health', type: 'select', options: calOpt('assetHealth'), value: e.health || 'Healthy', half: true },
      { key: 'sensors', label: 'Sensor configuration', type: 'checks', options: calOpt('gasTypes'), value: e.sensors || ['H₂S'] },
      { key: 'firmware', label: 'Firmware version', type: 'text', value: e.firmware, placeholder: 'e.g. v2.14', half: true },
      { type: 'section', label: 'Fixed / installed detector', hint: 'Only for fixed gas detectors — per the fixed-detector worksheet & certificate' },
      { key: 'tag', label: 'Detector tag no.', type: 'text', value: e.tag, placeholder: 'e.g. GD-101', half: true },
      { key: 'plcTag', label: 'Controller / PLC tag', type: 'text', value: e.plcTag, placeholder: 'e.g. PLC-A2', half: true },
      { key: 'loopNo', label: 'Loop no.', type: 'text', value: e.loopNo, placeholder: 'e.g. L-07', half: true },
      { type: 'section', label: 'Location' },
      { key: 'department', label: 'Department', type: 'text', value: e.department, half: true },
      { key: 'department', label: 'Department', type: 'text', value: e.department, half: true },
      { key: 'location', label: 'Site / location', type: 'text', value: e.location, half: true },
      { type: 'section', label: 'Lifecycle' },
      { key: 'purchase', label: 'Purchase date', type: 'date', value: _din(e.purchase), half: true },
      { key: 'commissioned', label: 'Commission date', type: 'date', value: _din(e.commissioned), half: true },
      { key: 'warranty', label: 'Warranty until', type: 'date', value: _din(e.warranty), half: true },
      { type: 'section', label: 'Calibration schedule' },
      { key: 'last', label: 'Last calibration', type: 'date', value: _din(e.last), half: true },
      { key: 'interval', label: 'Interval (months)', type: 'select', options: calOpt('intervals'), value: String(e.interval || 6), half: true },
      { key: 'status', label: 'Status', type: 'select', options: ASSET_STATUSES, value: _statusOf(e) || 'Valid', half: true },
    ],
    onSave: (v) => {
      const DBx = window.DB;
      if (v.customer === NEW_CUST) {
        window.toast('Add the new customer, then register their instrument.', 'info');
        if (window.openCustomerForm) window.openCustomerForm();
        return;
      }
      const cust = DBx.customers.find((c) => c.name === v.customer) || DBx.customers[0];
      if (!cust) { window.toast('Pick a customer for this instrument (or register a new one).', 'warn'); return; }
      const last = v.last ? new Date(v.last) : new Date();
      const due = DBx.addM(last, +v.interval || 6);
      const patch = {
        manufacturer: v.manufacturer, model: v.model, equipmentType: v.equipmentType, serial: v.serial || ('SN-' + Date.now().toString(36).toUpperCase()),
        firmware: v.firmware || '', tag: v.tag || '', plcTag: v.plcTag || '', loopNo: v.loopNo || '',
        health: v.health || 'Healthy',
        sensors: (v.sensors && v.sensors.length) ? v.sensors : ['H₂S'],
        customer: cust.id, department: v.department || '', location: v.location || '', interval: +v.interval || 6,
        purchase: v.purchase ? new Date(v.purchase) : null,
        commissioned: v.commissioned ? new Date(v.commissioned) : null,
        warranty: v.warranty ? new Date(v.warranty) : null,
        last, due, dueRel: DBx.rel(due),
        status: v.status || (due < DBx.TODAY ? 'Overdue' : (due - DBx.TODAY) / 864e5 < 30 ? 'Due soon' : 'Valid'),
      };
      if (rec) dbUpdate('monitors', 'id', e.id, patch);
      else {
        const seq = DBx.monitors.filter((m) => m.customer === cust.id).length + 1;
        const nid = `${cust.short}-MON-${String(seq).padStart(3, '0')}`;
        dbAdd('monitors', Object.assign({ id: nid }, patch));
        if (opts.onCreated) opts.onCreated(nid, cust);
      }
    },
  });
}

function openCustomerForm(rec) {
  const DB = window.DB; const e = rec || {};
  openForm({
    title: rec ? 'Edit customer — ' + e.name : 'New customer', icon: 'briefcase',
    sub: 'Company whose gas detectors the laboratory calibrates',
    submitLabel: rec ? 'Save changes' : 'Create customer', done: rec ? 'Customer updated' : 'Customer created',
    fields: [
      { key: '_code', label: 'Vendor code', type: 'readonly', value: e.code || 'Assigned automatically on registration' },
      { key: 'name', label: 'Company name', type: 'text', full: true, value: e.name },
      { key: 'short', label: 'Short code (2–4 letters)', type: 'text', value: e.short, half: true },
      { key: 'sector', label: 'Sector', type: 'select', options: calOpt('sectors'), value: e.sector || calOpt('sectors')[0], half: true },
      { key: 'site', label: 'Site / address', type: 'text', full: true, value: e.site },
      { key: 'contact', label: 'Contact person', type: 'text', value: e.contact, half: true },
      { key: 'email', label: 'Contact email (reminders)', type: 'text', value: e.email, placeholder: 'name@company.com', half: true },
      { key: 'tier', label: 'Agreement', type: 'select', options: calOpt('agreements'), value: e.tier || 'Contract', half: true },
      { type: 'section', label: 'Registration & compliance' },
      { key: 'crNumber', label: 'Commercial Registration (CR) No.', type: 'text', value: e.crNumber, placeholder: 'e.g. 1234567', half: true },
      { key: 'crExpiry', label: 'CR validity / expiry date', type: 'date', value: _din(e.crExpiry), half: true },
    ],
    onSave: (v) => {
      if (!v.name || !v.name.trim()) { window.toast('A company name is required to register a vendor.', 'warn'); return; }
      const palette = ['#2f6fed', '#15a06b', '#c9820a', '#7a5bff', '#df463c', '#0e7490', '#be185d', '#475569'];
      const short = (v.short || v.name.slice(0, 3)).toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 4);
      const patch = { name: v.name.trim(), short, sector: v.sector, site: v.site, contact: v.contact, email: v.email, tier: v.tier, crNumber: (v.crNumber || '').trim(), crExpiry: v.crExpiry ? new Date(v.crExpiry) : null };
      if (rec) dbUpdate('customers', 'id', e.id, patch);
      else {
        const code = window.calNextVendorCode();
        dbAdd('customers', Object.assign({ id: 'C-' + short + '-' + Date.now().toString(36).slice(-3), code, color: palette[window.DB.customers.length % palette.length], assets: 0, created: new Date() }, patch));
        window.toast('Vendor registered — code ' + code + ' assigned.', 'ok');
      }
    },
  });
}

function openCylinderForm(rec) {
  const DB = window.DB; const e = rec || {};
  openForm({
    title: rec ? 'Edit ' + e.id : 'Register gas cylinder', icon: 'cylinder',
    sub: 'Certified calibration gas — validity gates the worksheet (ISO 17025 §6.4/6.5)',
    submitLabel: rec ? 'Save changes' : 'Register cylinder', done: rec ? 'Cylinder updated' : 'Cylinder registered',
    fields: [
      { key: 'mix', label: 'Gas mixture', type: 'text', value: e.mix, placeholder: 'e.g. H₂S in N₂', half: true },
      { key: 'conc', label: 'Certified concentration', type: 'text', value: e.conc, placeholder: 'e.g. 25 ppm', half: true },
      { key: 'lot', label: 'Lot / cylinder no.', type: 'text', value: e.lot, half: true },
      { key: 'supplier', label: 'Supplier', type: 'select', options: calOpt('suppliers'), value: e.supplier || calOpt('suppliers')[0], half: true },
      { key: 'pressure', label: 'Pressure (psi)', type: 'number', value: String(e.pressure != null ? e.pressure : 2000), half: true },
      { key: 'exp', label: 'Expiry date (supplier certificate)', type: 'date', value: _din(e.exp), half: true },
      { key: 'status', label: 'Status', type: 'select', options: ['Active', 'Low pressure', 'Expired', 'Empty'], value: e.status || 'Active', half: true },
    ],
    onSave: (v) => {
      if (!v.mix || !v.conc || !v.exp) { window.toast('Mixture, concentration and expiry date are required', 'bad'); return; }
      const pressure = parseInt(v.pressure, 10) || 2000;
      const patch = { mix: v.mix, conc: v.conc, lot: v.lot || '', supplier: v.supplier || '', pressure, exp: new Date(v.exp), status: v.status };
      if (rec) dbUpdate('cylinders', 'id', e.id, patch);
      else {
        const nums = DB.cylinders.map((c) => parseInt(String(c.id).replace(/\D/g, ''), 10) || 0);
        dbAdd('cylinders', Object.assign({ id: 'CYL-' + (Math.max(1000, ...nums) + 1), p0: pressure }, patch));
      }
    },
  });
}

/* Calibration label — printed and affixed to the instrument after calibration */
function printAssetLabel(m) {
  const DB = window.DB;
  const c = DB.custById(m.customer) || {};
  const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Label ${m.id}</title><style>
    body{font-family:'Helvetica Neue',Arial,sans-serif;margin:0;padding:24px;display:flex;gap:20px;flex-wrap:wrap}
    .label{width:280px;border:2px solid #16314a;border-radius:10px;padding:12px 14px;color:#16283c}
    .hd{display:flex;justify-content:space-between;align-items:center;border-bottom:1.5px solid #16314a;padding-bottom:6px;margin-bottom:8px}
    .hd b{font-size:12px;letter-spacing:.5px}.hd span{font-size:9px;color:#4a5b70}
    .big{font-size:17px;font-weight:800;letter-spacing:-.3px}
    .lrow{display:flex;justify-content:space-between;font-size:10.5px;margin-top:4px}
    .lrow b{font-weight:700}
    .due{margin-top:8px;background:#16314a;color:#fff;border-radius:6px;padding:6px 10px;text-align:center;font-size:11px}
    .due b{font-size:14px;display:block}
    .print-btn{position:fixed;top:10px;right:10px;padding:8px 16px;background:#2f6fed;color:#fff;border:none;border-radius:8px;font-weight:700;cursor:pointer}
    @media print{.print-btn{display:none}}
  </style></head><body>
  <button class="print-btn" onclick="window.print()">Print</button>
  <div class="label">
    <div class="hd"><b>${(DB.company.short || 'ADAM')} CALIBRATION</b><span>${DB.company.standards || ''}</span></div>
    <div class="big">${m.id}</div>
    <div class="lrow"><span>${m.model || ''}</span><b>S/N ${m.serial || '—'}</b></div>
    <div class="lrow"><span>Customer</span><b>${c.short || c.name || '—'}</b></div>
    <div class="lrow"><span>Calibrated</span><b>${m.last ? DB.fmt(m.last) : '—'}</b></div>
    <div class="due">NEXT CALIBRATION DUE<b>${m.due ? DB.fmt(m.due) : '—'}</b></div>
  </div>
  </body></html>`;
  const w = window.open('', '_blank');
  if (!w) { window.toast('Allow pop-ups to print the label', 'bad'); return; }
  w.document.write(html); w.document.close();
}

/* Standalone Customers register (own sidebar entry) */
function CustomersModule({ onNav }) {
  const DB = window.DB;
  const [q, setQ] = useState('');
  const norm = DB.monitors.map((m) => Object.assign({}, m, { status: _statusOf(m) }));
  const list = DB.customers.filter((c) => !q || (c.name + ' ' + (c.code || '') + ' ' + (c.crNumber || '') + ' ' + (c.contact || '') + ' ' + (c.sector || '')).toLowerCase().includes(q.toLowerCase()));
  const overdue = norm.filter((m) => m.status === 'Overdue').length;
  const crExpiring = DB.customers.filter((c) => c.crExpiry && new Date(c.crExpiry) - DB.TODAY < 60 * 864e5).length;
  return (
    <div className="page page-wide rise">
      <PageHead title="Customers" sub="Vendors whose gas detectors the laboratory calibrates — profiles, assets, certificates & documents">
        <Btn variant="primary" icon="plus" onClick={() => openCustomerForm()}>New customer</Btn>
      </PageHead>
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)', marginBottom: 16 }}>
        <Kpi label="Customers" value={DB.customers.length} icon="briefcase" tone="info" />
        <Kpi label="Assets under management" value={norm.length} icon="cpu" tone="info" />
        <Kpi label="Overdue assets" value={overdue} icon="alert" tone={overdue ? 'bad' : 'ok'} />
        <Kpi label="CR expiring (60 days)" value={crExpiring} icon="documents" tone={crExpiring ? 'warn' : 'ok'} />
      </div>
      {DB.customers.length === 0 ? (
        <div className="card">
          <EmptyState icon="briefcase"
            title="Add your first customer"
            desc="Customers are the companies whose gas detectors you calibrate. Add one to start registering their instruments and booking calibration jobs."
            actionLabel="Add a customer"
            onAction={() => openCustomerForm()} />
        </div>
      ) : (
        <div className="card">
          <div className="card-head" style={{ gap: 10 }}>
            <div className="h-sec" style={{ fontSize: 14 }}>Customer register</div>
            <span className="faint" style={{ fontSize: 11.5 }}>{list.length} of {DB.customers.length}</span>
            <span className="spacer" />
            <div className="searchbox" style={{ width: 240 }}><Icon name="search" size={14} /><input placeholder="Name, vendor code, CR no, contact…" value={q} onChange={(e) => setQ(e.target.value)} style={{ fontSize: 12.5 }} /></div>
          </div>
          <table className="tbl">
            <thead><tr><th>Customer</th><th>Vendor code</th><th>CR expiry</th><th>Sector</th><th>Contact</th><th>Agreement</th><th>Assets</th><th>Overdue</th><th style={{ width: 90 }}></th></tr></thead>
            <tbody>
              {list.map((c) => {
                const assets = norm.filter((m) => m.customer === c.id);
                const od = assets.filter((m) => m.status === 'Overdue').length;
                const crExp = c.crExpiry ? new Date(c.crExpiry) : null;
                const crBad = crExp && crExp < DB.TODAY;
                const crWarn = crExp && !crBad && (crExp - DB.TODAY) / 864e5 < 60;
                return (
                  <tr key={c.id} className="clickable" onClick={() => window.openDetail('customer', c)}>
                    <td><div className="row" style={{ gap: 8 }}><span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 6 }}>{c.short}</span><span style={{ fontSize: 13, fontWeight: 650 }}>{c.name}</span></div></td>
                    <td className="mono" style={{ fontSize: 11.5 }}>{c.code || '—'}</td>
                    <td>{crExp ? <span style={{ fontSize: 12, fontWeight: 600, color: crBad ? 'var(--bad)' : crWarn ? 'var(--warn)' : 'var(--text)' }}>{DB.fmt(crExp)}</span> : <span className="faint">—</span>}</td>
                    <td className="faint" style={{ fontSize: 12 }}>{c.sector || '—'}</td>
                    <td style={{ fontSize: 12.5 }}>{c.contact || '—'}{c.email ? <div className="faint" style={{ fontSize: 11 }}>{c.email}</div> : null}</td>
                    <td><Badge kind={c.tier === 'Enterprise AMC' ? 'info' : 'neutral'}>{c.tier || '—'}</Badge></td>
                    <td className="mono" style={{ fontSize: 12.5 }}>{assets.length}</td>
                    <td>{od ? <Badge kind="bad">{od}</Badge> : <Badge kind="ok">0</Badge>}</td>
                    <td onClick={(e) => e.stopPropagation()}>
                      <div className="row" style={{ gap: 6, justifyContent: 'flex-end' }}>
                        <Btn size="sm" variant="ghost" onClick={() => openCustomerForm(c)}>Edit</Btn>
                      </div>
                    </td>
                  </tr>
                );
              })}
              {list.length === 0 && <tr><td colSpan={9} className="faint" style={{ textAlign: 'center', padding: 24 }}>No customers match your search.</td></tr>}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

function AssetsModule({ onNav, initialTab }) {
  const DB = window.DB;
  const [tab, setTab] = useState(initialTab === 'customers' ? 'assets' : (initialTab || 'assets'));
  const [cust, setCust] = useState('All');
  const [status, setStatus] = useState('All');
  const [q, setQ] = useState('');
  const [menu, setMenu] = useState(null); // asset id with open kebab

  const norm = DB.monitors.map((m) => Object.assign({}, m, { status: _statusOf(m) }));
  const list = norm.filter((m) =>
    (cust === 'All' || m.customer === cust) &&
    (status === 'All' || m.status === status) &&
    (!q || (m.id + ' ' + m.serial + ' ' + m.model + ' ' + (m.location || '')).toLowerCase().includes(q.toLowerCase()))
  );
  const count = (s) => norm.filter((m) => m.status === s).length;
  const sel = { fontFamily: 'inherit', fontSize: 12.5, padding: '7px 11px', borderRadius: 9, border: '1px solid var(--border-2)', background: 'var(--surface)', fontWeight: 600 };

  return (
    <div className="page page-wide rise">
      <PageHead title="Asset Management" sub="Customer gas detectors · calibration schedule · status lifecycle — per asset-management scope">
        {tab === 'cylinders'
          ? <Btn variant="primary" icon="plus" onClick={() => openCylinderForm()}>Register cylinder</Btn>
          : <Btn variant="primary" icon="plus" onClick={() => openAssetForm()}>Register asset</Btn>}
      </PageHead>

      <ModuleGuide module="assets" onNav={onNav} />

      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(6,1fr)', marginBottom: 16 }}>
        <Kpi label="Registered assets" value={norm.length} icon="cpu" tone="info" delta={DB.customers.length + ' customers'} />
        <Kpi label="Valid" value={count('Valid')} icon="check-circle" tone="ok" />
        <Kpi label="Due soon" value={count('Due soon')} icon="clock" tone="warn" />
        <Kpi label="Overdue" value={count('Overdue')} icon="alert" tone="bad" />
        <Kpi label="Failed / repair" value={count('Failed') + count('Under repair')} icon="wrench" tone="warn" />
        <Kpi label="Retired" value={count('Retired')} icon="box" tone="info" />
      </div>

      <div className="tabs" style={{ marginBottom: 14 }}>
        {[['assets', 'Asset register'], ['cylinders', 'Gas cylinders']].map(([id, label]) => (
          <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{label}</div>
        ))}
      </div>

      {tab === 'assets' && norm.length === 0 && (
        <div className="card">
          <EmptyState icon="cpu"
            title={DB.customers.length ? 'No instruments on the register yet' : 'Add your first customer to begin'}
            desc={DB.customers.length
              ? 'Register the gas detectors you calibrate for your customers. Each one gets a calibration schedule, worksheets and certificates.'
              : 'A customer owns the instruments you calibrate. Add the company first, then register their gas detectors.'}
            actionLabel={DB.customers.length ? 'Register an instrument' : 'Add a customer'}
            onAction={() => DB.customers.length ? openAssetForm() : openCustomerForm()}
            secondaryLabel={DB.customers.length ? null : 'View customers'}
            onSecondary={() => onNav && onNav('customers')} />
        </div>
      )}

      {tab === 'assets' && norm.length > 0 && (
        <div className="card">
          <div className="card-head" style={{ gap: 10, flexWrap: 'wrap' }}>
            <div className="h-sec" style={{ fontSize: 14 }}>Asset register</div>
            <span className="faint" style={{ fontSize: 11.5 }}>{list.length} of {norm.length}</span>
            <span className="spacer" />
            <div className="searchbox" style={{ width: 220 }}>
              <Icon name="search" size={14} />
              <input placeholder="ID, serial, model, location…" value={q} onChange={(e) => setQ(e.target.value)} style={{ fontSize: 12.5 }} />
            </div>
            <select value={status} onChange={(e) => setStatus(e.target.value)} style={sel}>
              <option value="All">All statuses</option>
              {ASSET_STATUSES.map((s) => <option key={s}>{s}</option>)}
            </select>
            <select value={cust} onChange={(e) => setCust(e.target.value)} style={sel}>
              <option value="All">All customers</option>
              {DB.customers.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
          </div>
          <table className="tbl">
            <thead><tr><th>Asset ID</th><th>Model</th><th>Serial</th><th>Sensors</th><th>Customer</th><th>Location</th><th>Last cal</th><th>Next due</th><th>Status</th><th style={{ width: 40 }}></th></tr></thead>
            <tbody>
              {list.map((m) => {
                const c = DB.custById(m.customer);
                return (
                  <tr key={m.id} className="clickable" onClick={() => window.openDetail('asset', m)}>
                    <td className="mono cell-strong" style={{ fontSize: 12 }}>{m.id}</td>
                    <td style={{ fontSize: 12.5 }}>{m.model}</td>
                    <td className="mono faint" style={{ fontSize: 11.5 }}>{m.serial}</td>
                    <td><div className="row wrap" style={{ gap: 4 }}>{(m.sensors || []).map((s) => <span key={s} className="tag" style={{ padding: '1px 6px', fontSize: 10.5 }}>{s}</span>)}</div></td>
                    <td><div className="row" style={{ gap: 6 }}><span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 6 }}>{c.short}</span>{c.short}</div></td>
                    <td className="faint" style={{ fontSize: 12 }}>{m.location || '—'}</td>
                    <td className="faint" style={{ fontSize: 12 }}>{DB.fmt(m.last)}</td>
                    <td><span style={{ fontSize: 12, fontWeight: 600, color: m.status === 'Overdue' ? 'var(--bad)' : m.status === 'Due soon' ? 'var(--warn)' : 'var(--text)' }}>{m.status === 'Retired' ? '—' : DB.rel(m.due)}</span></td>
                    <td><StatusBadge status={m.status} /></td>
                    <td onClick={(e) => e.stopPropagation()}>
                      <span style={{ position: 'relative', display: 'inline-block' }}>
                        <button className="icon-btn" style={{ width: 26, height: 26 }} onClick={() => setMenu(menu === m.id ? null : m.id)}><Icon name="dots" size={14} /></button>
                        {menu === m.id && (
                          <React.Fragment>
                            <div style={{ position: 'fixed', inset: 0, zIndex: 60 }} onClick={() => setMenu(null)} />
                            <div className="popover" style={{ right: 0, top: 'calc(100% + 4px)', minWidth: 140, zIndex: 61 }}>
                              <div className="pop-item" onClick={() => { setMenu(null); window.openDetail('asset', m); }}><Icon name="eye" size={13} />View</div>
                              <div className="pop-item" onClick={() => { setMenu(null); openAssetForm(DB.monitors.find((x) => x.id === m.id)); }}><Icon name="signature" size={13} />Edit</div>
                              <div className="pop-item" onClick={() => { setMenu(null); printAssetLabel(m); }}><Icon name="qr" size={13} />Print label</div>
                              {m.status !== 'Retired' && <div className="pop-item" onClick={() => { setMenu(null); dbUpdate('monitors', 'id', m.id, { status: 'Retired' }); window.toast(m.id + ' retired', 'ok'); }}><Icon name="box" size={13} />Retire</div>}
                              <div className="pop-item" style={{ color: 'var(--bad)' }} onClick={() => { setMenu(null); if (window.confirm('Delete ' + m.id + '? This cannot be undone.')) { dbRemove('monitors', 'id', m.id); window.toast(m.id + ' deleted', 'ok'); } }}><Icon name="x" size={13} />Delete</div>
                            </div>
                          </React.Fragment>
                        )}
                      </span>
                    </td>
                  </tr>
                );
              })}
              {list.length === 0 && <tr><td colSpan={10} className="faint" style={{ textAlign: 'center', padding: 24 }}>No assets match these filters — try clearing the search or status filter.</td></tr>}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'cylinders' && (
        <div className="card">
          <div className="card-head">
            <div className="h-sec" style={{ fontSize: 14 }}>Calibration gas cylinders</div>
            <span className="faint" style={{ fontSize: 11.5 }}>expired or non-active cylinders are blocked from worksheets</span>
            <span className="spacer" />
            <span className="faint" style={{ fontSize: 11.5 }}>{DB.cylinders.length} registered</span>
          </div>
          <table className="tbl">
            <thead><tr><th>Cylinder</th><th>Mixture</th><th>Concentration</th><th>Lot</th><th>Supplier</th><th>Pressure</th><th>Expiry</th><th>Status</th><th style={{ width: 120 }}></th></tr></thead>
            <tbody>
              {DB.cylinders.map((c) => {
                const pct = c.p0 ? pressurePct(c) : 100;
                const expired = c.exp && new Date(c.exp) <= DB.TODAY;
                return (
                  <tr key={c.id}>
                    <td className="mono cell-strong" style={{ fontSize: 12 }}>{c.id}</td>
                    <td style={{ fontSize: 12.5 }}>{c.mix}</td>
                    <td className="mono" style={{ fontSize: 12 }}>{c.conc}</td>
                    <td className="mono faint" style={{ fontSize: 11.5 }}>{c.lot || '—'}</td>
                    <td className="faint" style={{ fontSize: 12 }}>{c.supplier || '—'}</td>
                    <td style={{ width: 120 }}><div className="row" style={{ gap: 7 }}><div style={{ width: 56 }}><Bar pct={pct} color={pct < 25 ? 'var(--bad)' : pct < 50 ? 'var(--warn)' : 'var(--ok)'} /></div><span className="mono faint" style={{ fontSize: 11 }}>{c.pressure}</span></div></td>
                    <td><span style={{ fontSize: 12, fontWeight: 600, color: expired ? 'var(--bad)' : c.exp && (new Date(c.exp) - DB.TODAY) / 864e5 < 30 ? 'var(--warn)' : 'var(--text)' }}>{c.exp ? DB.fmt(c.exp) : '—'}</span></td>
                    <td><StatusBadge status={expired && c.status === 'Active' ? 'Expired' : c.status} /></td>
                    <td>
                      <div className="row" style={{ gap: 6, justifyContent: 'flex-end' }}>
                        <Btn size="sm" variant="ghost" onClick={() => openCylinderForm(c)}>Edit</Btn>
                        <Btn size="sm" variant="ghost" style={{ color: 'var(--bad)' }} onClick={() => { if (window.confirm('Delete ' + c.id + '?')) { dbRemove('cylinders', 'id', c.id); window.toast(c.id + ' deleted', 'ok'); } }}>Del</Btn>
                      </div>
                    </td>
                  </tr>
                );
              })}
              {DB.cylinders.length === 0 && <tr><td colSpan={9} className="faint" style={{ textAlign: 'center', padding: 24 }}>No gas cylinders registered — a certified, in-date cylinder is required before any calibration worksheet can be completed.</td></tr>}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
}

/* ---------------- CRM & Contracts ---------------------------------- */
function CRMModule({ onNav }) {
  const DB = window.DB;
  const [tab, setTab] = useState('customers');
  const tabs = [['customers', 'Customers'], ['quotations', 'Quotations'], ['contracts', 'Contracts / AMC'], ['prequal', 'Prequalification']];
  const qStatus = { Accepted: 'ok', Sent: 'info', Draft: 'neutral', Rejected: 'bad' };
  return (
    <div className="page page-wide rise">
      <PageHead title="CRM &amp; Contracts" sub="Customers · prequalification · quotations · AMC contracts that auto-drive scheduling">
        <Btn icon="receipt" size="sm" onClick={() => window.openForm({ title: 'New quotation', sub: 'Converts to job or AMC on acceptance (CRM-03)', icon: 'receipt', fields: [{ label: 'Customer', type: 'select', options: window.DB.customers.map((c) => c.name) }, { label: 'Validity (days)', type: 'number', value: '30' }, { label: 'Scope / title', type: 'text', full: true, placeholder: 'e.g. 40 monitors — annual calibration' }, { label: 'No. of items', type: 'number' }, { label: 'Estimated value (OMR)', type: 'number' }], submitLabel: 'Create quotation', done: 'Quotation created (Draft)' })}>New quotation</Btn><Btn variant="primary" icon="plus" onClick={() => window.openForm({ title: 'New AMC contract', sub: 'Covered assets auto-planned across the period', icon: 'briefcase', fields: [{ label: 'Customer', type: 'select', options: window.DB.customers.map((c) => c.name) }, { label: 'Covered assets', type: 'number' }, { label: 'Frequency', type: 'select', options: ['6-monthly', 'Quarterly', 'Annual'] }, { label: 'Annual value (OMR)', type: 'number' }, { label: 'Contract period', type: 'text', full: true, placeholder: 'Jan–Dec 2026' }], submitLabel: 'Create contract', done: 'Contract created · covered assets auto-planned' })}>New contract</Btn>
      </PageHead>
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(4,1fr)', marginBottom: 16 }}>
        <Kpi label="Active customers" value={DB.customers.length} icon="building" tone="info" />
        <Kpi label="Open quotations" value={DB.quotations.filter((q) => q.status === 'Sent' || q.status === 'Draft').length} icon="receipt" tone="warn" delta="awaiting decision" />
        <Kpi label="AMC contracts" value={DB.contracts.length} icon="briefcase" tone="ok" />
        <Kpi label="Prequal. expiring" value={DB.customers.filter((c) => (c.prequalExp - DB.TODAY) / 864e5 < 90).length} icon="alert" tone="bad" />
      </div>
      <div className="tabs" style={{ marginBottom: 16 }}>{tabs.map(([id, l]) => <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{l}</div>)}</div>

      {tab === 'customers' && (
        <div className="card">
          <div className="card-head"><Icon name="building" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Customers</div></div>
          <table className="tbl">
            <thead><tr><th>Customer</th><th>Sector</th><th>Assets</th><th>Tier</th><th>Prequalification</th><th></th></tr></thead>
            <tbody>
              {DB.customers.map((c) => (
                <tr key={c.id} className="clickable" onClick={() => window.openDetail('customer', c)}>
                  <td><CustChip id={c.id} sub /></td>
                  <td className="faint" style={{ fontSize: 12 }}>{c.sector}</td>
                  <td className="num mono">{c.assets}</td>
                  <td><span className="tag">{c.tier}</span></td>
                  <td>{c.prequal === 'Approved' ? <Badge kind="ok" dot>Approved</Badge> : c.prequal === 'Renewal due' ? <Badge kind="warn" dot>Renewal {DB.rel(c.prequalExp)}</Badge> : <Badge kind="info" dot>{c.prequal}</Badge>}</td>
                  <td><Icon name="chevR" size={16} style={{ color: 'var(--text-3)' }} /></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'quotations' && (
        <div className="card">
          <div className="card-head"><Icon name="receipt" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Quotations &amp; proposals</div></div>
          <table className="tbl">
            <thead><tr><th>Quotation №</th><th>Customer</th><th>Scope</th><th className="num">Value (OMR)</th><th>Valid until</th><th>Status</th><th></th></tr></thead>
            <tbody>
              {DB.quotations.map((q) => { const c = DB.custById(q.customer); return (
                <tr key={q.id} className="clickable" onClick={() => window.openDetail('quotation', q)}>
                  <td className="mono cell-strong" style={{ fontSize: 12 }}>{q.id}</td>
                  <td><div className="row" style={{ gap: 6 }}><span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 6 }}>{c.short}</span>{c.short}</div></td>
                  <td style={{ fontSize: 12.5, maxWidth: 260 }}>{q.title}</td>
                  <td className="num mono cell-strong">{q.value.toLocaleString()}</td>
                  <td><span style={{ fontWeight: 600, color: q.validity <= DB.TODAY ? 'var(--bad)' : 'var(--text)' }}>{DB.rel(q.validity)}</span></td>
                  <td><Badge kind={qStatus[q.status]} dot>{q.status}</Badge></td>
                  <td><Icon name="chevR" size={16} style={{ color: 'var(--text-3)' }} /></td>
                </tr>
              ); })}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'contracts' && (
        <div className="card">
          <div className="card-head"><Icon name="briefcase" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Service agreements / AMC</div></div>
          <table className="tbl">
            <thead><tr><th>Contract №</th><th>Customer</th><th>Assets</th><th>Frequency</th><th className="num">Annual value</th><th>Renewal</th><th>Status</th><th></th></tr></thead>
            <tbody>
              {DB.contracts.map((ct) => { const c = DB.custById(ct.customer); return (
                <tr key={ct.id} className="clickable" onClick={() => window.openDetail('contract', ct)}>
                  <td className="mono cell-strong" style={{ fontSize: 12 }}>{ct.id}</td>
                  <td><div className="row" style={{ gap: 6 }}><span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 6 }}>{c.short}</span>{c.short}</div></td>
                  <td className="num mono">{ct.assets}</td><td className="faint" style={{ fontSize: 12 }}>{ct.freq}</td>
                  <td className="num mono cell-strong">{ct.value.toLocaleString()}</td>
                  <td>{DB.rel(ct.renew)}</td>
                  <td><StatusBadge status={ct.status} /></td>
                  <td><Icon name="chevR" size={16} style={{ color: 'var(--text-3)' }} /></td>
                </tr>
              ); })}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'prequal' && (
        <div className="card">
          <div className="card-head"><Icon name="check-shield" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Client approval &amp; prequalification</div></div>
          <table className="tbl">
            <thead><tr><th>Customer</th><th>Status</th><th>Expiry</th><th>Documents</th><th></th></tr></thead>
            <tbody>
              {DB.customers.map((c) => (
                <tr key={c.id} className="clickable" onClick={() => window.openDetail('customer', c)}>
                  <td><CustChip id={c.id} /></td>
                  <td>{c.prequal === 'Approved' ? <Badge kind="ok" dot>Approved</Badge> : c.prequal === 'Renewal due' ? <Badge kind="warn" dot>Renewal due</Badge> : <Badge kind="info" dot>{c.prequal}</Badge>}</td>
                  <td><span style={{ fontWeight: 600, color: (c.prequalExp - DB.TODAY) / 864e5 < 60 ? 'var(--warn)' : 'var(--text)' }}>{DB.fmt(c.prequalExp)} · {DB.rel(c.prequalExp)}</span></td>
                  <td><span className="tag"><Icon name="documents" size={11} /> Vendor registration</span></td>
                  <td><Icon name="chevR" size={16} style={{ color: 'var(--text-3)' }} /></td>
                </tr>
              ))}
            </tbody>
          </table>
          <div className="card-body" style={{ borderTop: '1px solid var(--border)' }}>
            <AINote title="2 contracts need renewal outreach" clause="AIX-01" action="Generate outreach list" onAction={() => onNav('schedule')}>
              Vale AMC renews in ~1 month and prequalification expires soon. AI drafted a renewal email and a covered-asset summary.
            </AINote>
          </div>
        </div>
      )}
    </div>
  );
}

/* ---------------- Scheduling -------------------------------------- */
/* =====================================================================
   Email Reminders (package feature 6) — live due-date queue, real sends
   via /api/cal-remind (Resend when configured), persisted send log,
   editable template with {{PLACEHOLDER}} variables.
   ===================================================================== */
function ScheduleModule({ onNav }) {
  const DB = window.DB;
  const [tab, setTab] = useState('queue');
  const [sending, setSending] = useState({}); // customerId -> bool
  const S = DB.reminderSettings || { windows: [30, 15, 7], includeOverdue: true, template: { subject: '', body: '' } };
  const day = 864e5;

  // ---- live queue: group due/overdue assets by customer ----
  const norm = DB.monitors.filter((m) => m.status !== 'Retired' && m.due);
  const maxWin = Math.max.apply(null, S.windows || [30]);
  const bucketOf = (m) => {
    const d = Math.ceil((new Date(m.due) - DB.TODAY) / day);
    if (d < 0) return S.includeOverdue ? 'Overdue' : null;
    const win = (S.windows || []).slice().sort((a, b) => a - b).find((w) => d <= w);
    return win ? win + ' days' : null;
  };
  const queue = {};
  norm.forEach((m) => {
    const b = bucketOf(m);
    if (!b) return;
    (queue[m.customer] = queue[m.customer] || []).push(Object.assign({ bucket: b }, m));
  });
  const groups = Object.keys(queue).map((cid) => ({ c: DB.custById(cid), items: queue[cid].sort((a, b) => new Date(a.due) - new Date(b.due)) }))
    .sort((a, b) => b.items.filter((i) => i.bucket === 'Overdue').length - a.items.filter((i) => i.bucket === 'Overdue').length);
  const totalDue = groups.reduce((a, g) => a + g.items.length, 0);
  const overdueN = groups.reduce((a, g) => a + g.items.filter((i) => i.bucket === 'Overdue').length, 0);
  const log = DB.reminderLog || [];
  const sentThisMonth = log.filter((l) => l.status === 'Sent' && new Date(l.date).getMonth() === DB.TODAY.getMonth()).length;

  const render = (tpl, g) => tpl
    .replace(/{{CUSTOMER}}/g, g.c.name)
    .replace(/{{CONTACT}}/g, g.c.contact || 'Sir/Madam')
    .replace(/{{COUNT}}/g, g.items.length)
    .replace(/{{LAB}}/g, DB.company.lab)
    .replace(/{{COMPANY}}/g, DB.company.name)
    .replace(/{{ASSETS}}/g, g.items.map((i) => `• ${i.id} — ${i.model} (S/N ${i.serial}) — due ${DB.fmt(i.due)}${i.bucket === 'Overdue' ? ' (OVERDUE)' : ''}`).join('\n'));

  const sendGroup = async (g) => {
    if (!g.c.email) { window.toast(g.c.name + ' has no contact email, so the reminder can\'t be sent. Add one on the customer record.', 'warn', { label: 'Edit customer', onClick: () => window.openCustomerForm && window.openCustomerForm(g.c) }); return; }
    setSending((s) => Object.assign({}, s, { [g.c.id]: true }));
    const subject = render(S.template.subject, g);
    const text = render(S.template.body, g);
    let status = 'Failed', detail = '';
    try {
      const r = await fetch('/api/cal-remind', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + CAL.token },
        body: JSON.stringify({ to: g.c.email, subject, text, customer: g.c.id, assets: g.items.map((i) => i.id) }),
      });
      const j = await r.json();
      status = j.status || (j.ok ? 'Sent' : 'Failed'); detail = j.detail || '';
    } catch (e) { detail = e.message; }
    dbAdd('reminderLog', {
      id: 'RM-' + Date.now().toString(36).toUpperCase(),
      date: new Date(), customer: g.c.id, to: g.c.email, subject,
      assets: g.items.map((i) => i.id), count: g.items.length, status, detail,
      by: (CAL.user && CAL.user.name) || '—',
    });
    setSending((s) => Object.assign({}, s, { [g.c.id]: false }));
    window.toast(status === 'Sent' ? 'Reminder emailed to ' + g.c.email : status === 'Queued' ? 'Reminder queued — connect an email provider to send' : 'Send failed — ' + detail, status === 'Sent' ? 'ok' : status === 'Queued' ? 'info' : 'bad');
  };
  const sendAll = async () => { for (const g of groups) { if (g.c.email) await sendGroup(g); } };

  const bucketBadge = (b) => <Badge kind={b === 'Overdue' ? 'bad' : b === '7 days' ? 'warn' : 'info'} dot>{b === 'Overdue' ? 'Overdue' : 'Due in ' + b}</Badge>;

  return (
    <div className="page page-wide rise">
      <PageHead title="Email Reminders" sub="Calibration-due reminders per customer · computed live from asset due dates · every send is logged">
        <Btn variant="primary" icon="bell" onClick={sendAll}>Send all ({groups.filter((g) => g.c.email).length})</Btn>
      </PageHead>

      <ModuleGuide module="schedule" onNav={onNav} />

      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(5,1fr)', marginBottom: 16 }}>
        <Kpi label="Assets in reminder window" value={totalDue} icon="bell" tone="info" delta={'within ' + maxWin + ' days'} />
        <Kpi label="Overdue" value={overdueN} icon="alert" tone="bad" />
        <Kpi label="Customers to notify" value={groups.length} icon="users" tone="info" />
        <Kpi label="Sent this month" value={sentThisMonth} icon="check-circle" tone="ok" />
        <Kpi label="Log entries" value={log.length} icon="documents" tone="info" />
      </div>

      <div className="tabs" style={{ marginBottom: 14 }}>
        {[['queue', 'Reminder queue'], ['log', 'Send log']].map(([id, l]) => (
          <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{l}</div>
        ))}
      </div>

      {tab === 'queue' && (
        groups.length === 0
          ? <div className="card card-pad" style={{ textAlign: 'center', padding: 40 }}>
              <Icon name="check-circle" size={34} style={{ color: 'var(--ok)' }} />
              <div style={{ fontWeight: 750, fontSize: 15, marginTop: 10 }}>Nothing due</div>
              <div className="faint" style={{ fontSize: 12.5, marginTop: 4 }}>No assets fall inside the reminder windows ({(S.windows || []).join(' / ')} days{S.includeOverdue ? ' + overdue' : ''}).</div>
            </div>
          : <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {groups.map((g) => (
                <div key={g.c.id} className="card">
                  <div className="card-head" style={{ gap: 10 }}>
                    <span className="avatar avatar-sm" style={{ background: g.c.color, borderRadius: 6 }}>{g.c.short}</span>
                    <div>
                      <div className="h-sec" style={{ fontSize: 14 }}>{g.c.name}</div>
                      <div className="faint" style={{ fontSize: 11 }}>{g.c.email ? g.c.contact + ' · ' + g.c.email : <span style={{ color: 'var(--bad)' }}>No contact email — add in Customers</span>}</div>
                    </div>
                    <span className="spacer" />
                    {g.items.some((i) => i.bucket === 'Overdue') && <Badge kind="bad">{g.items.filter((i) => i.bucket === 'Overdue').length} overdue</Badge>}
                    <Badge kind="info">{g.items.length} asset{g.items.length > 1 ? 's' : ''}</Badge>
                    <Btn size="sm" variant="primary" icon="bell" onClick={() => sendGroup(g)}
                      style={!g.c.email || sending[g.c.id] ? { opacity: .5, pointerEvents: sending[g.c.id] ? 'none' : 'auto' } : null}>
                      {sending[g.c.id] ? 'Sending…' : 'Send reminder'}
                    </Btn>
                  </div>
                  <table className="tbl">
                    <thead><tr><th>Asset</th><th>Model</th><th>Serial</th><th>Location</th><th>Due date</th><th>Window</th></tr></thead>
                    <tbody>
                      {g.items.map((i) => (
                        <tr key={i.id} className="clickable" onClick={() => window.openDetail('asset', DB.monitors.find((m) => m.id === i.id))}>
                          <td className="mono cell-strong" style={{ fontSize: 12 }}>{i.id}</td>
                          <td style={{ fontSize: 12.5 }}>{i.model}</td>
                          <td className="mono faint" style={{ fontSize: 11.5 }}>{i.serial}</td>
                          <td className="faint" style={{ fontSize: 12 }}>{i.location || '—'}</td>
                          <td style={{ fontSize: 12.5, fontWeight: 600, color: i.bucket === 'Overdue' ? 'var(--bad)' : 'var(--text)' }}>{DB.fmt(i.due)} · {DB.rel(i.due)}</td>
                          <td>{bucketBadge(i.bucket)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              ))}
            </div>
      )}

      {tab === 'log' && (
        <div className="card">
          <div className="card-head"><Icon name="history" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Send log</div><span className="spacer" /><span className="faint" style={{ fontSize: 11.5 }}>{log.length} entries · persisted</span></div>
          <table className="tbl">
            <thead><tr><th>Date</th><th>Customer</th><th>Recipient</th><th>Subject</th><th>Assets</th><th>By</th><th>Status</th></tr></thead>
            <tbody>
              {log.map((l) => {
                const c = DB.custById(l.customer);
                return (
                  <tr key={l.id}>
                    <td className="faint" style={{ fontSize: 12 }}>{DB.fmt(l.date)} {new Date(l.date).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })}</td>
                    <td><div className="row" style={{ gap: 6 }}><span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 6 }}>{c.short}</span>{c.short}</div></td>
                    <td className="faint" style={{ fontSize: 12 }}>{l.to}</td>
                    <td style={{ fontSize: 12, maxWidth: 260, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={l.subject}>{l.subject}</td>
                    <td className="mono" style={{ fontSize: 11.5 }}>{l.count}</td>
                    <td className="faint" style={{ fontSize: 12 }}>{l.by}</td>
                    <td><Badge kind={l.status === 'Sent' ? 'ok' : l.status === 'Queued' ? 'warn' : 'bad'} dot>{l.status}</Badge>{l.detail ? <div className="faint" style={{ fontSize: 10 }}>{l.detail}</div> : null}</td>
                  </tr>
                );
              })}
              {log.length === 0 && <tr><td colSpan={7} className="faint" style={{ textAlign: 'center', padding: 24 }}>No reminders sent yet.</td></tr>}
            </tbody>
          </table>
        </div>
      )}

    </div>
  );
}

function ReminderSettings({ settings }) {
  const DB = window.DB;
  const [subject, setSubject] = useState(settings.template.subject);
  const [body, setBody] = useState(settings.template.body);
  const [windows, setWindows] = useState((settings.windows || []).join(', '));
  const [incOverdue, setIncOverdue] = useState(settings.includeOverdue !== false);
  const inp = { width: '100%', fontFamily: 'inherit', fontSize: 13, padding: '10px 12px', borderRadius: 10, border: '1.5px solid var(--border-2)', outline: 'none', boxSizing: 'border-box', background: 'var(--surface)' };
  const save = () => {
    const w = windows.split(/[,\s]+/).map((x) => parseInt(x, 10)).filter((x) => x > 0 && x <= 365);
    dbSet('reminderSettings', { windows: w.length ? w : [30, 15, 7], includeOverdue: incOverdue, template: { subject, body } });
    window.toast('Reminder settings saved', 'ok');
  };
  return (
    <div className="settings-2col" style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: 16, alignItems: 'start' }}>
      <div className="card card-pad">
        <div className="eyebrow" style={{ marginBottom: 10 }}>Email template</div>
        <label className="eyebrow" style={{ fontSize: 10 }}>Subject</label>
        <input style={{ ...inp, margin: '5px 0 14px' }} value={subject} onChange={(e) => setSubject(e.target.value)} />
        <label className="eyebrow" style={{ fontSize: 10 }}>Message</label>
        <textarea style={{ ...inp, marginTop: 5, minHeight: 220, lineHeight: 1.5 }} value={body} onChange={(e) => setBody(e.target.value)} />
        <div className="faint" style={{ fontSize: 11.5, marginTop: 8 }}>
          Variables: <span className="mono">{'{{CUSTOMER}} {{CONTACT}} {{COUNT}} {{ASSETS}} {{LAB}} {{COMPANY}}'}</span>
        </div>
        <div className="row" style={{ marginTop: 14 }}>
          <span className="spacer" />
          <Btn variant="primary" icon="check" onClick={save}>Save settings</Btn>
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        <div className="card card-pad">
          <div className="eyebrow" style={{ marginBottom: 10 }}>Reminder windows</div>
          <label className="eyebrow" style={{ fontSize: 10 }}>Days before due (comma-separated)</label>
          <input style={{ ...inp, margin: '5px 0 12px' }} value={windows} onChange={(e) => setWindows(e.target.value)} placeholder="30, 15, 7" />
          <label className="row" style={{ gap: 8, fontSize: 13, cursor: 'pointer' }}>
            <input type="checkbox" checked={incOverdue} onChange={(e) => setIncOverdue(e.target.checked)} />
            Include overdue assets in the queue
          </label>
        </div>
        <div className="card card-pad">
          <div className="eyebrow" style={{ marginBottom: 8 }}>Delivery</div>
          <div style={{ fontSize: 12.5, lineHeight: 1.55, color: 'var(--text-2)' }}>
            Reminders send through the email provider configured in the <b>Email delivery (SMTP)</b> panel
            above. Until a provider is set up, sends are recorded as <Badge kind="warn">Queued</Badge> in
            the log so nothing is lost.
          </div>
        </div>
      </div>
    </div>
  );
}
function QMSModule({ onNav }) {
  const DB = window.DB;
  const [tab, setTab] = useState('capa');
  const tabs = [['capa', 'NCR / CAPA'], ['mr', 'Management review'], ['incidents', 'Incidents & complaints'], ['audits', 'Audits & risk']];
  const latestMR = DB.managementReviews.find((m) => m.status === 'Closed') || DB.managementReviews[0];
  return (
    <div className="page page-wide rise">
      <PageHead title={window.EDITION === 'essential' ? 'NCR & CAPA' : 'QMS & Compliance Registers'} sub={window.EDITION === 'essential' ? 'Nonconformities & corrective/preventive actions — one improvement register' : 'NCR · CAPA · management review · incidents · audits & risk — one improvement loop for the whole system'}>
        <Btn icon="filter" size="sm" onClick={() => window.toast('Filters applied', 'info')}>Filter</Btn><Btn variant="primary" icon="plus" onClick={() => window.openForm({ title: 'Raise NCR', sub: 'Opens in the shared improvement register (CMP)', icon: 'flag', fields: [{ label: 'Source', type: 'select', options: ['Internal audit', 'Proficiency testing', 'Complaint', 'HSE incident', 'Document control', 'Other'] }, { label: 'Severity', type: 'select', options: ['Minor', 'Major'] }, { label: 'Linked job / certificate', type: 'text', placeholder: 'optional' }, { label: 'Owner', type: 'select', options: window.DB.staff.map((s) => s.name) }, { label: 'Description', type: 'textarea', full: true, placeholder: 'Nonconformity detail & containment…' }], submitLabel: 'Raise NCR', done: 'NCR raised · added to shared register' })}>Raise NCR</Btn>
      </PageHead>
      <div className="kpi-grid" style={{ gridTemplateColumns: 'repeat(5,1fr)', marginBottom: 16 }}>
        <Kpi label="Open NCRs" value={6} icon="flag" tone="warn" />
        <Kpi label="CAPAs in progress" value={4} icon="refresh" tone="info" />
        <Kpi label="Overdue actions" value={2} icon="alert" tone="bad" />
        <Kpi label="Open complaints" value={DB.complaints.filter((c) => c.status !== 'Resolved').length} icon="chat" tone="info" />
        <Kpi label="On-time closure" value="88%" icon="trend" tone="ok" delta="+6%" deltaDir="up" />
      </div>
      {window.EDITION !== 'essential' && <div className="tabs" style={{ marginBottom: 16 }}>{tabs.map(([id, l]) => <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{l}</div>)}</div>}

      {tab === 'capa' && (
        <div className="card">
          <div className="card-head"><Icon name="check-shield" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>NCR / CAPA register</div><span className="spacer" /><Badge kind="ai" icon="sparkles">AI root-cause suggestions</Badge></div>
          <table className="tbl">
            <thead><tr><th>ID</th><th>Title</th><th>Source</th><th>Severity</th><th>Owner</th><th>Stage</th><th>Due</th><th>Status</th></tr></thead>
            <tbody>
              {DB.capa.map((x) => (
                <tr key={x.id} className="clickable" onClick={() => window.openDetail('capa', x)}>
                  <td className="mono cell-strong" style={{ fontSize: 12 }}>{x.id}</td>
                  <td style={{ fontSize: 12.5, maxWidth: 280 }}>{x.title}</td>
                  <td><span className="tag">{x.source}</span></td>
                  <td><Badge kind={x.severity === 'Major' ? 'bad' : 'warn'}>{x.severity}</Badge></td>
                  <td><Avatar id={x.owner} size="sm" /></td>
                  <td className="faint" style={{ fontSize: 12 }}>{x.stage}</td>
                  <td><span style={{ fontSize: 12, fontWeight: 600, color: x.due < DB.TODAY && x.status !== 'Closed' ? 'var(--bad)' : 'var(--text)' }}>{DB.rel(x.due)}</span></td>
                  <td><StatusBadge status={x.status} /></td>
                </tr>
              ))}
            </tbody>
          </table>
          <div className="card-body" style={{ borderTop: '1px solid var(--border)' }}>
            <AINote title="Recurring theme detected across 3 NCRs" clause="AIX-03" action="Draft systemic CAPA">
              Cylinder-validity and template-revision findings cluster around the gas-store handover. AI suggests a single systemic CAPA and a candidate root cause for review.
            </AINote>
          </div>
        </div>
      )}

      {tab === 'mr' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div className="card">
            <div className="card-head"><Icon name="check-shield" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Management review register</div><span className="faint" style={{ fontSize: 11, marginLeft: 8 }}>ISO 9001 §9.3 · ISO 17025 §8.9 · ADM-QSP-L2-P-008</span><span className="spacer" /><Btn size="sm" variant="primary" icon="plus" onClick={() => window.toast('Management review scheduled · agenda circulated', 'info')}>Schedule review</Btn></div>
            <table className="tbl">
              <thead><tr><th>Ref</th><th>Review</th><th>Date</th><th>Chair</th><th>Attendees</th><th className="num">Actions</th><th>Status</th></tr></thead>
              <tbody>{DB.managementReviews.map((m) => (
                <tr key={m.id}><td className="mono cell-strong" style={{ fontSize: 12 }}>{m.id}</td><td style={{ fontSize: 12.5 }}>{m.title}</td><td className="faint">{DB.fmt(m.date)}</td><td><Avatar id={m.chair} size="sm" /></td>
                  <td><div className="row" style={{ gap: -4 }}>{m.attendees.slice(0, 4).map((a, i) => <span key={i} style={{ marginLeft: i ? -6 : 0 }}><Avatar id={a} size="sm" /></span>)}</div></td>
                  <td className="num">{m.actions ? <Badge kind={m.open ? 'warn' : 'ok'}>{m.open ? `${m.open} open` : `${m.actions} ✓`}</Badge> : <span className="faint">—</span>}</td>
                  <td><StatusBadge status={m.status} /></td></tr>
              ))}</tbody>
            </table>
          </div>
          {/* Management review pack (MCR report) */}
          <div className="card">
            <div className="card-head"><Icon name="documents" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Management review pack — {latestMR.title}</div><span className="spacer" /><Btn size="sm" variant="ghost" icon="download" onClick={() => window.toast(`${latestMR.id} management review pack exported (PDF)`)}>Download pack</Btn></div>
            <div className="card-body" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
              <div>
                <div className="eyebrow" style={{ fontSize: 9.5, marginBottom: 8 }}>Inputs reviewed (§9.3.2)</div>
                {latestMR.inputs.map((inp, i) => (
                  <div key={i} className="row" style={{ gap: 9, padding: '6px 0', fontSize: 12.5 }}><Icon name="check-circle" size={15} style={{ color: 'var(--ok)', flex: 'none' }} />{inp}</div>
                ))}
              </div>
              <div>
                <div className="eyebrow" style={{ fontSize: 9.5, marginBottom: 8 }}>Outputs & decisions (§9.3.3)</div>
                {latestMR.outputs.map((o, i) => (
                  <div key={i} className="row" style={{ gap: 9, padding: '6px 0', fontSize: 12.5 }}><Icon name="arrowR" size={14} style={{ color: 'var(--primary-600)', flex: 'none' }} />{o}</div>
                ))}
                <div className="row" style={{ gap: 14, marginTop: 12, paddingTop: 10, borderTop: '1px solid var(--border)' }}>
                  <span className="faint" style={{ fontSize: 12 }}>Actions raised <b style={{ color: 'var(--text)' }}>{latestMR.actions}</b></span>
                  <span className="faint" style={{ fontSize: 12 }}>Open <b style={{ color: latestMR.open ? 'var(--warn)' : 'var(--ok)' }}>{latestMR.open}</b></span>
                  <span className="faint" style={{ fontSize: 12 }}>Chaired by <b style={{ color: 'var(--text)' }}>{DB.staffById(latestMR.chair).name}</b></span>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      {tab === 'incidents' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
          <div className="card">
            <div className="card-head"><Icon name="helmet" size={16} style={{ color: 'var(--warn)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Incident & near-miss register</div><span className="spacer" /><Btn size="sm" variant="ghost" icon="download" onClick={() => window.toast('HSE incident report generated (PDF)')}>Incident report</Btn><Btn size="sm" variant="primary" icon="plus" onClick={() => window.openForm({ title: 'Report incident / near-miss', sub: 'ADM-QMS-L4-F-012 · routes to the shared CAPA loop', icon: 'helmet', fields: [{ label: 'Type', type: 'select', options: ['Near miss', 'Incident', 'Property damage'] }, { label: 'Severity', type: 'select', options: ['Low', 'Medium', 'High'] }, { label: 'Location', type: 'text' }, { label: 'Date & time', type: 'datetime-local' }, { label: 'Description', type: 'textarea', full: true, placeholder: 'What happened, immediate action taken…' }], submitLabel: 'Submit report', done: 'Incident reported · CAPA loop opened' })}>Report incident</Btn></div>
            <table className="tbl">
              <thead><tr><th>Ref</th><th>Type</th><th>Description</th><th>Date</th><th>Severity</th><th>Linked CAPA</th><th>Status</th></tr></thead>
              <tbody>{DB.hseIncidents.map((x) => (
                <tr key={x.id} className="clickable" onClick={() => window.openDetail('hse', x)}>
                  <td className="mono cell-strong" style={{ fontSize: 12 }}>{x.id}</td>
                  <td><Badge kind={x.type === 'Incident' ? 'bad' : 'warn'}>{x.type}</Badge></td>
                  <td style={{ fontSize: 12.5 }}>{x.title}</td><td className="faint">{DB.fmt(x.date)}</td>
                  <td><Badge kind={x.severity === 'Medium' ? 'warn' : 'neutral'}>{x.severity}</Badge></td>
                  <td>{x.capa ? <span className="mono" style={{ fontSize: 11.5, color: 'var(--primary-700)' }}>{x.capa}</span> : <span className="faint">—</span>}</td>
                  <td><StatusBadge status={x.status} /></td>
                </tr>))}</tbody>
            </table>
          </div>
          <div className="card">
            <div className="card-head"><Icon name="chat" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Customer complaints</div><span className="faint" style={{ fontSize: 11, marginLeft: 8 }}>ISO 17025 §7.9</span></div>
            <table className="tbl">
              <thead><tr><th>Ref</th><th>Customer</th><th>Subject</th><th>Date</th><th>Linked CAPA</th><th>Status</th></tr></thead>
              <tbody>{DB.complaints.map((c) => { const cu = DB.custById(c.customer); return (
                <tr key={c.id}><td className="mono cell-strong" style={{ fontSize: 12 }}>{c.id}</td><td><div className="row" style={{ gap: 7 }}><span className="avatar avatar-sm" style={{ background: cu.color, borderRadius: 6 }}>{cu.short}</span>{cu.short}</div></td><td style={{ fontSize: 12.5 }}>{c.subject}</td><td className="faint">{DB.fmt(c.date)}</td><td>{c.capa ? <span className="mono" style={{ fontSize: 11.5, color: 'var(--primary-700)' }}>{c.capa}</span> : <span className="faint">—</span>}</td><td><StatusBadge status={c.status} /></td></tr>
              ); })}</tbody>
            </table>
          </div>
        </div>
      )}

      {tab === 'audits' && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, alignItems: 'start' }}>
          <div className="card">
            <div className="card-head"><Icon name="check-shield" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Internal audits</div><span className="spacer" /><Badge kind="neutral">ADM-QSP-L2-P-004</Badge></div>
            <table className="tbl">
              <thead><tr><th>Ref</th><th>Scope</th><th>Date</th><th>Auditor</th><th className="num">Findings</th><th>Status</th></tr></thead>
              <tbody>{DB.internalAudits.map((a) => (
                <tr key={a.id}><td className="mono cell-strong" style={{ fontSize: 11.5 }}>{a.id}</td><td style={{ fontSize: 12 }}>{a.scope}</td><td className="faint">{DB.fmt(a.date)}</td><td><Avatar id={a.auditor} size="sm" /></td><td className="num">{a.findings}{a.major ? <span style={{ color: 'var(--bad)' }}> ({a.major} maj)</span> : ''}</td><td><StatusBadge status={a.status} /></td></tr>
              ))}</tbody>
            </table>
          </div>
          <div className="card">
            <div className="card-head"><Icon name="alert" size={16} style={{ color: 'var(--warn)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Risk & opportunity register</div></div>
            <table className="tbl">
              <thead><tr><th>Ref</th><th>Area / task</th><th>Next review</th><th>Status</th></tr></thead>
              <tbody>{DB.riskAssessments.map((r) => (
                <tr key={r.id} className="clickable" onClick={() => window.openDetail('risk', r)}><td className="mono cell-strong" style={{ fontSize: 11.5 }}>{r.id}</td><td style={{ fontSize: 12, fontWeight: 600 }}>{r.area}</td><td><span style={{ fontWeight: 600, color: r.review <= DB.TODAY ? 'var(--bad)' : 'var(--text)' }}>{DB.rel(r.review)}</span></td><td><StatusBadge status={r.status} /></td></tr>
              ))}</tbody>
            </table>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { CylindersModule, AssetsModule, CustomersModule, CRMModule, ScheduleModule, QMSModule, PageHead, openAssetForm, openCustomerForm, openCylinderForm, printAssetLabel });
