/* =====================================================================
   AI-COS QHSE & Operational Excellence — application
   Reuses shared primitives (Icon, Btn, Badge, Avatar, Kpi, AINote, Bar,
   Ring, FormModal, toast…) from components.jsx. AI guidance is the spine.
   ===================================================================== */
/* hooks (useState/useEffect/useRef) come from components.jsx (loaded first) */
const DB = window.DB;
const scoreColor = (v) => v >= 90 ? 'var(--ok)' : v >= 75 ? 'var(--primary-600)' : v >= 50 ? 'var(--warn)' : 'var(--bad)';

/* ---------- bilingual EN/AR (NFR-LOC.1) ---------- */
const AR = {
  'Executive': 'الإدارة التنفيذية', 'HSE & Risk': 'الصحة والسلامة والمخاطر', 'Quality & Compliance': 'الجودة والامتثال', 'Assets & People': 'الأصول والأفراد', 'Delivery': 'التنفيذ',
  'Business Excellence': 'التميّز المؤسسي', 'Department Dashboards': 'لوحات الأقسام', 'Sentinel': 'سنتينل',
  'Incident Management': 'إدارة الحوادث', 'Risk & HAZID': 'المخاطر و HAZID', 'Permit to Work': 'تصاريح العمل', 'Emergency Preparedness': 'الاستعداد للطوارئ',
  'Quality / NCR': 'الجودة / عدم المطابقة', 'ISO Compliance': 'الامتثال للأيزو', 'Audit Management': 'إدارة التدقيق', 'CAPA': 'الإجراءات التصحيحية', 'Document Control': 'ضبط الوثائق',
  'Asset Integrity': 'سلامة الأصول', 'Competency & Training': 'الكفاءة والتدريب', 'Contractor Management': 'إدارة المقاولين', 'Fleet & Journey': 'الأسطول والرحلات',
  'Project Execution': 'تنفيذ المشاريع', 'Field Operations': 'العمليات الميدانية', 'Industrial Services': 'الخدمات الصناعية',
  'Operational excellence': 'التميّز التشغيلي', 'On track': 'على المسار', 'Sign out': 'تسجيل الخروج', 'QHSE Director': 'مدير الجودة والسلامة',
  'Search incidents, NCRs, permits, projects, assets…': 'ابحث في الحوادث والتصاريح والمشاريع والأصول…',
};
const t = (s) => (window.LANG === 'ar' && AR[s]) ? AR[s] : s;

function PageHead({ title, sub, children }) {
  return (
    <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 18, flexWrap: 'wrap', gap: 12 }}>
      <div><div className="h-page" dangerouslySetInnerHTML={{ __html: t(title) }} /><div className="h-sub">{sub}</div></div>
      <div className="row" style={{ gap: 8 }}>{children}</div>
    </div>
  );
}
function Tabs({ tabs, tab, setTab }) {
  return <div className="tabs" style={{ marginBottom: 16 }}>{tabs.map(([id, l]) => <div key={id} className={`tab${tab === id ? ' on' : ''}`} onClick={() => setTab(id)}>{l}</div>)}</div>;
}
const DATE_RANGES = ['Today', 'This week', 'Last week', 'Last 7 days', 'This month', 'Last 30 days', 'Last month', '6 months', 'This year', 'Last year', 'Custom…'];
function DateFilter({ defaultRange = 'Last 30 days', onChange }) {
  const [open, setOpen] = useState(false);
  const [val, setVal] = useState(defaultRange);
  return (
    <div style={{ position: 'relative' }}>
      <button onClick={() => setOpen((o) => !o)} style={{ display: 'inline-flex', alignItems: 'center', gap: 7, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 9, padding: '7px 11px', fontSize: 12.5, fontWeight: 600, fontFamily: 'inherit', cursor: 'pointer', color: 'var(--text)' }}>
        <Icon name="calendar" size={14} style={{ color: 'var(--text-3)' }} />{val}<Icon name="chevD" size={13} style={{ color: 'var(--text-3)' }} />
      </button>
      {open && <React.Fragment>
        <div style={{ position: 'fixed', inset: 0, zIndex: 60 }} onClick={() => setOpen(false)} />
        <div className="popover" style={{ right: 0, top: 'calc(100% + 6px)', minWidth: 168, zIndex: 61, maxHeight: '74vh', overflowY: 'auto' }}>
          {DATE_RANGES.map((r) => <div key={r} className="pop-item" style={{ fontWeight: r === val ? 700 : 500, color: r === val ? 'var(--primary-700)' : undefined }} onClick={() => { setVal(r); setOpen(false); onChange && onChange(r); window.toast && window.toast(`Filtered · ${r}`, 'info'); }}>{r}</div>)}
        </div>
      </React.Fragment>}
    </div>
  );
}
function Pagination({ page, perPage, total, onPage }) {
  const pages = Math.max(1, Math.ceil(total / perPage));
  const from = total === 0 ? 0 : (page - 1) * perPage + 1, to = Math.min(total, page * perPage);
  const nums = []; for (let i = 1; i <= pages; i++) { if (i === 1 || i === pages || Math.abs(i - page) <= 1) nums.push(i); else if (nums[nums.length - 1] !== '…') nums.push('…'); }
  const pb = (active, disabled) => ({ minWidth: 28, height: 28, borderRadius: 7, border: `1px solid ${active ? 'var(--primary-600)' : 'var(--border)'}`, background: active ? 'var(--primary-600)' : 'var(--surface)', color: active ? '#fff' : 'var(--text-2)', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.4 : 1, fontSize: 12, fontWeight: 600, fontFamily: 'inherit', display: 'inline-grid', placeItems: 'center' });
  return (
    <div className="row" style={{ justifyContent: 'space-between', marginTop: 12, flexWrap: 'wrap', gap: 8 }}>
      <span className="faint" style={{ fontSize: 11.5 }}>Showing {from}–{to} of {total} entries</span>
      <div className="row" style={{ gap: 4 }}>
        <button style={pb(false, page <= 1)} disabled={page <= 1} onClick={() => onPage(page - 1)}><Icon name="chevL" size={14} /></button>
        {nums.map((n, i) => n === '…' ? <span key={i} style={{ padding: '0 3px', color: 'var(--text-3)' }}>…</span> : <button key={i} style={pb(n === page)} onClick={() => onPage(n)}>{n}</button>)}
        <button style={pb(false, page >= pages)} disabled={page >= pages} onClick={() => onPage(page + 1)}><Icon name="chevR" size={14} /></button>
      </div>
    </div>
  );
}

