/* =====================================================================
   AI-COS — Shared UI primitives + icon set  →  window.*
   ===================================================================== */
const { useState, useEffect, useRef, useMemo } = React;

/* ---------------- Icons (Lucide-style line set) --------------------- */
const ICONS = {
  grid: 'M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z',
  calendar: 'M8 2v4M16 2v4M3 10h18M5 4h14a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z',
  briefcase: 'M4 7h16a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1zM8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 12h18',
  receipt: 'M5 3v18l2-1.5L9 21l2-1.5L13 21l2-1.5L17 21l2-1.5V3l-2 1.5L15 3l-2 1.5L11 3 9 4.5 7 3zM8 8h8M8 12h8M8 16h5',
  workflow: 'M4 4h6v6H4zM14 14h6v6h-6zM7 10v4a2 2 0 0 0 2 2h4M17 10v4',
  shield: 'M12 2l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V5z',
  cylinder: 'M12 3c-3 0-5 1-5 2.5S9 8 12 8s5-1 5-2.5S15 3 12 3zM7 5.5v13C7 20 9 21 12 21s5-1 5-2.5v-13',
  cpu: 'M6 6h12v12H6zM9 9h6v6H9M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3',
  certificate: 'M4 4h16v11H4zM4 15l3 5 2-2 2 2M12 8h5M7 8h2M7 11h8',
  'check-shield': 'M12 2l8 3v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V5zM9 11l2 2 4-4',
  documents: 'M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7zM14 2v5h5M9 13h6M9 17h6',
  users: 'M16 11a4 4 0 1 0-4-4M17 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',
  helmet: 'M3 14a9 9 0 0 1 18 0M2 14h20v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zM12 5v4M8 6.5l1 3M16 6.5l-1 3',
  chat: 'M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.7a8.5 8.5 0 0 1-.9-3.8A8.38 8.38 0 0 1 11.5 3a8.38 8.38 0 0 1 9.5 8.5z',
  box: 'M21 8l-9-5-9 5 9 5 9-5zM3 8v8l9 5 9-5V8M12 13v8',
  wrench: 'M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 0 0 5.4-5.4l-2.5 2.5-2.3-.6-.6-2.3z',
  truck: 'M1 6h13v10H1zM14 9h4l3 3v4h-7M5.5 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM17.5 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',
  chart: 'M3 3v18h18M8 15v3M13 11v7M18 7v11',
  sparkles: 'M12 3l1.8 4.7L18 9.5l-4.2 1.8L12 16l-1.8-4.7L6 9.5l4.2-1.8zM5 16l.9 2.1L8 19l-2.1.9L5 22l-.9-2.1L2 19l2.1-.9zM19 14l.6 1.6L21 16l-1.4.4L19 18l-.6-1.6L17 16l1.4-.4z',
  settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-2.82 1.17V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 7 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 2.6 14H2.5a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4 8.6a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6V4.5a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 2.82 1.17l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9v.09a2 2 0 0 1 0 4h-.09',
  search: 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.3-4.3',
  bell: 'M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 0 1-3.4 0',
  check: 'M20 6L9 17l-5-5',
  'check-circle': 'M22 11.1V12a10 10 0 1 1-5.9-9.1M22 4L12 14.01l-3-3',
  x: 'M18 6L6 18M6 6l12 12',
  'x-circle': 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM15 9l-6 6M9 9l6 6',
  alert: 'M10.3 3.9L1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0zM12 9v4M12 17h.01',
  clock: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 6v6l4 2',
  lock: 'M5 11h14v10H5zM8 11V7a4 4 0 0 1 8 0v4',
  chevR: 'M9 18l6-6-6-6',
  chevL: 'M15 18l-6-6 6-6',
  chevD: 'M6 9l6 6 6-6',
  plus: 'M12 5v14M5 12h14',
  arrowUp: 'M12 19V5M5 12l7-7 7 7',
  arrowDown: 'M12 5v14M5 12l7 7 7-7',
  arrowR: 'M5 12h14M12 5l7 7-7 7',
  ext: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3',
  download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
  qr: 'M3 3h7v7H3zM14 3h7v7h-7zM3 14h7v7H3zM14 14h3v3h-3zM20 14v3M14 20h3M20 20h1',
  filter: 'M22 3H2l8 9.5V19l4 2v-8.5z',
  dots: 'M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM19 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2z',
  eye: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
  pin: 'M12 21s-7-6.5-7-11a7 7 0 0 1 14 0c0 4.5-7 11-7 11zM12 12a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z',
  thermometer: 'M14 14.76V5a2 2 0 1 0-4 0v9.76a4 4 0 1 0 4 0z',
  droplet: 'M12 2.7l5.3 5.3a7.5 7.5 0 1 1-10.6 0z',
  gauge: 'M12 22a10 10 0 1 1 0-20 10 10 0 0 1 0 20M12 12l4-3M8 13a4 4 0 0 1 8 0',
  signature: 'M3 17c3 0 4-9 6-9s2 6 4 6 2-3 3-3 2 1 4 1M3 21h18',
  flask: 'M9 3h6M10 3v6l-5 9a2 2 0 0 0 1.8 3h10.4a2 2 0 0 0 1.8-3l-5-9V3M7.5 14h9',
  history: 'M3 3v6h6M3 9a9 9 0 1 1 1 6M12 7v5l3 2',
  link: 'M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1',
  logout: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9',
  star: 'M12 2l3 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.9 21l1.2-6.8-5-4.9 6.9-1z',
  trend: 'M22 7l-8.5 8.5-4-4L2 19M16 7h6v6',
  building: 'M3 21h18M5 21V5a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v16M9 7h2M9 11h2M9 15h2M15 21V11h4v10',
  phone: 'M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3-8.6A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1.9.4 1.8.7 2.7a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.4-1.2a2 2 0 0 1 2.1-.5c.9.3 1.8.6 2.7.7a2 2 0 0 1 1.7 2z',
  refresh: 'M21 2v6h-6M3 22v-6h6M3.5 9a9 9 0 0 1 14.8-3.4L21 8M20.5 15a9 9 0 0 1-14.8 3.4L3 16',
  flag: 'M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1zM4 22v-7',
  menu: 'M3 12h18M3 6h18M3 18h18',
};

function Icon({ name, size = 18, sw = 1.9, fill = false, style, className }) {
  const d = ICONS[name] || ICONS.grid;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill ? 'currentColor' : 'none'}
         stroke="currentColor" strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round"
         style={style} className={className} aria-hidden="true">
      {d.split('M').filter(Boolean).map((seg, i) => <path key={i} d={'M' + seg} />)}
    </svg>
  );
}

/* ---------------- Badge ------------------- */
function Badge({ kind = 'neutral', children, dot, icon, style }) {
  return (
    <span className={`badge b-${kind}`} style={style}>
      {dot && <span className="dot" />}
      {icon && <Icon name={icon} size={12} />}
      {children}
    </span>
  );
}
const STATUS_KIND = {
  'Valid': 'ok', 'Active': 'ok', 'Pass': 'ok', 'Paid': 'ok', 'Satisfactory': 'ok', 'Published': 'ok', 'Approved': 'ok', 'OK': 'ok', 'Closed': 'ok', 'Dispatched': 'ok',
  'Due soon': 'warn', 'Renewal due': 'warn', 'Low pressure': 'warn', 'Low': 'warn', 'In review': 'warn', 'Sent': 'warn', 'Questionable': 'warn', 'In validation': 'warn', 'Draft': 'neutral', 'In progress': 'info', 'In calibration': 'info', 'In Calibration': 'info', 'QA/QC Review': 'warn', 'Certified': 'ok', 'Review': 'info', 'Assigned': 'info', 'Received': 'neutral', 'Inspection': 'info', 'Open': 'info',
  'Overdue': 'bad', 'Expired': 'bad', 'Fail': 'bad', 'Failed': 'bad', 'Under repair': 'warn', 'Retired': 'neutral', 'Superseded': 'neutral', 'Withdrawn': 'bad',
  'Scheduled': 'info', 'Planned': 'neutral', 'Resolved': 'ok', 'Actions open': 'warn', 'Logged': 'neutral', 'Action taken': 'info', 'Investigation': 'warn', 'Review due': 'warn',
};
function StatusBadge({ status }) {
  const kind = STATUS_KIND[status] || 'neutral';
  return <Badge kind={kind} dot>{status}</Badge>;
}