/* ============================ SHELL ================================== */
const demoUser = () => DB.demoUsers.find((x) => x.id === window.DEMO_USER) || DB.demoUsers[0];
function Sidebar({ active, onNav, onLogout, collapsed, onToggleCollapse }) {
  const u = demoUser();
  const ALLIDS = DB.nav.flatMap((g) => g.items.flatMap((i) => [i.id, ...((i.children || []).map((c) => c.id))]));
  const ALL_NO_ADMIN = ALLIDS.filter((id) => id !== 'admin');
  const allowedSet = new Set(u.sysadmin ? ALLIDS : (!u.modules || u.modules === 'ALL') ? ALL_NO_ADMIN : u.modules);
  const groups = DB.nav.map((g) => ({ ...g, items: g.items.filter((it) => allowedSet.has(it.id)) })).filter((g) => g.items.length);
  const parentOf = (id) => groups.flatMap((g) => g.items).find((it) => it.id === id || (it.children || []).some((c) => c.id === id));
  const pa = parentOf(active);
  const [openId, setOpenId] = useState(pa && (pa.children || []).length ? pa.id : null);
  useEffect(() => { const p = parentOf(active); if (p && (p.children || []).length) setOpenId(p.id); }, [active]);
  return (
    <aside className={`sidebar${collapsed ? ' rail' : ''}`}>
      <div className="brand">
        {collapsed ? <BrandMark size={34} light /> : <img src="app/aicos-logo-dark.png" alt="AI-COS — QHSE Excellence" style={{ height: 32, width: 'auto', maxWidth: 150, display: 'block' }} />}
        {!collapsed && <div style={{ minWidth: 0 }}><div className="brand-sub" style={{ marginTop: 0 }}>{u.deptCode} · {u.level}{u.ro ? ' · view' : ''}</div></div>}
        <button className="nav-collapse" onClick={onToggleCollapse} title={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}><Icon name={collapsed ? 'chevR' : 'chevL'} size={15} /></button>
      </div>
      <nav className="nav">
        {groups.map((grp) => (
          <div key={grp.group}>
            {!collapsed && <div className="nav-group-label">{t(grp.group)}</div>}
            {grp.items.map((it) => {
              const kids = (it.children || []).filter((c) => allowedSet.has(it.id) || allowedSet.has(c.id));
              const open = openId === it.id && !collapsed;
              const childActive = kids.some((c) => c.id === active);
              return (
                <div key={it.id}>
                  <div className={`nav-item${active === it.id || (collapsed && childActive) ? ' active' : ''}`} title={collapsed ? t(it.label) : undefined}
                    onClick={() => { onNav(it.id); if (kids.length && !collapsed) setOpenId((prev) => prev === it.id ? null : it.id); }}>
                    <Icon name={it.icon} size={18} className="nav-ico" />
                    {!collapsed && <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t(it.label)}</span>}
                    {!collapsed && it.badge ? <span className={`nav-badge${it.badgeAmber ? ' amber' : ''}`} style={{ marginLeft: 0 }}>{it.badge}</span> : null}
                    {!collapsed && kids.length > 0 && <Icon name="chevD" size={13} style={{ marginLeft: it.badge ? 6 : 'auto', opacity: 0.55, transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .15s', flex: 'none' }} />}
                  </div>
                  {open && kids.map((c) => (
                    <div key={c.id} className={`nav-item nav-sub${active === c.id ? ' active' : ''}`} onClick={() => onNav(c.id)}>
                      <span className="nav-sub-dot" /><span style={{ fontSize: 12.5, opacity: active === c.id ? 1 : 0.85 }}>{t(c.label)}</span>
                    </div>
                  ))}
                </div>
              );
            })}
          </div>
        ))}
      </nav>
      <div className="side-foot">
        {!collapsed && <div className="readiness-card" onClick={() => onNav('dashboard')}>
          <div className="row" style={{ justifyContent: 'space-between' }}>
            <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.5px', color: '#9fb4d0', textTransform: 'uppercase' }}>{t('Operational excellence')}</span>
            <Icon name="sparkles" size={13} style={{ color: 'var(--primary-500)' }} fill />
          </div>
          <div className="row" style={{ gap: 10, marginTop: 8, alignItems: 'flex-end' }}>
            <span style={{ fontSize: 30, fontWeight: 800, color: '#fff', lineHeight: 1, letterSpacing: '-1px' }}>{DB.readiness.score}</span>
            <span style={{ fontSize: 12, color: '#7e94b0', marginBottom: 3 }}>/ 100</span>
            <span className="badge b-ok" style={{ marginLeft: 'auto', marginBottom: 2 }}>{t('On track')}</span>
          </div>
          <div className="progress" style={{ marginTop: 9, height: 5, background: '#0a1c30' }}><i style={{ width: `${DB.readiness.score}%`, background: 'linear-gradient(90deg, var(--primary-500), #58d6a6)' }} /></div>
        </div>}
        <div className="nav-item" style={{ marginTop: 6, color: '#8aa0bd' }} title={collapsed ? 'Sign out' : undefined} onClick={onLogout}><Icon name="logout" size={17} className="nav-ico" />{!collapsed && <span>{t('Sign out')}</span>}</div>
      </div>
    </aside>
  );
}
function Topbar({ crumb, onOpenAI, onOpenNotif, onNav, onLogout, lang, onToggleLang, aiOffline, onToggleAI, onSwitch, onReset }) {
  const [menu, setMenu] = useState(false);
  const u = demoUser();
  return (
    <header className="topbar">
      <div className="breadcrumb"><span>AI-COS</span><Icon name="chevR" size={13} /><b>{t(crumb)}</b></div>
      <DemoChip />
      <div className="spacer" />
      <div className="searchbox" onClick={(e) => e.currentTarget.querySelector('input').focus()}>
        <Icon name="search" size={16} /><input placeholder={t('Search incidents, NCRs, permits, projects, assets…')} /><kbd>⌘K</kbd>
      </div>
      <button className="icon-btn" onClick={onToggleLang} title="English / العربية" style={{ fontSize: 12.5, fontWeight: 700, width: 'auto', padding: '0 11px', letterSpacing: '.3px' }}>{lang === 'ar' ? 'EN' : 'ع'}</button>
      <button className="icon-btn" onClick={onOpenAI} title={aiOffline ? 'Sentinel — offline' : 'Sentinel'} style={{ position: 'relative', color: aiOffline ? 'var(--text-3)' : 'var(--ai)', borderColor: aiOffline ? 'var(--border)' : 'var(--ai-line)', background: aiOffline ? 'var(--surface-2)' : 'var(--ai-bg)' }}><Icon name="sparkles" size={17} fill />{aiOffline && <span style={{ position: 'absolute', top: -2, right: -2, width: 9, height: 9, borderRadius: '50%', background: 'var(--warn)', border: '2px solid var(--surface)' }} />}</button>
      <button className="icon-btn" onClick={onOpenNotif} title="Notifications"><Icon name="bell" size={17} /><span className="dot-badge" /></button>
      <div className="vr" />
      <div style={{ position: 'relative' }}>
        <div className="row" style={{ gap: 9, cursor: 'pointer' }} onClick={() => setMenu((m) => !m)}>
          <span className="avatar" style={{ background: u.color, color: '#fff', fontSize: 12.5 }}>{u.initials}</span>
          <div className="col" style={{ gap: 0, lineHeight: 1.25 }}><span style={{ fontSize: 13, fontWeight: 700 }}>{u.name}</span><span style={{ fontSize: 11, color: 'var(--text-3)' }}>{u.levelName} · {u.deptCode}{u.ro ? ' · read-only' : ''}</span></div>
          <Icon name="chevD" size={14} style={{ color: 'var(--text-3)' }} />
        </div>
        {menu && <React.Fragment>
          <div style={{ position: 'fixed', inset: 0, zIndex: 70 }} onClick={() => setMenu(false)} />
          <div className="popover" style={{ right: 0, top: 'calc(100% + 8px)', minWidth: 320, maxHeight: '78vh', overflowY: 'auto', padding: 8 }}>
            <div style={{ padding: '4px 8px 9px', borderBottom: '1px solid var(--border)', marginBottom: 6 }}>
              <div className="row" style={{ gap: 7, alignItems: 'center' }}><Icon name="users" size={14} style={{ color: 'var(--ai)' }} /><div style={{ fontSize: 12.5, fontWeight: 750 }}>Switch demo user</div><span className="badge" style={{ marginLeft: 'auto', background: '#fff4d6', color: '#8a5a00', fontSize: 9.5 }}>DEMO</span></div>
              <div className="faint" style={{ fontSize: 11, marginTop: 4, lineHeight: 1.45 }}>One click switches department, level, scoped navigation and Sentinel context. Sandbox only — no real accounts.</div>
            </div>
            {DB.demoUsers.map((d) => {
              const on = d.id === u.id;
              return (
                <div key={d.id} className="pop-item" style={{ alignItems: 'flex-start', gap: 10, padding: '8px 8px', borderRadius: 9, background: on ? 'var(--ai-bg)' : 'transparent' }} onClick={() => { setMenu(false); if (!on) onSwitch(d.id); }}>
                  <span className="avatar" style={{ background: d.color, color: '#fff', fontSize: 11, width: 30, height: 30, flex: 'none' }}>{d.initials}</span>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="row" style={{ gap: 6, alignItems: 'center' }}><span style={{ fontSize: 12.5, fontWeight: 700 }}>{d.name}</span><span className="badge" style={{ fontSize: 9, padding: '1px 6px', background: 'var(--surface-2)', color: 'var(--text-2)' }}>{d.level}</span>{d.ro && <span className="badge" style={{ fontSize: 9, padding: '1px 6px' }}>RO</span>}{on && <Icon name="check" size={13} style={{ color: 'var(--ai)', marginLeft: 'auto' }} />}</div>
                    <div className="faint" style={{ fontSize: 11, marginTop: 1 }}>{d.levelName} · {d.dept}</div>
                    <div className="faint" style={{ fontSize: 10.5, marginTop: 2, color: 'var(--text-3)' }}>{d.cap}</div>
                  </div>
                </div>
              );
            })}
            <div style={{ borderTop: '1px solid var(--border)', marginTop: 6, paddingTop: 6 }}>
              <div className="pop-item" onClick={() => { setMenu(false); onToggleAI(); }}><Icon name={aiOffline ? 'sparkles' : 'alert'} size={14} />{aiOffline ? 'Bring Sentinel online' : 'Simulate Sentinel offline'}</div>
              <div className="pop-item" onClick={() => { setMenu(false); onReset(); }}><Icon name="refresh" size={14} />Reset demo</div>
              <div className="pop-item" style={{ color: 'var(--bad)' }} onClick={() => { setMenu(false); onLogout(); }}><Icon name="logout" size={14} />Sign out</div>
            </div>
          </div>
        </React.Fragment>}
      </div>
    </header>
  );
}
function NotifPanel({ open, onClose, onNav }) {
  return (
    <Drawer open={open} onClose={onClose} width={400}>
      <div className="drawer-head"><Icon name="bell" size={18} /><div style={{ flex: 1, fontWeight: 750, fontSize: 15 }}>Notifications</div><button className="icon-btn" onClick={onClose}><Icon name="x" size={17} /></button></div>
      <div className="drawer-body" style={{ padding: 14 }}>
        {DB.notifications.map((it, i) => {
          const bg = { bad: 'var(--bad-bg)', ok: 'var(--ok-bg)', warn: 'var(--warn-bg)', info: 'var(--info-bg)' }[it.tone];
          const fg = { bad: 'var(--bad)', ok: 'var(--ok)', warn: 'var(--warn)', info: 'var(--primary-600)' }[it.tone];
          return (
            <div key={i} className="row" style={{ gap: 11, padding: '11px 8px', borderBottom: '1px solid var(--border)', alignItems: 'flex-start', cursor: 'pointer', borderRadius: 8 }} onClick={() => { onClose(); onNav(it.nav); }}>
              <span className="kpi-ico" style={{ background: bg, color: fg, width: 32, height: 32 }}><Icon name={it.ico} size={16} /></span>
              <div style={{ flex: 1 }}><div style={{ fontWeight: 650, fontSize: 13 }}>{it.t}</div><div className="faint" style={{ fontSize: 12, marginTop: 1 }}>{it.s}</div></div>
            </div>
          );
        })}
      </div>
    </Drawer>
  );
}

/* ===================== SENTINEL (drawer) ========================== */
const SENTINEL_SCENARIOS = [
  { q: 'A dropped-object near-miss just happened at height — what do I do?', steps: [
    'Make the area safe and account for all personnel before anything else.',
    'Log it as a High-Potential (HIPO) event — AI flags this as your 3rd at-height event on P-204 this quarter.',
    'Launch the AI Investigation Assistant on INC-26-031 — it will reconstruct the sequence and run a 5-Why / TapRooT analysis.',
    'Raise CAPA-26-040 on lifting controls (AI pre-drafted it) and assign to the HSE Manager.',
    'Issue a Learning Alert to all crews working at height across active projects.',
  ], cite: 'ISO 45001 §10.2 · IOGP LSR' },
  { q: 'A permit needs approval but it overlaps a hot-work permit nearby', steps: [
    'AI detects a SIMOPS conflict: PTW-26-114 (N₂ confined space) overlaps PTW-26-113 (hot work) in the same area.',
    'Do not approve in parallel — request a sequencing review or spatial separation.',
    'Confirm gas tests, rescue plan and standby are in place for the confined-space entry.',
    'Approve only once the SIMOPS risk score drops below threshold; the decision is logged to the audit trail.',
  ], cite: 'HAZID · PTW Procedure' },
  { q: 'We received an NCR that looks familiar — is it systemic?', steps: [
    'AI matched NCR-26-058 to 2 prior NCRs on bolt-torque records — this is a repeat signature, not isolated.',
    'Treat it as systemic: the root cause is likely a procedure/competency gap, not a one-off error.',
    'Open the AI root-cause view and accept the suggested CAPA targeting the torque-recording procedure.',
    'Add the affected inspectors to a competency refresh and verify effectiveness before closing.',
  ], cite: 'ISO 9001 §10.2' },
  { q: 'How ready are we for the upcoming client audit?', steps: [
    'Current audit-readiness signals: ISO 45001 audit in 12 days, 1 open finding, 1 overdue CAPA.',
    'AI predicts the most likely findings: lifting-controls CAPA closure evidence and 7 expiring certifications.',
    'Close CAPA-26-040 and renew the 7 certs before P-211 mobilization to lift the score above 90.',
    'Generate the audit pack — IMS manual, project HSE plans, training matrix and CAPA register — in one click.',
  ], cite: 'ISO 19011 · Management Review' },
];
/* ---------- conversational cross-module AI (reasons across the data) ---------- */
const QHSE_QA = [
  { m: ['block', 'p-211', 'p211', 'mobiliz', 'deploy'], sources: ['Competency', 'Asset Integrity', 'Contractors'], nav: 'competency', answer: "P-211 mobilization is blocked by three things I'm tracking across modules: (1) 7 certifications expire before deployment — 2 N₂-unit operators are missing a valid Aramco CSTP; (2) Hydrotest Pump HP-03 certification is overdue; (3) Gulf Crane Hire's insurance has lapsed (HSE score 78). Sequence: renew the certs, re-certify HP-03, suspend the contractor until insured. That protects both schedule and HSE readiness." },
  { m: ['audit', 'ready', '45001', '19011', 'assess'], sources: ['ISO Compliance', 'CAPA', 'Competency'], nav: 'compliance', answer: "Audit readiness: ISO 45001 surveillance is in 12 days with 1 open finding and 1 overdue CAPA. I predict the two most likely findings are (a) closure evidence for lifting-controls CAPA-26-040 and (b) the 7 expiring certifications. Close both and the 45001 score moves from 91 to ~93 — clearing the audit. The audit pack can be generated in one click." },
  { m: ['risk', 'biggest', 'top', 'danger', 'concern'], sources: ['Risk & HAZID', 'Permit to Work', 'Incidents'], nav: 'risk', answer: "Top live risks: (1) SIMOPS on P-204 — a confined-space N₂ permit overlaps hot work (score 16/25); (2) confined-space entry on P-211 with high consequence; (3) the lifting-controls CAPA is overdue and tied to 3 at-height near-misses. I recommend sequencing the P-204 permits first, then closing the lifting CAPA." },
  { m: ['incident', 'trend', 'height', 'dropped', 'pattern', 'near-miss', 'hipo'], sources: ['Incidents', 'CAPA'], nav: 'incidents', answer: "Incident trend: at-height events are your emerging pattern — INC-26-031 is the 3rd dropped-object near-miss on P-204 this quarter. I assess this as a systemic lifting/tethering control gap, not isolated. The AI investigation already produced the root cause and a CAPA; broadcasting the 'secure tools at height' learning alert to all crews is recommended." },
  { m: ['ncr', 'quality', 'repeat', 'torque', 'non-conform', 'nonconform'], sources: ['Quality / NCR'], nav: 'quality', answer: "Quality signal: NCR-26-058 (bolt-torque records) matches 2 prior NCRs — a repeat signature, i.e. systemic. The likely root cause is a missing verification gate in the torque-recording step under schedule pressure. The fix that worked before (a mandatory record-verification gate + inspector refresh) is recommended again." },
  { m: ['cert', 'expir', 'renew', 'competen', 'training', 'medical'], sources: ['Competency'], nav: 'competency', answer: "7 certifications expire within 30 days, concentrated before P-211: 3 N₂-unit operators and 4 lifting-gear certs, with 2 operators also missing a valid Aramco CSTP. The renewal/training plan is ready — scheduling it now keeps P-211 fully crewed and audit-ready." },
  { m: ['contractor', 'crane', 'insurance', 'vendor', 'subcontract'], sources: ['Contractors', 'Incidents'], nav: 'contractors', answer: "Contractor risk: Gulf Crane Hire's insurance has lapsed, HSE score is 78 (below threshold), yet prequalification renews in 20 days. I recommend suspending mobilization until insurance and HSE are restored — this contractor supplies cranes to the same P-204 lifting scope as the recent near-miss." },
  { m: ['environment', 'spill', '14001', 'effluent', 'emission', 'waste'], sources: ['ISO Compliance', 'Risk & HAZID'], nav: 'compliance', answer: "Environmental: the GAMEP environmental-permit obligation is overdue (audit-critical), and chemical-cleaning effluent on P-219 is a High-significance aspect sitting in 'Action'. I recommend actioning the GAMEP report and verifying bunding + licensed disposal before P-219 chemical cleaning starts." },
  { m: ['capa', 'overdue', 'corrective', 'effective'], sources: ['CAPA'], nav: 'capa', answer: "CAPA status: 1 overdue (lifting controls, audit-critical) and 1 ready to close (hand-protection RA refresh has evidence — I assess it effective). Run the AI effectiveness check before closing any CAPA; it scans 90 days for recurrence and checks evidence adequacy." },
  { m: ['simops', 'conflict', 'hot work', 'parallel'], sources: ['Permit to Work', 'Risk & HAZID'], nav: 'ptw', answer: "SIMOPS: PTW-26-114 (N₂ confined-space entry) overlaps PTW-26-113 (hot work) in the same area on P-204. Do not approve in parallel — sequence the activities or separate them spatially, confirm gas tests and a rescue plan, then approve once the SIMOPS score drops below threshold." },
  { m: ['hse', 'score', 'trir', 'performance', 'lti', 'safety', 'statistic'], sources: ['Business Excellence', 'Incidents'], nav: 'dashboard', answer: "HSE performance: 312 LTI-free days, 1.84M man-hours YTD, TRIR 0.42 and trending down. 2 open incidents, both with investigations and CAPAs in progress. Your at-height near-misses are the one cluster to watch — closing the lifting CAPA addresses it." },
];
function authorizedFor(cert) { return DB.competency.filter((c) => c.certs[cert] === 'V').map((c) => DB.staffById(c.staff).name); }
function answerQuery(q) {
  const t = q.toLowerCase();
  if (/(who|authoriz|qualif|competent|can)/.test(t) && /(confined|cse|space)/.test(t)) {
    const names = authorizedFor('CSE');
    return { answer: `Authorized for confined-space entry (valid CSE + medical): ${names.join(', ')}. One N₂ operator's Aramco CSTP has lapsed, so I exclude them from client-certified CSE tasks until it's renewed.`, sources: ['Competency'], nav: 'competency', conf: 'High' };
  }
  let best = null, score = 0;
  QHSE_QA.forEach((e) => { const s = e.m.reduce((n, w) => n + (t.includes(w) ? 1 : 0), 0); if (s > score) { score = s; best = e; } });
  if (best && score > 0) return { ...best, conf: 'High' };
  return { answer: "I reason across HSE, quality, compliance, risk, permits, competency, assets, projects and contractors. Try: “what's blocking P-211?”, “are we audit ready?”, “biggest risks now?”, or “who can do confined-space entry?”", sources: [], conf: 'Low' };
}
function AIChat({ onNav, height }) {
  const [msgs, setMsgs] = useState([{ role: 'ai', text: 'Ask me anything about HSE, quality, compliance, risk, permits, competency, assets or projects — I reason across your live data and cite the source.' }]);
  const [input, setInput] = useState('');
  const [thinking, setThinking] = useState(false);
  const endRef = useRef(null);
  const ask = (text) => { if (!text.trim()) return; const a = answerQuery(text); setMsgs((m) => [...m, { role: 'user', text }]); setInput(''); setThinking(true); setTimeout(() => { setMsgs((m) => [...m, { role: 'ai', text: a.answer, sources: a.sources, nav: a.nav, conf: a.conf }]); setThinking(false); setTimeout(() => endRef.current && endRef.current.scrollIntoView({ block: 'nearest' }), 40); }, 680); };
  const sugg = ["What's blocking P-211 mobilization?", 'Are we ready for the ISO 45001 audit?', 'What are our biggest risks right now?', 'Who is authorized for confined-space entry?'];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: height || 520, minHeight: 0 }}>
      <div style={{ flex: 1, overflowY: 'auto', padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 11 }}>
        {msgs.map((m, i) => (
          <div key={i} style={{ display: 'flex', justifyContent: m.role === 'user' ? 'flex-end' : 'flex-start' }}>
            <div style={{ maxWidth: '88%', padding: '10px 13px', borderRadius: 13, fontSize: 13, lineHeight: 1.5, background: m.role === 'user' ? 'var(--primary-600)' : 'var(--bg)', color: m.role === 'user' ? '#fff' : 'var(--text)', border: m.role === 'user' ? 'none' : '1px solid var(--border)' }}>
              {m.text}
              {m.sources && m.sources.length > 0 && <div className="row wrap" style={{ gap: 5, marginTop: 8 }}>{m.sources.map((s) => <span key={s} className="tag" style={{ fontSize: 10, background: 'var(--ai-bg)', color: '#5b3fd6', borderColor: 'var(--ai-line)' }}><Icon name="documents" size={10} />{s}</span>)}</div>}
              {m.conf && <div style={{ marginTop: 7 }}><span className="tag" style={{ fontSize: 10, border: 'none', background: m.conf === 'High' ? 'var(--ok-bg)' : 'var(--warn-bg)', color: m.conf === 'High' ? 'var(--ok)' : 'var(--warn)' }}><Icon name={m.conf === 'High' ? 'check-circle' : 'alert'} size={10} />{m.conf} confidence{m.sources && m.sources.length ? ' · grounded' : ''}</span></div>}
              {m.nav && <div style={{ marginTop: 8 }}><button onClick={() => onNav && onNav(m.nav)} style={{ background: 'none', border: 'none', color: 'var(--primary-600)', fontWeight: 600, fontSize: 11.5, cursor: 'pointer', fontFamily: 'inherit', padding: 0, display: 'inline-flex', alignItems: 'center', gap: 4 }}>Open module <Icon name="arrowR" size={12} /></button></div>}
            </div>
          </div>
        ))}
        {thinking && <div style={{ display: 'flex', justifyContent: 'flex-start' }}><div style={{ padding: '10px 13px', borderRadius: 13, background: 'var(--bg)', border: '1px solid var(--border)', fontSize: 12.5, color: 'var(--text-3)' }}><span className="row" style={{ gap: 6 }}><Icon name="sparkles" size={13} style={{ color: 'var(--ai)' }} />reasoning across modules…</span></div></div>}
        <div ref={endRef} />
      </div>
      <div style={{ padding: '10px 14px', borderTop: '1px solid var(--border)' }}>
        <div className="row wrap" style={{ gap: 6, marginBottom: 9 }}>{sugg.map((s) => <button key={s} className="tag" style={{ cursor: 'pointer', border: '1px solid var(--border)' }} onClick={() => ask(s)}>{s}</button>)}</div>
        <div className="searchbox" style={{ width: '100%' }}><Icon name="sparkles" size={15} style={{ color: 'var(--ai)' }} /><input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && ask(input)} placeholder="Ask Sentinel anything…" /><Btn variant="ai" size="sm" onClick={() => ask(input)}>Ask</Btn></div>
      </div>
    </div>
  );
}
/* ---------- AI next-best-actions (prioritised across the whole system) ---------- */
const NBA = [
  { pri: 'High', impact: 'Audit + safety', effort: '2d', title: 'Close lifting-controls CAPA-26-040', why: 'Overdue & audit-critical; linked to 3 at-height near-misses on P-204', nav: 'capa' },
  { pri: 'High', impact: 'Mobilization', effort: '5d', title: 'Renew 7 certifications before P-211', why: '2 N₂ operators missing Aramco CSTP — blocks crewing', nav: 'competency' },
  { pri: 'High', impact: 'Safety', effort: '1d', title: 'Resolve SIMOPS conflict on P-204', why: 'N₂ confined-space overlaps hot work — must sequence before approval', nav: 'ptw' },
  { pri: 'Med', impact: 'Compliance', effort: '3d', title: 'Action overdue GAMEP environmental permit', why: 'Regulatory obligation overdue; audit-critical', nav: 'compliance' },
  { pri: 'Med', impact: 'Asset readiness', effort: '2d', title: 'Re-certify Hydrotest Pump HP-03', why: 'Certification overdue; blocks P-211 hydrotest', nav: 'assets' },
  { pri: 'Med', impact: 'Contractor', effort: '1d', title: 'Suspend Gulf Crane Hire until insured', why: 'Insurance lapsed; HSE score 78 below threshold', nav: 'contractors' },
];
function NextBestActions({ onNav, limit }) {
  const items = limit ? NBA.slice(0, limit) : NBA;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {items.map((a, i) => (
        <div key={i} className="card hover-lift" style={{ padding: '12px 14px', borderLeft: `3px solid ${a.pri === 'High' ? 'var(--bad)' : 'var(--warn)'}`, cursor: 'pointer' }} onClick={() => onNav(a.nav)}>
          <div className="row" style={{ justifyContent: 'space-between' }}>
            <div className="row" style={{ gap: 8 }}><Badge kind={a.pri === 'High' ? 'bad' : 'warn'}>{a.pri}</Badge><span style={{ fontSize: 13, fontWeight: 700 }}>{a.title}</span></div>
            <Icon name="arrowR" size={15} style={{ color: 'var(--text-3)' }} />
          </div>
          <div className="faint" style={{ fontSize: 12, marginTop: 5 }}>{a.why}</div>
          <div className="row" style={{ gap: 7, marginTop: 7 }}><span className="tag">Impact: {a.impact}</span><span className="tag">~{a.effort}</span></div>
        </div>
      ))}
    </div>
  );
}
/* ---------- predictive analytics with explainability ---------- */
function MiniSpark({ data, color, limit }) {
  const w = 100, h = 30, min = Math.min(...data, limit || Infinity) - .05, max = Math.max(...data, limit || 0) + .05;
  const pts = data.map((v, i) => `${(i / (data.length - 1)) * w},${h - ((v - min) / (max - min)) * h}`).join(' ');
  return <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height: 34 }} preserveAspectRatio="none"><polyline points={pts} fill="none" stroke={color} strokeWidth="1.8" vectorEffect="non-scaling-stroke" /></svg>;
}
function AIPredictions({ onNav }) {
  const ranking = [['7 certifications (P-211 crew)', 92, 'competency'], ['Hydrotest Pump HP-03 cert', 88, 'assets'], ['Lifting-controls CAPA closure', 81, 'capa'], ['Gulf Crane Hire insurance', 76, 'contractors'], ['GAMEP environmental permit', 64, 'compliance']];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
        <div className="card card-pad">
          <div className="row" style={{ justifyContent: 'space-between' }}><span className="eyebrow" style={{ fontSize: 9.5 }}>TRIR forecast</span><Badge kind="ok">improving</Badge></div>
          <div style={{ fontSize: 30, fontWeight: 800, margin: '6px 0 2px' }}>0.42 <span className="faint" style={{ fontSize: 13, fontWeight: 500 }}>→ 0.36 proj.</span></div>
          <MiniSpark data={[0.71, 0.63, 0.58, 0.51, 0.47, 0.42]} color="var(--ok)" />
          <div className="faint" style={{ fontSize: 11.5, marginTop: 6 }}>AI projects continued decline next quarter <b>if</b> the at-height lifting CAPA closes; otherwise the recurring near-misses flatten the curve.</div>
        </div>
        <div className="card card-pad">
          <div className="row" style={{ justifyContent: 'space-between' }}><span className="eyebrow" style={{ fontSize: 9.5 }}>Audit-readiness trajectory</span><Badge kind="warn">action needed</Badge></div>
          <div style={{ fontSize: 30, fontWeight: 800, margin: '6px 0 2px' }}>84 <span className="faint" style={{ fontSize: 13, fontWeight: 500 }}>→ 92 if closed</span></div>
          <div style={{ marginTop: 8 }}><div className="row" style={{ justifyContent: 'space-between', fontSize: 11.5 }}><span className="faint">Now</span><span>84</span></div><Bar pct={84} color="var(--warn)" /><div className="row" style={{ justifyContent: 'space-between', fontSize: 11.5, marginTop: 8 }}><span className="faint">After closing CAPA + cert renewals</span><span>92</span></div><Bar pct={92} color="var(--ok)" /></div>
        </div>
      </div>
      <div className="card">
        <div className="card-head"><Icon name="trend" size={16} style={{ color: 'var(--primary-600)' }} /><div className="h-sec" style={{ fontSize: 14 }}>Predicted compliance gaps — ranked by likelihood</div><span className="spacer" /><Badge kind="ai">explainable</Badge></div>
        <table className="tbl"><thead><tr><th>Item</th><th>Risk of lapse</th><th></th></tr></thead><tbody>
          {ranking.map((r, i) => (
            <tr key={i} className="clickable" onClick={() => onNav(r[2])}><td style={{ fontSize: 12.5 }}>{r[0]}</td><td style={{ width: 200 }}><div className="row" style={{ gap: 8 }}><div style={{ flex: 1 }}><Bar pct={r[1]} color={r[1] > 80 ? 'var(--bad)' : 'var(--warn)'} /></div><b className="mono" style={{ fontSize: 11.5, width: 34 }}>{r[1]}%</b></div></td><td><Icon name="chevR" size={15} style={{ color: 'var(--text-3)' }} /></td></tr>
          ))}
        </tbody></table>
      </div>
    </div>
  );
}