/* ---------------- Avatar ------------------ */
function Avatar({ id, size = 'm' }) {
  const s = window.DB.staffById(id) || { initials: '?', color: '#94a3b8' };
  const cls = size === 'sm' ? 'avatar avatar-sm' : size === 'lg' ? 'avatar avatar-lg' : 'avatar';
  return <span className={cls} style={{ background: s.color }} title={s.name}>{s.initials}</span>;
}
function CustChip({ id, sub }) {
  const c = window.DB.custById(id);
  if (!c) return null;
  return (
    <div className="row" style={{ gap: 9 }}>
      <span className="avatar avatar-sm" style={{ background: c.color, borderRadius: 7 }}>{c.short}</span>
      <div className="col" style={{ gap: 0 }}>
        <span className="cell-strong" style={{ fontSize: 13 }}>{c.name}</span>
        {sub && <span className="cell-sub">{c.site}</span>}
      </div>
    </div>
  );
}

/* ---------------- Button ------------------ */
function Btn({ variant = 'ghost', size, icon, iconR, children, onClick, disabled, style }) {
  const sz = size === 'sm' ? ' btn-sm' : size === 'lg' ? ' btn-lg' : '';
  return (
    <button className={`btn btn-${variant}${sz}`} onClick={onClick} disabled={disabled} style={style}>
      {icon && <Icon name={icon} size={size === 'sm' ? 13 : 15} />}
      {children}
      {iconR && <Icon name={iconR} size={size === 'sm' ? 13 : 15} />}
    </button>
  );
}

/* ---------------- KPI card ----------------- */
function Kpi({ label, value, unit, icon, tone = 'info', delta, deltaDir, sub }) {
  const toneBg = { info: 'var(--info-bg)', ok: 'var(--ok-bg)', warn: 'var(--warn-bg)', bad: 'var(--bad-bg)', ai: 'var(--ai-bg)' }[tone];
  const toneFg = { info: 'var(--primary-600)', ok: 'var(--ok)', warn: 'var(--warn)', bad: 'var(--bad)', ai: 'var(--ai)' }[tone];
  return (
    <div className="kpi hover-lift">
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <span className="kpi-label">{label}</span>
        {icon && <span className="kpi-ico" style={{ background: toneBg, color: toneFg }}><Icon name={icon} size={17} /></span>}
      </div>
      <div className="kpi-val">{value}{unit && <span style={{ fontSize: 15, fontWeight: 700, color: 'var(--text-2)', marginLeft: 3 }}>{unit}</span>}</div>
      {(delta || sub) && (
        <div className="kpi-delta" style={{ color: deltaDir === 'up' ? 'var(--ok)' : deltaDir === 'down' ? 'var(--bad)' : 'var(--text-3)' }}>
          {deltaDir && <Icon name={deltaDir === 'up' ? 'arrowUp' : 'arrowDown'} size={13} />}
          {delta}{sub && <span style={{ color: 'var(--text-3)', fontWeight: 500 }}>{sub}</span>}
        </div>
      )}
    </div>
  );
}

/* ---------------- AI note ------------------ */
function AINote({ title, children, action, onAction, clause }) {
  if (window.EDITION === 'essential') return null; // no AI in the Essential edition
  return (
    <div className="ai-note">
      <span className="ai-badge-ico"><Icon name="sparkles" size={15} fill /></span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="row" style={{ gap: 8 }}>
          <span className="ai-label">AI · advisory</span>
          {clause && <span className="gate-clause" style={{ color: 'var(--ai)' }}>{clause}</span>}
        </div>
        <div style={{ fontWeight: 700, fontSize: 13, marginTop: 3 }}>{title}</div>
        {children && <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>{children}</div>}
        {action && <button className="btn btn-ai btn-sm" style={{ marginTop: 9 }} onClick={onAction}>{action}<Icon name="arrowR" size={13} /></button>}
      </div>
    </div>
  );
}

/* ---------------- Live Sentinel analysis panel (reactive while filling a form) ---------------- */
function SentinelPanel({ insight, onApply }) {
  if (window.EDITION === 'essential') return null;
  if (!insight) return null;
  const toneMap = { warn: ['var(--warn)', 'var(--warn-bg)'], bad: ['var(--bad)', 'var(--bad-bg)'], ok: ['var(--ok)', 'var(--ok-bg)'], info: ['var(--ai)', 'var(--ai-bg, #eef0ff)'] };
  const headTone = toneMap[insight.tone] || toneMap.info;
  const lines = insight.lines || [];
  const suggest = insight.suggest || [];
  return (
    <div className="ai-note" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 10 }}>
      <div className="row" style={{ gap: 10, alignItems: 'flex-start' }}>
        <span className="ai-badge-ico"><Icon name="sparkles" size={15} fill /></span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div className="row" style={{ gap: 8, alignItems: 'center' }}>
            <span className="ai-label">AI · live analysis</span>
            <span className="row" style={{ gap: 4, alignItems: 'center', fontSize: 9.5, fontWeight: 700, color: 'var(--ai)', textTransform: 'uppercase', letterSpacing: '.04em' }}>
              <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--ai)', display: 'inline-block', animation: 'sentinelPulse 1.4s ease-in-out infinite' }} />Sentinel
            </span>
            {insight.clause && <span className="gate-clause" style={{ color: 'var(--ai)' }}>{insight.clause}</span>}
          </div>
          {insight.headline && <div style={{ fontWeight: 750, fontSize: 13, marginTop: 4, color: headTone[0] }}>{insight.headline}</div>}
        </div>
      </div>
      {lines.length > 0 && (
        <div style={{ display: 'grid', gap: 6 }}>
          {lines.map((ln, i) => {
            const t = toneMap[ln.tone] || toneMap.info;
            return (
              <div key={i} className="row" style={{ gap: 8, alignItems: 'flex-start', fontSize: 12.5, lineHeight: 1.35 }}>
                <span style={{ width: 16, height: 16, borderRadius: '50%', background: t[1], color: t[0], display: 'grid', placeItems: 'center', flex: 'none', marginTop: 1 }}><Icon name={ln.icon || 'check'} size={10} /></span>
                <span className="muted" style={{ flex: 1 }}>{ln.text}</span>
              </div>
            );
          })}
        </div>
      )}
      {suggest.length > 0 && (
        <div style={{ borderTop: '1px dashed var(--border-2)', paddingTop: 9 }}>
          <div className="faint" style={{ fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.04em', marginBottom: 6 }}>Suggested by Sentinel</div>
          <div className="row" style={{ gap: 7, flexWrap: 'wrap' }}>
            {suggest.map((s, i) => (
              <button key={i} type="button" className="btn btn-ai btn-sm" onClick={() => onApply && s.set && onApply(s.set.key, s.set.val)} title={s.set ? 'Apply to form' : ''} style={{ fontSize: 11.5 }}>
                <Icon name="sparkles" size={12} fill />{s.text}{s.set && <Icon name="plus" size={12} />}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

/* ---------------- Gate chip ---------------- */
function Gate({ gate }) {
  const map = { pass: ['pass', 'check-circle', 'var(--ok)', 'var(--ok-bg)'], block: ['block', 'x-circle', 'var(--bad)', 'var(--bad-bg)'], warn: ['warn', 'alert', 'var(--warn)', 'var(--warn-bg)'], pending: ['pending', 'lock', 'var(--text-3)', '#eef1f6'] };
  const [cls, ico, fg, bg] = map[gate.state] || map.pending;
  return (
    <div className={`gate ${cls}`}>
      <span className="gate-ico" style={{ background: bg, color: fg }}><Icon name={ico} size={18} /></span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="row" style={{ gap: 8 }}>
          <span className="gate-title">{gate.title}</span>
          <span className="gate-clause">{gate.clause}</span>
          <span style={{ marginLeft: 'auto' }}>
            {gate.state === 'pass' && <Badge kind="ok">Passed</Badge>}
            {gate.state === 'block' && <Badge kind="bad">Blocked</Badge>}
            {gate.state === 'pending' && <Badge kind="neutral">On submit</Badge>}
          </span>
        </div>
        <div className="gate-desc">{gate.desc}</div>
      </div>
    </div>
  );
}

/* ---------------- Section header ----------- */
function SectionHead({ title, sub, right }) {
  return (
    <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14 }}>
      <div>
        <div className="h-sec">{title}</div>
        {sub && <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>{sub}</div>}
      </div>
      {right}
    </div>
  );
}

/* ---------------- Progress / ring ---------- */
function Bar({ pct, color = 'var(--primary-600)' }) {
  return <div className="progress"><i style={{ width: `${Math.max(0, Math.min(100, pct))}%`, background: color }} /></div>;
}
function Ring({ value, size = 132, stroke = 12, color = 'var(--primary-600)', track = '#eaeef4', children }) {
  const r = (size - stroke) / 2, c = 2 * Math.PI * r, off = c - (value / 100) * c;
  return (
    <div style={{ position: 'relative', width: size, height: size }}>
      <svg width={size} height={size} className="ring">
        <circle cx={size / 2} cy={size / 2} r={r} stroke={track} strokeWidth={stroke} />
        <circle cx={size / 2} cy={size / 2} r={r} stroke={color} strokeWidth={stroke}
                strokeDasharray={c} strokeDashoffset={off} style={{ transition: 'stroke-dashoffset 1s cubic-bezier(.3,1,.4,1)' }} />
      </svg>
      <div style={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center' }}>{children}</div>
    </div>
  );
}

/* ---------------- Drawer ------------------- */
function Drawer({ open, onClose, children, width }) {
  useEffect(() => {
    const h = (e) => e.key === 'Escape' && onClose();
    if (open) window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open]);
  if (!open) return null;
  return (
    <React.Fragment>
      <div className="scrim" onClick={onClose} />
      <div className="drawer" style={width ? { width } : null}>{children}</div>
    </React.Fragment>
  );
}

/* ---------------- Empty / placeholder module ---------------- */
function ModulePlaceholder({ icon, title, phase, desc, points }) {
  return (
    <div className="card" style={{ padding: '40px 36px', textAlign: 'center', maxWidth: 720, margin: '20px auto' }}>
      <div style={{ width: 60, height: 60, borderRadius: 16, background: 'var(--primary-50)', color: 'var(--primary-600)', display: 'grid', placeItems: 'center', margin: '0 auto 16px' }}>
        <Icon name={icon} size={28} />
      </div>
      <div className="row" style={{ justifyContent: 'center', gap: 8 }}>
        <span className="h-sec" style={{ fontSize: 18 }}>{title}</span>
        {phase && <Badge kind="neutral">Phase {phase.replace('P', '')}</Badge>}
      </div>
      <p className="muted" style={{ maxWidth: 460, margin: '8px auto 18px', fontSize: 13.5 }}>{desc}</p>
      {points && (
        <div style={{ display: 'inline-flex', flexDirection: 'column', gap: 8, textAlign: 'left' }}>
          {points.map((p, i) => (
            <div key={i} className="row" style={{ gap: 9, fontSize: 13 }}>
              <Icon name="check" size={15} style={{ color: 'var(--ok)' }} />{p}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ---------------- Toast system ---------------- */
let _toastListeners = [];
// toast(message, kind, action?) — action = { label, onClick } renders a button
function pushToast(msg, kind = 'ok', action) { _toastListeners.forEach((l) => l(msg, kind, action)); }
function ToastHost() {
  const [items, setItems] = useState([]);
  const dismiss = (id) => setItems((s) => s.filter((i) => i.id !== id));
  useEffect(() => {
    const l = (msg, kind, action) => {
      const id = Math.random().toString(36).slice(2);
      setItems((s) => [...s, { id, msg, kind, action }]);
      // linger longer when there's an action to click, and for warnings that need reading
      const ttl = action ? 8000 : (kind === 'bad' || kind === 'warn') ? 5000 : 3000;
      setTimeout(() => setItems((s) => s.filter((i) => i.id !== id)), ttl);
    };
    _toastListeners.push(l);
    return () => { _toastListeners = _toastListeners.filter((x) => x !== l); };
  }, []);
  const map = { ok: ['check', 'var(--ok)'], info: ['bell', 'var(--primary-500)'], warn: ['alert', 'var(--warn)'], bad: ['alert', 'var(--bad)'], ai: ['sparkles', 'var(--ai)'] };
  return (
    <div className="toast-host">
      {items.map((it) => {
        const [ico, col] = map[it.kind] || map.ok;
        return (
          <div key={it.id} className="toast">
            <span className="t-ico" style={{ background: col }}><Icon name={ico} size={14} style={{ color: '#fff' }} /></span>
            <span className="t-msg">{it.msg}</span>
            {it.action && (
              <button className="t-action" onClick={() => { dismiss(it.id); try { it.action.onClick(); } catch (e) {} }}>{it.action.label}</button>
            )}
            <button className="t-close" onClick={() => dismiss(it.id)} aria-label="Dismiss"><Icon name="x" size={13} /></button>
          </div>
        );
      })}
    </div>
  );
}

/* ---------------- Generic create/form modal ---------------- */
function FormModal({ title, sub, icon = 'plus', fields = [], submitLabel = 'Create', onClose, onSubmit, ai, live, derive, aside, wide }) {
  const init = {};
  fields.forEach((f) => { if (f.key) init[f.key] = f.value != null ? f.value : (f.type === 'checks' ? [] : (f.type === 'date' ? new Date().toISOString().slice(0, 10) : '')); });
  const [val, setVal] = useState(init);
  const set = (k, v) => setVal((s) => { let n = { ...s, [k]: v }; if (derive) { try { n = { ...n, ...(derive(n, k) || {}) }; } catch (e) {} } return n; });
  const toggle = (k, o) => setVal((s) => { const arr = s[k] || []; let n = { ...s, [k]: arr.includes(o) ? arr.filter((x) => x !== o) : [...arr, o] }; if (derive) { try { n = { ...n, ...(derive(n, k) || {}) }; } catch (e) {} } return n; });
  const submit = () => { if (onSubmit) onSubmit(val); };
  const cell = (f, i) => {
    const full = f.full || f.type === 'section' || f.type === 'textarea' || f.type === 'checks' || f.type === 'ai' || f.type === 'approvers';
    if (f.type === 'section') return <div key={i} style={{ gridColumn: '1 / -1', marginTop: i ? 6 : 0 }}><div className="eyebrow" style={{ fontSize: 9.5, marginBottom: 2 }}>{f.label}</div><div style={{ height: 1, background: 'var(--border)' }} />{f.hint && <div className="faint" style={{ fontSize: 11, marginTop: 4 }}>{f.hint}</div>}</div>;
    if (f.type === 'ai') return <div key={i} style={{ gridColumn: '1 / -1' }}><AINote title={f.label || 'Sentinel'} clause={f.clause}>{f.body}</AINote></div>;
    if (f.type === 'approvers') return (
      <div key={i} className="field" style={{ gridColumn: '1 / -1' }}>
        <label>{f.label}</label>
        <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>{(f.options || []).map(([role, name], ri) => (
          <div key={ri} className="row" style={{ gap: 10, padding: '9px 12px', borderBottom: ri < f.options.length - 1 ? '1px solid var(--border)' : 'none', alignItems: 'center' }}>
            <span style={{ width: 26, height: 26, 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.5, fontWeight: 650 }}>{role}</div><div className="faint" style={{ fontSize: 11 }}>{name}</div></div>
            <span style={{ flex: 'none', borderBottom: '1.5px dashed var(--border-2)', minWidth: 80, height: 18, display: 'inline-block' }} />
            <span className="badge" style={{ background: 'var(--warn-bg)', color: '#9a6300', flex: 'none' }}>Awaiting</span>
          </div>
        ))}</div>
      </div>
    );
    if (f.type === 'sign') return (
      <div key={i} className="field" style={f.full ? { gridColumn: '1 / -1' } : null}>
        <label>{f.label}</label>
        <button type="button" onClick={() => set(f.key, val[f.key] ? '' : (f.signer || 'Signed'))}
          style={{ fontFamily: 'inherit', cursor: 'pointer', textAlign: 'left', width: '100%', height: 56, borderRadius: 10, padding: '6px 12px', background: val[f.key] ? 'var(--ok-bg)' : 'var(--surface-2)', border: '1.5px dashed ' + (val[f.key] ? 'var(--ok-line)' : 'var(--border-2)') }}>
          {val[f.key]
            ? <span><span style={{ fontFamily: 'Segoe Script, "Snell Roundhand", cursive', fontSize: 20, color: 'var(--primary-700)' }}>{f.signer || 'Signed'}</span><span className="row" style={{ gap: 5, fontSize: 10.5, color: '#0f7a4f', marginTop: 2 }}><Icon name="check" size={12} />Electronically signed</span></span>
            : <span className="row" style={{ gap: 7, color: 'var(--text-3)', fontSize: 12.5, height: '100%', alignItems: 'center' }}><Icon name="signature" size={16} />Tap to sign electronically</span>}
        </button>
      </div>
    );
    return (
      <div key={i} className="field" style={full ? { gridColumn: '1 / -1' } : null}>
        {f.type !== 'readonly' && <label>{f.label}</label>}
        {f.type === 'select' ? (
          <select value={val[f.key] ?? ''} onChange={(e) => set(f.key, e.target.value)}>{!f.value && <option value="">Select…</option>}{(f.options || []).map((o) => <option key={o} value={o}>{o}</option>)}</select>
        ) : f.type === 'textarea' ? (
          <textarea rows="3" placeholder={f.placeholder} value={val[f.key] ?? ''} onChange={(e) => set(f.key, e.target.value)} />
        ) : f.type === 'radio' ? (
          <div className="row" style={{ gap: 6, flexWrap: 'wrap' }}>{(f.options || []).map((o) => <button key={o} type="button" onClick={() => set(f.key, o)} style={{ fontFamily: 'inherit', fontSize: 12, fontWeight: 600, padding: '6px 12px', borderRadius: 8, cursor: 'pointer', border: '1px solid ' + (val[f.key] === o ? 'var(--primary-600)' : 'var(--border-2)'), background: val[f.key] === o ? 'var(--primary-50)' : 'var(--surface)', color: val[f.key] === o ? 'var(--primary-700)' : 'var(--text-2)' }}>{o}</button>)}</div>
        ) : f.type === 'checks' ? (
          <div className="row" style={{ gap: 8, flexWrap: 'wrap' }}>{(f.options || []).map((o) => { const on = (val[f.key] || []).includes(o); return <button key={o} type="button" onClick={() => toggle(f.key, o)} style={{ fontFamily: 'inherit', fontSize: 12, fontWeight: 600, padding: '6px 11px', borderRadius: 8, cursor: 'pointer', border: '1px solid ' + (on ? 'var(--ok)' : 'var(--border-2)'), background: on ? 'var(--ok-bg)' : 'var(--surface)', color: on ? '#0f7a4f' : 'var(--text-2)' }}>{on ? '✓ ' : ''}{o}</button>; })}</div>
        ) : f.type === 'readonly' ? (
          <div><div className="faint" style={{ fontSize: 10.5 }}>{f.label}</div><div style={{ fontSize: 13, fontWeight: 650 }}>{f.value}</div></div>
        ) : (
          <input type={f.type === 'datetime' ? 'datetime-local' : (f.type || 'text')} placeholder={f.placeholder} value={val[f.key] ?? ''} onChange={(e) => set(f.key, e.target.value)} />
        )}
      </div>
    );
  };
  const insight = live ? (() => { try { return live(val); } catch (e) { return null; } })() : null;
  if (aside && live) {
    // Two-column layout: form fields on the left, live Sentinel panel docked on the right side
    return (
      <React.Fragment>
        <div className="scrim" onClick={onClose} />
        <div className="modal" style={{ width: 960, maxWidth: '96vw' }}>
          <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} size={18} /></span>
            <div style={{ flex: 1 }}><div style={{ fontWeight: 750, fontSize: 16 }}>{title}</div>{sub && <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: 'flex', alignItems: 'stretch', maxHeight: '66vh' }}>
            <div style={{ flex: 1, minWidth: 0, padding: 22, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, overflowY: 'auto' }}>
              {fields.map((f, i) => cell(f, i))}
            </div>
            <div style={{ width: 320, flex: 'none', borderLeft: '1px solid var(--border)', background: 'linear-gradient(180deg, var(--ai-bg), var(--surface) 240px)', padding: 16, overflowY: 'auto' }}>
              <SentinelPanel insight={insight} onApply={set} />
            </div>
          </div>
          <div className="row" style={{ gap: 10, padding: '14px 22px 22px', borderTop: '1px solid var(--border)' }}>
            <Btn variant="ghost" onClick={onClose} style={{ flex: 1 }}>Cancel</Btn>
            <Btn variant="primary" icon="check" onClick={submit} style={{ flex: 2 }}>{submitLabel}</Btn>
          </div>
        </div>
      </React.Fragment>
    );
  }
  return (
    <React.Fragment>
      <div className="scrim" onClick={onClose} />
      <div className="modal" style={wide ? { width: 660 } : null}>
        <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} size={18} /></span>
          <div style={{ flex: 1 }}><div style={{ fontWeight: 750, fontSize: 16 }}>{title}</div>{sub && <div className="faint" style={{ fontSize: 12 }}>{sub}</div>}</div>
          <button className="icon-btn" onClick={onClose}><Icon name="x" size={17} /></button>
        </div>
        <div style={{ padding: 22, display: 'grid', gridTemplateColumns: wide ? '1fr 1fr' : '1fr', gap: 14, maxHeight: '62vh', overflowY: 'auto' }}>
          {live
            ? <div style={{ gridColumn: '1 / -1' }}><SentinelPanel insight={insight} onApply={set} /></div>
            : ai && <div style={{ gridColumn: '1 / -1' }}><AINote title={ai.title || 'Sentinel assist'} clause={ai.clause}>{ai.body}</AINote></div>}
          {fields.map((f, i) => cell(f, i))}
        </div>
        <div className="row" style={{ gap: 10, padding: '0 22px 22px' }}>
          <Btn variant="ghost" onClick={onClose} style={{ flex: 1 }}>Cancel</Btn>
          <Btn variant="primary" icon="check" onClick={submit} style={{ flex: 2 }}>{submitLabel}</Btn>
        </div>
      </div>
    </React.Fragment>
  );
}

/* ---------------- Filter popover ---------------- */
function FilterMenu({ options, onPick, onClose, label = 'Filter by' }) {
  const ref = useRef(null);
  useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    setTimeout(() => document.addEventListener('mousedown', h), 0);
    return () => document.removeEventListener('mousedown', h);
  }, []);
  return (
    <div className="popover" style={{ right: 0, top: 'calc(100% + 6px)' }} ref={ref}>
      <div className="eyebrow" style={{ fontSize: 9.5, padding: '4px 10px 6px' }}>{label}</div>
      {options.map((o) => (
        <div key={o} className="pop-item" onClick={() => { onPick(o); onClose(); }}>
          <Icon name="filter" size={13} style={{ color: 'var(--text-3)' }} />{o}
        </div>
      ))}
    </div>
  );
}

/* ---------------- Global create-form host ---------------- */
let _formListener = null;
function openForm(cfg) { if (_formListener) _formListener(cfg); }
function FormHost() {
  const [cfg, setCfg] = useState(null);
  useEffect(() => { _formListener = (c) => setCfg(c); return () => { _formListener = null; }; }, []);
  if (!cfg) return null;
  return <FormModal {...cfg} wide={cfg.wide !== false} onClose={() => setCfg(null)} onSubmit={(values) => { try { cfg.onSave && cfg.onSave(values); } catch (e) { console.error(e); } setCfg(null); pushToast(cfg.done || 'Saved', 'ok'); }} />;
}

/* ---------------- Empty state (first-run CTA) ---------------- */
function EmptyState({ icon = 'grid', title, desc, actionLabel, actionIcon = 'plus', onAction, secondaryLabel, onSecondary }) {
  return (
    <div className="empty">
      <span className="empty-ico"><Icon name={icon} size={26} /></span>
      <h3>{title}</h3>
      {desc && <p>{desc}</p>}
      <div className="row" style={{ gap: 10, justifyContent: 'center' }}>
        {actionLabel && <Btn variant="primary" icon={actionIcon} onClick={onAction}>{actionLabel}</Btn>}
        {secondaryLabel && <Btn variant="ghost" onClick={onSecondary}>{secondaryLabel}</Btn>}
      </div>
    </div>
  );
}

Object.assign(window, {
  Icon, Badge, StatusBadge, Avatar, CustChip, Btn, Kpi, AINote, Gate,
  SectionHead, Bar, Ring, Drawer, ModulePlaceholder, EmptyState, ICONS,
  ToastHost, toast: pushToast, FormModal, FilterMenu, FormHost, openForm,
});