function SentinelDrawer({ open, onClose, onNav }) {
  return (
    <Drawer open={open} onClose={onClose} width={460}>
      <div className="drawer-head" style={{ background: 'linear-gradient(120deg, var(--ai-bg), #fff)' }}>
        <span className="ai-badge-ico" style={{ width: 34, height: 34 }}><Icon name="sparkles" size={18} fill /></span>
        <div style={{ flex: 1 }}><div style={{ fontWeight: 750, fontSize: 15 }}>Sentinel</div><div className="faint" style={{ fontSize: 12 }}>Reasons across your data · advisory · logged</div></div>
        <button className="icon-btn" onClick={onClose}><Icon name="x" size={17} /></button>
      </div>
      <div className="drawer-body" style={{ padding: 0, display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
        {window.AI_OFFLINE
          ? <div style={{ padding: 24, textAlign: 'center', margin: 'auto' }}><span className="kpi-ico" style={{ width: 52, height: 52, background: 'var(--warn-bg)', color: 'var(--warn)', margin: '0 auto 14px' }}><Icon name="alert" size={24} /></span><div style={{ fontWeight: 750, fontSize: 16 }}>Sentinel is offline</div><div className="muted" style={{ fontSize: 13, marginTop: 6, lineHeight: 1.5, maxWidth: 320 }}>The AI service is temporarily unavailable. Every register, form and approval still works normally — Sentinel's guidance, drafting and answers resume automatically when it reconnects.</div></div>
          : <AIChat onNav={(id) => { onClose(); onNav(id); }} height="100%" />}
      </div>
    </Drawer>
  );
}

/* ===================== AI INVESTIGATION ASSISTANT =================== */
function AIInvestigation({ incident }) {
  const [run, setRun] = useState(false);
  const [step, setStep] = useState(0);
  const phases = [
    { h: 'Reconstructed sequence', icon: 'history', body: 'Lift in progress on V-204 platform → tagline not secured → wind gust → small tool displaced → fell 4 m inside barricaded zone. No personnel beneath; barricade held.' },
    { h: '5-Why analysis', icon: 'sparkles', body: '1) Object fell → not tethered. 2) Not tethered → no tool-tethering on the task. 3) No tethering → JSA omitted dropped-object control. 4) JSA gap → generic template reused without site review. 5) Generic template → no AI/peer check at JSA approval.' },
    { h: 'Contributing factors (TapRooT)', icon: 'grid', body: 'Procedure (JSA control gap) · Human-engineering (no tethering points) · Supervision (JSA approved without review). 3rd at-height event on P-204 this quarter — systemic.' },
    { h: 'Root cause', icon: 'flag', body: 'Inadequate dropped-object controls in the JSA process for work at height — a systemic procedure & review gap, not an individual error.' },
    { h: 'Suggested CAPA', icon: 'refresh', body: 'CAPA-26-040 — Mandate tool-tethering + AI-assisted JSA review for all at-height tasks; competency refresh for supervisors. Owner: HSE Manager · Due 14 days.', action: 'Accept CAPA' },
    { h: 'Learning alert', icon: 'bell', body: 'Draft alert "Secure your tools at height" ready to broadcast to all crews on active projects. Acknowledgement tracked.', action: 'Issue learning alert' },
  ];
  useEffect(() => {
    if (!run) return;
    if (step < phases.length) { const t = setTimeout(() => setStep((s) => s + 1), 700); return () => clearTimeout(t); }
  }, [run, step]);
  return (
    <div className="card" style={{ borderColor: 'var(--ai-line)' }}>
      <div className="card-head" style={{ background: 'linear-gradient(120deg, var(--ai-bg), #fff)' }}>
        <span className="ai-badge-ico"><Icon name="sparkles" size={15} fill /></span>
        <div className="h-sec" style={{ fontSize: 14 }}>AI Investigation Assistant</div>
        <span className="spacer" />
        {!run && <Btn variant="ai" size="sm" icon="sparkles" onClick={() => { setRun(true); setStep(0); }}>Run AI investigation</Btn>}
      </div>
      <div className="card-body" style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
        {!run ? <div className="faint" style={{ fontSize: 12.5, padding: '4px 2px' }}>Sentinel reconstructs the event, runs a 5-Why / TapRooT root-cause analysis, drafts the CAPA and a learning alert — you review and confirm each.</div>
          : phases.map((p, i) => (
            <div key={i} className="row" style={{ gap: 12, alignItems: 'flex-start', padding: '11px 0', borderBottom: i < phases.length - 1 ? '1px solid var(--border)' : 'none', opacity: i < step ? 1 : .25, transition: 'opacity .35s' }}>
              <span style={{ width: 30, height: 30, borderRadius: 9, flex: 'none', display: 'grid', placeItems: 'center', background: i < step ? 'var(--ai-bg)' : '#eef1f6', color: i < step ? 'var(--ai)' : 'var(--text-3)' }}>{i < step ? <Icon name={p.icon} size={15} /> : <span className="spin" style={{ width: 12, height: 12, border: '2px solid var(--border-2)', borderTopColor: 'var(--ai)', borderRadius: '50%' }} />}</span>
              <div style={{ flex: 1 }}>
                <div className="row" style={{ justifyContent: 'space-between' }}><div style={{ fontWeight: 700, fontSize: 12.5 }}>{p.h}</div>{p.action && i < step && <Btn size="sm" variant="ghost" icon="check" onClick={() => window.toast(p.action === 'Accept CAPA' ? 'CAPA-26-040 accepted · assigned to HSE Manager' : 'Learning alert issued to all crews · acknowledgement tracking on')}>{p.action}</Btn>}</div>
                <div className="faint" style={{ fontSize: 12, marginTop: 3, lineHeight: 1.5 }}>{p.body}</div>
              </div>
            </div>
          ))}
      </div>
      <style>{`@keyframes spin{to{transform:rotate(360deg)}}.spin{animation:spin .7s linear infinite}`}</style>
    </div>
  );
}

/* ===================== AI-ASSISTED ACTION MODAL ====================
   A create/action form with a LIVE Sentinel panel that recomputes
   suggestions (conflicts, missing controls, repeat records, risk score)
   as the user fills the form — the "AI guides you while you act" pitch.
   ==================================================================== */
const _tone = { bad: ['var(--bad)', 'var(--bad-bg)', 'var(--bad-line)', 'alert'], warn: ['var(--warn)', 'var(--warn-bg)', 'var(--warn-line)', 'alert'], ok: ['var(--ok)', 'var(--ok-bg)', 'var(--ok-line)', 'check-circle'], info: ['var(--primary-600)', 'var(--info-bg)', 'var(--primary-100)', 'sparkles'] };
const _inp = { width: '100%', marginTop: 5, fontFamily: 'inherit', fontSize: 13, padding: '9px 11px', borderRadius: 9, border: '1.5px solid var(--border-2)', outline: 'none', boxSizing: 'border-box', background: '#fff', color: 'var(--ink)' };
let _aiListener = null;
function openAIAssist(cfg) { if (_aiListener) _aiListener(cfg); }
function AIAssistModal({ title, sub, icon, fields, compute, submitLabel, done, onClose, width, onSave }) {
  const init = {}; fields.forEach((f) => { if (f.type === 'section') return; init[f.key] = f.value != null ? f.value : (f.type === 'checks' ? [] : f.options ? f.options[0] : ''); });
  const [vals, setVals] = useState(init);
  const [pulse, setPulse] = useState(false);
  const set = (k, v) => { setVals((s) => ({ ...s, [k]: v })); setPulse(true); };
  const toggle = (k, o) => { setVals((s) => { const cur = s[k] || []; return { ...s, [k]: cur.includes(o) ? cur.filter((x) => x !== o) : [...cur, o] }; }); setPulse(true); };
  useEffect(() => { if (pulse) { const t = setTimeout(() => setPulse(false), 450); return () => clearTimeout(t); } }, [pulse]);
  const res = compute(vals) || {};
  const tips = res.tips || [];
  const offline = window.AI_OFFLINE;
  const _chip = (on) => ({ cursor: 'pointer', userSelect: 'none', fontSize: 11.5, fontWeight: 600, padding: '5px 10px', borderRadius: 8, border: `1px solid ${on ? 'var(--primary-300)' : 'var(--border)'}`, background: on ? 'var(--primary-50)' : 'var(--surface)', color: on ? 'var(--primary-700)' : 'var(--text-2)', display: 'inline-flex', alignItems: 'center', gap: 5 });
  return (
    <React.Fragment>
      <div className="scrim" onClick={onClose} />
      <div className="modal" style={{ width: width || 800, maxHeight: '90vh', display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '18px 22px', borderBottom: '1px solid var(--border)' }} className="row">
          <span className="kpi-ico" style={{ background: 'var(--primary-50)', color: 'var(--primary-600)', width: 36, height: 36 }}><Icon name={icon || 'plus'} size={18} /></span>
          <div style={{ flex: 1 }}><div style={{ fontWeight: 750, fontSize: 16 }}>{title}</div><div className="faint" style={{ fontSize: 12 }}>{sub}</div></div>
          <button className="icon-btn" onClick={onClose}><Icon name="x" size={17} /></button>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 310px', flex: 1, overflow: 'hidden' }}>
          <div style={{ padding: '16px 22px', overflowY: 'auto', display: 'flex', flexWrap: 'wrap', gap: 12, alignContent: 'flex-start' }}>
            {fields.map((f, idx) => {
              if (f.show && !f.show(vals)) return null;
              if (f.type === 'section') return <div key={'s' + idx} style={{ width: '100%', marginTop: idx ? 8 : 0, paddingBottom: 5, borderBottom: '1px solid var(--border)' }}><div className="eyebrow" style={{ fontSize: 10.5, color: 'var(--primary-600)' }}>{f.label}</div>{f.hint && <div className="faint" style={{ fontSize: 10.5, marginTop: 2 }}>{f.hint}</div>}</div>;
              if (f.type === 'approvers') return (
                <div key={f.key} style={{ width: '100%' }}>
                  <label className="eyebrow" style={{ fontSize: 10 }}>{f.label}</label>
                  <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden', marginTop: 5 }}>{(f.options || []).map(([role, name], ri) => (
                    <div key={ri} className="row" style={{ gap: 10, padding: '8px 11px', borderBottom: ri < f.options.length - 1 ? '1px solid var(--border)' : 'none', alignItems: 'center' }}>
                      <span style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--surface-2)', border: '1px solid var(--border-2)', display: 'grid', placeItems: 'center', fontSize: 10, fontWeight: 700, color: 'var(--text-3)', flex: 'none' }}>{ri + 1}</span>
                      <div style={{ flex: 1, minWidth: 0 }}><div style={{ fontSize: 12, fontWeight: 650 }}>{role}</div><div className="faint" style={{ fontSize: 10.5 }}>{name}</div></div>
                      <span className="badge" style={{ background: 'var(--warn-bg)', color: '#9a6300', flex: 'none' }}>Awaiting</span>
                    </div>
                  ))}</div>
                </div>
              );
              return (
                <div key={f.key} style={{ width: f.half ? 'calc(50% - 6px)' : '100%' }}>
                  {f.type !== 'readonly' && <label className="eyebrow" style={{ fontSize: 10 }}>{f.label}</label>}
                  {f.type === 'select' ? <select value={vals[f.key]} onChange={(e) => set(f.key, e.target.value)} style={_inp}>{!f.value && <option value="">Select…</option>}{f.options.map((o) => <option key={o}>{o}</option>)}</select>
                    : f.type === 'textarea' ? <textarea value={vals[f.key]} onChange={(e) => set(f.key, e.target.value)} placeholder={f.placeholder} rows={f.rows || 3} style={_inp} />
                      : f.type === 'radio' ? <div className="row" style={{ gap: 6, flexWrap: 'wrap', marginTop: 5 }}>{f.options.map((o) => <span key={o} style={_chip(vals[f.key] === o)} onClick={() => set(f.key, o)}>{vals[f.key] === o && <Icon name="check" size={11} />}{o}</span>)}</div>
                        : f.type === 'checks' ? <div className="row" style={{ gap: 6, flexWrap: 'wrap', marginTop: 5 }}>{f.options.map((o) => { const on = (vals[f.key] || []).includes(o); return <span key={o} style={_chip(on)} onClick={() => toggle(f.key, o)}>{on && <Icon name="check" size={11} />}{o}</span>; })}</div>
                          : f.type === 'readonly' ? <div><div className="faint" style={{ fontSize: 10 }}>{f.label}</div><div style={{ fontSize: 13, fontWeight: 650, marginTop: 2 }}>{f.value || vals[f.key]}</div></div>
                            : f.type === 'sign' ? <button type="button" onClick={() => set(f.key, vals[f.key] ? '' : (f.signer || 'Signed'))} style={{ fontFamily: 'inherit', cursor: 'pointer', textAlign: 'left', width: '100%', marginTop: 5, height: 50, borderRadius: 9, padding: '6px 11px', background: vals[f.key] ? 'var(--ok-bg)' : 'var(--surface)', border: '1.5px dashed ' + (vals[f.key] ? 'var(--ok-line)' : 'var(--border-2)') }}>{vals[f.key] ? <span><span style={{ fontFamily: 'Segoe Script, cursive', fontSize: 18, color: 'var(--primary-700)' }}>{f.signer || 'Signed'}</span><span className="row" style={{ gap: 5, fontSize: 10, color: '#0f7a4f', marginTop: 1 }}><Icon name="check" size={11} />Electronically signed</span></span> : <span className="row" style={{ gap: 7, color: 'var(--text-3)', fontSize: 12, height: '100%', alignItems: 'center' }}><Icon name="signature" size={15} />Tap to sign</span>}</button>
                              : <input type={f.type === 'datetime' ? 'datetime-local' : (f.type === 'date' ? 'date' : (f.type === 'number' ? 'number' : 'text'))} value={vals[f.key]} onChange={(e) => set(f.key, e.target.value)} placeholder={f.placeholder} style={_inp} />}
                </div>
              );
            })}
          </div>
          <div style={{ borderLeft: '1px solid var(--border)', background: 'linear-gradient(160deg, var(--ai-bg), #fff)', padding: '16px 18px', overflowY: 'auto' }}>
            <div className="row" style={{ gap: 8, marginBottom: 12 }}><span className="ai-badge-ico" style={{ width: 28, height: 28, filter: offline ? 'grayscale(1)' : 'none', opacity: offline ? .6 : 1 }}><Icon name="sparkles" size={14} fill /></span><div><div style={{ fontWeight: 700, fontSize: 13 }}>Sentinel</div><div className="faint" style={{ fontSize: 10.5 }}>{offline ? 'offline · manual mode' : pulse ? 'analyzing…' : 'live guidance'}</div></div></div>
            {offline ? <div style={{ border: '1px solid var(--warn-line)', background: 'var(--warn-bg)', borderRadius: 10, padding: '10px 12px' }}><div className="row" style={{ gap: 7, alignItems: 'flex-start' }}><Icon name="alert" size={13} style={{ color: 'var(--warn)', flex: 'none', marginTop: 1 }} /><div style={{ fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.45 }}><b style={{ color: 'var(--warn)' }}>Sentinel is offline.</b> Complete the form manually — it submits and is logged as normal. AI checks resume automatically when the service is back.</div></div></div>
              : <React.Fragment>
                {res.risk != null && <div style={{ marginBottom: 12 }}><div className="row" style={{ justifyContent: 'space-between' }}><span className="eyebrow" style={{ fontSize: 9 }}>{res.riskLabel || 'Permit risk score'}</span><b style={{ color: res.risk >= 70 ? 'var(--bad)' : res.risk >= 40 ? 'var(--warn)' : 'var(--ok)' }}>{res.risk}/100</b></div><Bar pct={res.risk} color={res.risk >= 70 ? 'var(--bad)' : res.risk >= 40 ? 'var(--warn)' : 'var(--ok)'} /></div>}
                <div style={{ display: 'flex', flexDirection: 'column', gap: 9, opacity: pulse ? .45 : 1, transition: 'opacity .2s' }}>
                  {tips.length === 0 ? <div className="faint" style={{ fontSize: 12, lineHeight: 1.5 }}>Start filling the form — Sentinel will flag conflicts, missing controls and similar past records as you go.</div>
                    : tips.map((t, i) => { const c = _tone[t.tone] || _tone.info; return (
                      <div key={i} style={{ border: `1px solid ${c[2]}`, background: c[1], borderRadius: 10, padding: '9px 11px' }}>
                        <div className="row" style={{ gap: 7, alignItems: 'flex-start' }}><Icon name={c[3]} size={13} style={{ color: c[0], flex: 'none', marginTop: 1 }} /><div><div style={{ fontSize: 12, fontWeight: 700, color: c[0] }}>{t.title}</div><div style={{ fontSize: 11.5, color: 'var(--text-2)', marginTop: 2, lineHeight: 1.45 }}>{t.body}</div></div></div>
                      </div>
                    ); })}
                </div>
              </React.Fragment>}
          </div>
        </div>
        <div style={{ padding: '12px 22px', borderTop: '1px solid var(--border)' }} className="row">
          <span className="faint" style={{ fontSize: 11, flex: 1 }}><Icon name="lock" size={11} /> AI is advisory · your submission is human-confirmed &amp; logged</span>
          <Btn variant="ghost" onClick={onClose}>Cancel</Btn>
          {res.block && !offline
            ? <Btn variant="ghost" icon="alert" disabled style={{ color: 'var(--bad)', borderColor: 'var(--bad-line)' }}>{res.blockLabel || 'Blocked'}</Btn>
            : <Btn variant="primary" icon="check" onClick={() => { try { onSave && onSave(vals); } catch (e) { console.error(e); } onClose(); window.toast(done || 'Submitted · AI follow-up ready'); }}>{submitLabel}</Btn>}
        </div>
      </div>
    </React.Fragment>
  );
}
class _ModalEB extends React.Component {
  constructor(p) { super(p); this.state = { e: null }; }
  static getDerivedStateFromError(e) { return { e }; }
  render() {
    if (this.state.e) return (
      <React.Fragment>
        <div className="scrim" onClick={this.props.onClose} />
        <div className="modal" style={{ width: 420, padding: 22 }}>
          <div className="row" style={{ gap: 9, marginBottom: 8 }}><Icon name="alert" size={18} style={{ color: 'var(--warn)' }} /><div style={{ fontWeight: 750, fontSize: 15 }}>Form unavailable</div></div>
          <div className="faint" style={{ fontSize: 12.5, lineHeight: 1.5 }}>Sentinel hit a snag rendering this form. Your data was not lost — please close and try again.</div>
          <div className="row" style={{ justifyContent: 'flex-end', marginTop: 16 }}><Btn variant="primary" onClick={this.props.onClose}>Close</Btn></div>
        </div>
      </React.Fragment>
    );
    return this.props.children;
  }
}
function AIAssistHost() {
  const [cfg, setCfg] = useState(null);
  useEffect(() => { _aiListener = (c) => setCfg(c); return () => { _aiListener = null; }; }, []);
  if (!cfg) return null;
  return <_ModalEB onClose={() => setCfg(null)}><AIAssistModal {...cfg} onClose={() => setCfg(null)} /></_ModalEB>;
}
window.openAIAssist = openAIAssist;

window.QHSE = { PageHead, Tabs, Sidebar, Topbar, NotifPanel, SentinelDrawer, AIInvestigation, AIAssistHost, scoreColor };
