/* Madison Renewal Engine — shared component library.
   Status vocabulary, condition cards, evidence, artifacts, timeline, gates,
   source health, automation state, shadow treatment, tables, states. */
const { useState, useEffect, useRef, useMemo, useContext, createContext, Fragment } = React;
const MCtx = createContext({});
const useApp = () => useContext(MCtx);

/* ---------- icons ---------- */
const IP = {
  'chevron-right': <path d="m9 18 6-6-6-6" />, 'chevron-down': <path d="m6 9 6 6 6-6" />, 'chevron-left': <path d="m15 18-6-6 6-6" />,
  'chevron-up': <path d="m18 15-6-6-6 6" />, check: <path d="M20 6 9 17l-5-5" />, x: <><path d="M18 6 6 18" /><path d="m6 6 12 12" /></>,
  'arrow-right': <><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></>, 'arrow-up': <><path d="M12 19V5" /><path d="m5 12 7-7 7 7" /></>,
  alert: <><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></>,
  info: <><circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" /></>, clock: <><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></>,
  mail: <><rect width="20" height="16" x="2" y="4" rx="2" /><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></>,
  send: <><path d="M14.5 21.7a.5.5 0 0 0 .94-.02l6.5-19a.5.5 0 0 0-.64-.64l-19 6.5a.5.5 0 0 0-.02.94l7.93 3.18a2 2 0 0 1 1.11 1.11z" /><path d="m21.85 2.15-10.94 10.94" /></>,
  user: <><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></>,
  users: <><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" /></>,
  building: <><rect width="16" height="20" x="4" y="2" rx="2" /><path d="M9 22v-4h6v4" /><path d="M8 6h.01M16 6h.01M8 10h.01M16 10h.01M8 14h.01M16 14h.01" /></>,
  file: <><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z" /><path d="M14 2v5h6" /></>,
  layers: <><path d="M12.8 2.2a2 2 0 0 0-1.6 0L2.6 6.1a1 1 0 0 0 0 1.8l8.6 3.9a2 2 0 0 0 1.6 0l8.6-3.9a1 1 0 0 0 0-1.8z" /><path d="m22 17.6-9.2 4.2a2 2 0 0 1-1.6 0L2 17.6" /><path d="m22 12.6-9.2 4.2a2 2 0 0 1-1.6 0L2 12.6" /></>,
  database: <><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5V19A9 3 0 0 0 21 19V5" /><path d="M3 12A9 3 0 0 0 21 12" /></>,
  search: <><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></>,
  filter: <path d="M22 3H2l8 9.46V19l4 2v-8.54z" />, list: <path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" />,
  bell: <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" /><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" /></>,
  shield: <><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="m9 12 2 2 4-4" /></>,
  lock: <><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></>,
  gauge: <><path d="m12 14 4-4" /><path d="M3.34 19a10 10 0 1 1 17.32 0" /></>,
  scale: <><path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1M2 16l3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1M7 21h10M12 3v18M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2" /></>,
  receipt: <><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1z" /><path d="M8 7h8M8 11h8M8 15h5" /></>,
  gate: <><path d="M3 21V8l9-5 9 5v13" /><path d="M9 21v-6h6v6" /></>,
  play: <polygon points="6 3 20 12 6 21 6 3" />, pause: <><rect x="6" y="4" width="4" height="16" rx="1" /><rect x="14" y="4" width="4" height="16" rx="1" /></>,
  refresh: <><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" /><path d="M21 3v5h-5" /><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" /><path d="M8 16H3v5" /></>,
  edit: <><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4z" /></>,
  ban: <><circle cx="12" cy="12" r="10" /><path d="m4.9 4.9 14.2 14.2" /></>,
  split: <><path d="M16 3h5v5M8 3H3v5M21 3l-7.5 7.5M3 3l7.5 7.5M16 21h5v-5M8 21H3v-5M21 21l-7.5-7.5M3 21l7.5-7.5" /></>,
  link: <><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></>,
  eye: <><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7" /><circle cx="12" cy="12" r="3" /></>,
  plus: <path d="M12 5v14M5 12h14" />, minus: <path d="M5 12h14" />, dot: <circle cx="12" cy="12" r="4" />,
  inbox: <><polyline points="22 12 16 12 14 15 10 15 8 12 2 12" /><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /></>,
  activity: <path d="M22 12h-4l-3 9L9 3l-3 9H2" />,
  sliders: <><path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6" /></>,
  'bar-chart': <><path d="M3 3v18h18" /><path d="M18 17V9M13 17V5M8 17v-3" /></>,
};
function Icon({ n, s = 15, w = 1.75, style, cls }) {
  return <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={w} strokeLinecap="round" strokeLinejoin="round" className={cls} style={{ flex: '0 0 auto', display: 'block', ...style }}>{IP[n] || IP.dot}</svg>;
}

/* ---------- formatting ---------- */
const MON = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const pad = (n) => (n < 10 ? '0' + n : '' + n);
function dt(at, withTime) {
  if (!at) return '—';
  const d = new Date(at);
  return MON[d.getMonth()] + ' ' + d.getDate() + (withTime === false ? '' : '  ' + pad(d.getHours()) + ':' + pad(d.getMinutes()));
}
function rel(at) {
  if (!at) return '—';
  const m = (window.MD.TODAY - new Date(at)) / 6e4;
  if (m < 2) return 'just now';
  if (m < 60) return Math.round(m) + 'm ago';
  if (m < 1440) return Math.round(m / 60) + 'h ago';
  return Math.round(m / 1440) + 'd ago';
}
const money = (n) => (n === null || n === undefined ? '—' : '$' + n.toLocaleString('en-US'));
const pct = (n) => Math.round(n * 100) + '%';
const dayStr = (n) => (n === null || n === undefined ? '—' : n + 'd');

/* ---------- primitives ---------- */
function Pill({ tone = 'slate', children, sq, ico, title }) {
  return <span className={'pill t-' + tone + (sq ? ' sq' : '')} title={title}>{ico ? <Icon n={ico} s={10} w={2.2} /> : null}{children}</span>;
}
const STAGE_TONE = { 'Renewal Created': '#A3ABBC', 'RAQ Sent': '#A3ABBC', 'RAQ In Progress': '#6E7481', 'Submission Prep': '#6E7481', 'Sent to Underwriting': '#2267EA', 'With Underwriting': '#2267EA', 'Sent to Actuarial': '#E79800', 'Pricing With Client': '#9355D1', 'Selection Received': '#09BD87', 'COI Sent': '#09BD87', Invoiced: '#0B8A64', Renewed: '#0D1318' };
function Stage({ v, since }) {
  return <span className="stage" title={since ? 'Since ' + dt(since, false) : ''}><i className="sbar" style={{ background: STAGE_TONE[v] || '#A3ABBC', width: 3, height: 11, borderRadius: 2, display: 'block' }} />{v}</span>;
}
function Bcode({ cat, resolved }) { return <span className={'bcode' + (resolved ? ' res' : '')}>{cat}</span>; }
const OWE_ICON = { Client: 'user', Advisor: 'users', AmRisk: 'building', Underwriting: 'shield', Accounting: 'receipt', 'Madison judgment': 'scale' };
function Owe({ v }) { return <span className="owe">{v ? <Icon n={OWE_ICON[v] || 'user'} s={12} cls="oi" /> : null}{v || '—'}</span>; }
function Dp({ k, days, compact }) {
  const d = days === undefined ? window.MD.daysTo(window.MD.dl(k)) : days;
  const tone = d < 0 ? 'crit' : d <= 14 ? 'crit' : d <= 30 ? 'tight' : 'ok';
  const fill = d < 0 ? 3 : d <= 14 ? 3 : d <= 30 ? 2 : 1;
  return <span className={'dp ' + tone} title={k ? k[0].toUpperCase() + k.slice(1) + ' deadline ' + window.MD.dl(k) : ''}>
    <span className="bars">{[0, 1, 2].map((i) => <i key={i} className={i < fill ? 'f' : ''} />)}</span>
    {d < 0 ? Math.abs(d) + 'd over' : d + 'd'}{compact ? '' : k ? <span className="dim" style={{ fontWeight: 400 }}>{' ' + k.slice(0, 3)}</span> : null}
  </span>;
}
function Src({ v, children }) { return <span className={'src ' + v}><i className="sd" />{children || v}</span>; }
function Age({ v, warm = 10, hot = 18 }) { return <span className={'age ' + (v >= hot ? 'hot' : v >= warm ? 'warm' : '')}>{v}d</span>; }
function Bar({ parts }) { return <span className="bar">{parts.map((p, i) => <i key={i} style={{ width: p.w, background: p.c }} />)}</span>; }

/* ---------- engine state / disposition ---------- */
const DISP_TONE = { 'Draft ready': 'blue', 'Draft ready · source stale': 'amber', 'Internal ageing': 'slate', Escalated: 'red', 'Routing recommended': 'red', 'On hold': 'amber', Suppressed: 'slate', 'Hold — conflicting evidence': 'amber', 'Hold — advisor currency': 'amber', 'Await external response': 'slate', 'No action required': 'slate', Closed: 'slate' };
function EngineState({ v }) {
  const t = DISP_TONE[v] || (v && v.indexOf('Close Chain') === 0 ? 'purple' : 'slate');
  return <Pill tone={t} sq>{v}</Pill>;
}
function Priority({ v }) { return <Pill tone={v === 'Critical' ? 'red' : v === 'High' ? 'amber' : 'slate'} sq>{v}</Pill>; }

/* ---------- condition card ---------- */
function CondCard({ c, r, onAct, dense }) {
  const app = useApp();
  const done = c.status === 'resolved';
  const held = c.status === 'held';
  const tone = done ? 'done' : c.internal ? 'int' : c.pressure === 'crit' || c.pressure === 'overdue' ? 'crit' : 'tight';
  const unverified = app.staleEmail && !done;
  return <div className={'cond ' + tone}>
    <div className="cond-h">
      <Bcode cat={c.cat} resolved={done} />
      <span className="ct">{c.item}</span>
      {done ? <Pill tone="green" ico="check">Resolved</Pill> : held ? <Pill tone="amber" ico="pause">Held</Pill> : null}
      {c.internal && !done ? <Pill tone="slate">Internal</Pill> : null}
      {c.conf === 'conflicting' ? <Pill tone="amber" ico="alert">Sources disagree</Pill> : null}
      {unverified && c.conf === 'stale-source' ? <Pill tone="amber" ico="alert">Response unverified</Pill> : null}
      <span className="grow" />
      <span className="sm mut">{c.label}</span>
    </div>
    {!dense && <div className="cond-m">
      <div><span className="l">Owing</span><span className="v"><Owe v={c.owing} /></span></div>
      <div><span className="l">Open</span><span className="v"><Age v={c.age} /> <span className="dim xs">since {dt(window.MD.ago(c.age), false)}</span></span></div>
      <div><span className="l">Last attempt</span><span className="v">{c.lastAttempt === null ? '—' : c.lastAttempt === 0 ? 'today' : c.lastAttempt + 'd ago'} <span className="dim xs">{c.attempts} total</span></span></div>
      <div><span className="l">Deadline</span><span className="v"><Dp k={c.dl} days={c.daysTo} /></span></div>
      <div><span className="l">Status</span><span className="v">{done ? 'Resolved ' + dt(c.resolved.at, false) : held ? 'Held' : 'Open'}</span></div>
    </div>}
    {done ? <div className="cond-f"><Icon n="check" s={12} /> <span>Resolved by {c.resolved.by} — {c.resolved.how}</span></div>
      : <div className="cond-f">
        {c.override ? <><Icon n="pause" s={12} /><span><b>Held by {c.override.by}</b> · {c.override.reason}</span></> : <><Icon n="arrow-right" s={12} /><span><span className="mut">Next</span> {c.next}</span></>}
        <span className="grow" />
        <button className="lnk" onClick={() => app.openEvidence({ title: c.cat + ' · ' + c.item, sub: r ? r.account : '', items: c.evidence, cond: c })}>Why? · {c.evidence.length} sources</button>
      </div>}
  </div>;
}

/* ---------- recommended action ---------- */
function RecModule({ r, onOpenDraft, compact }) {
  const app = useApp();
  const rec = r.rec; if (!rec) return null;
  const draft = window.MD.drafts.find((d) => d.renewal === r.id && d.state !== 'sent' && d.state !== 'discarded');
  const ICO = { 'Draft outreach': 'edit', Hold: 'pause', Consolidate: 'layers', 'Internal escalation': 'arrow-up', 'Recommend routing': 'split', Suppress: 'ban', 'Await external response': 'clock', 'Review exception': 'alert', 'No action required': 'check' };
  return <div className="rec">
    <div className="rec-h">
      <Icon n={ICO[rec.action] || 'edit'} s={16} style={{ color: 'var(--purple-deep)' }} />
      <span className="rt">{rec.action}</span>
      {rec.consolidate ? <Pill tone="purple" ico="layers">Consolidation recommended</Pill> : null}
      {rec.exception ? <Pill tone="red" ico="alert">Exception {rec.exception}</Pill> : null}
      <span className="grow" />
      <Src v="engine">Engine · {rel(r.lastEval)}</Src>
    </div>
    <div className="rec-b" style={compact ? { gridTemplateColumns: '1fr' } : null}>
      <div>
        <div className="eyebrow" style={{ marginBottom: 6 }}>Why now</div>
        <ul className="rec-why">{rec.why.map((w, i) => <li key={i}><i className="b" />{w}</li>)}</ul>
      </div>
      <div className="rec-side">
        <div><div className="eyebrow">Target</div><div className="sm" style={{ color: 'var(--text)' }}>{rec.target}</div></div>
        <div><div className="eyebrow">Urgency</div><div className="sm" style={{ color: 'var(--text)' }}>{rec.urgency}</div></div>
        <div><div className="eyebrow">Control</div><div className="sm"><Pill tone={rec.approval === 'None' || rec.approval === 'No action required' ? 'slate' : rec.approval.indexOf('Automatic') === 0 ? 'slate' : 'blue'} ico={rec.approval.indexOf('Automatic') === 0 ? 'refresh' : 'user'}>{rec.approval}</Pill></div></div>
      </div>
    </div>
    <div className="rec-f">
      <span className="why-not"><b>If nothing happens:</b> {rec.ifNothing}</span>
      {draft && draft.state !== 'suppressed' ? <button className="btn p" onClick={() => onOpenDraft ? onOpenDraft(draft.id) : app.nav('#/chase/' + draft.id)}><Icon n="edit" s={13} />Open draft</button> : null}
      {draft && draft.state === 'suppressed' ? <Pill tone="slate" ico="ban">Draft suppressed</Pill> : null}
      {rec.exception ? <button className="btn" onClick={() => app.nav('#/exceptions/' + rec.exception)}><Icon n="alert" s={13} />Open exception</button> : null}
    </div>
  </div>;
}

/* ---------- evidence ---------- */
const EV_ICON = { salesforce: 'database', email: 'mail', engine: 'activity', amrisk: 'building', accounting: 'receipt', user: 'user' };
function EvidenceList({ items }) {
  if (!items || !items.length) return <div className="sm mut">No evidence recorded.</div>;
  return <div>{items.map((e) => <div className="ev" key={e.id}>
    <span className="eb"><Icon n={EV_ICON[e.src] || 'file'} s={12} /></span>
    <div className="grow">
      <div className="row" style={{ gap: 6 }}><b>{e.label}</b><span className={'evtag ' + e.type}>{e.type}</span></div>
      <p>{e.detail}</p>
      <div className="row" style={{ gap: 7, marginTop: 3 }}><Src v={e.src} /><span className="dim xs">{dt(e.at)}</span></div>
    </div>
  </div>)}</div>;
}
function EvidenceDrawer() {
  const app = useApp();
  const d = app.drawer;
  if (!d) return null;
  return <div className="drawer-wrap" onClick={app.closeEvidence}>
    <div className="drawer" onClick={(e) => e.stopPropagation()}>
      <div className="drawer-h">
        <Icon n="eye" s={15} style={{ color: 'var(--text-muted)' }} />
        <div className="grow"><div className="f-b" style={{ fontSize: 12.5 }}>{d.title}</div><div className="xs mut">{d.sub}</div></div>
        <button className="btn xs" onClick={app.closeEvidence}><Icon n="x" s={12} /></button>
      </div>
      <div className="drawer-b">
        {d.cond ? <div className="banner blue" style={{ marginBottom: 12 }}>
          <Icon n="info" s={14} /><span>Condition held open because no artifact satisfying <b>{d.cond.item}</b> has been observed. Source completeness: <b>{d.cond.conf}</b>.</span>
        </div> : null}
        {d.note ? <div className="banner" style={{ marginBottom: 12 }}><Icon n="info" s={14} /><span>{d.note}</span></div> : null}
        <div className="eyebrow" style={{ marginBottom: 4 }}>Evidence · {(d.items || []).length}</div>
        <EvidenceList items={d.items} />
        {d.children}
      </div>
    </div>
  </div>;
}

/* ---------- timeline ---------- */
const SRC_LABEL = { salesforce: 'Salesforce', email: 'Email', engine: 'Engine', user: 'Madison user', amrisk: 'AmRisk', accounting: 'Accounting' };
function Timeline({ events, filter, onFilter }) {
  const srcs = ['salesforce', 'email', 'engine', 'user', 'amrisk', 'accounting'];
  const list = (events || []).filter((e) => !filter || filter === 'all' || e.src === filter).slice().sort((a, b) => new Date(b.at) - new Date(a.at));
  return <div>
    {onFilter ? <div className="row" style={{ gap: 4, padding: '0 0 9px', flexWrap: 'wrap' }}>
      <button className={'btn xs' + (!filter || filter === 'all' ? ' p' : '')} onClick={() => onFilter('all')}>All</button>
      {srcs.map((s) => <button key={s} className={'btn xs' + (filter === s ? ' p' : '')} onClick={() => onFilter(s)}>{SRC_LABEL[s]}</button>)}
    </div> : null}
    <div className="tl">{list.map((e, i) => <div className={'tl-ev ' + e.src} key={i}>
      <div className="tl-t">{dt(e.at)}</div>
      <div className="tl-r"><span className="tl-d" /></div>
      <div className="tl-c"><div className="row" style={{ gap: 7 }}><b>{e.title}</b><Src v={e.src} /></div>{e.detail ? <p>{e.detail}</p> : null}</div>
    </div>)}</div>
    {!list.length ? <div className="empty"><div className="ei"><Icon n="activity" s={16} /></div>No events from this source.</div> : null}
  </div>;
}

/* ---------- artifacts ---------- */
const FILING = { filed: ['green', 'Filed'], 'awaiting-match': ['amber', 'Awaiting match'], failed: ['red', 'Filing failed'], conflict: ['amber', 'Superseded'], unsupported: ['red', 'Unsupported'] };
function Artifact({ a, onOpen }) {
  const f = FILING[a.filing] || ['slate', a.filing];
  return <div className={'art' + (a.authoritative ? ' auth' : '')}>
    <span className="ai"><Icon n="file" s={13} /></span>
    <div className="grow" style={{ minWidth: 0 }}>
      <div className="row" style={{ gap: 6 }}><b className="trunc">{a.name}</b>{a.authoritative ? <Pill tone="amber" ico="lock">Authoritative</Pill> : null}</div>
      <div className="row xs mut" style={{ gap: 8 }}><span>{a.type}</span><span>·</span><span>{a.source}</span><span>·</span><span>{dt(a.at, false)}</span>{a.version ? <><span>·</span><span>{a.version}</span></> : null}{a.confidence ? <><span>·</span><span>match {a.confidence}</span></> : null}</div>
    </div>
    <Pill tone={f[0]} sq>{f[1]}</Pill>
    {onOpen ? <button className="btn xs" onClick={() => onOpen(a)}>Inspect</button> : null}
  </div>;
}

/* ---------- close chain ---------- */
const CLOSE_STATES = ['SELECTION_RECEIVED', 'LOCK_PENDING', 'LOCK_RECEIVED', 'SELECTION_VERIFYING', 'SELECTION_VERIFIED', 'GRID_STAGED', 'GRID_APPROVED', 'BILLING_STATE_READY', 'PAYMENT_PENDING', 'RECEIPT_CONFIRMED', 'RELEASE_ELIGIBLE'];
const CLOSE_LABEL = { SELECTION_RECEIVED: 'Selection received', LOCK_PENDING: 'Lock pending', LOCK_RECEIVED: 'Lock received', SELECTION_VERIFYING: 'Verifying', SELECTION_VERIFIED: 'Verified', GRID_STAGED: 'Grid staged', GRID_APPROVED: 'Grid approved', BILLING_STATE_READY: 'Billing ready', PAYMENT_PENDING: 'Payment pending', RECEIPT_CONFIRMED: 'Receipt confirmed', RELEASE_ELIGIBLE: 'Release eligible', GRID_REJECTED: 'Grid rejected', EXCEPTION: 'Exception' };
function Rail({ state, bad, compact }) {
  const idx = CLOSE_STATES.indexOf(state);
  const show = compact ? CLOSE_STATES.filter((s, i) => i >= Math.max(0, idx - 1) && i <= idx + 1) : CLOSE_STATES;
  return <div className="rail">{show.map((s, i) => {
    const si = CLOSE_STATES.indexOf(s);
    const cls = si < idx ? 'done' : si === idx ? (bad ? 'bad now' : 'now') : '';
    return <span key={s} className="row" style={{ gap: 0 }}>{i ? <i className="sep" /> : null}<span className={'st ' + cls}><i className="rd" />{CLOSE_LABEL[s]}</span></span>;
  })}</div>;
}
function ShadowBar({ where }) {
  return <div className="shadowbar">
    <span className="tag">2026 SHADOW</span>
    <span className="msg">Stage and compare only — no production policy, coverage, invoice, or release writes.</span>
    <span className="grow" />
    <span className="msg" style={{ opacity: .7 }}>{where || 'Close Chain'} · cutover decision 2027</span>
  </div>;
}
const GATE_TONE = { approved: ['green', 'check', 'Approved'], exists: ['green', 'check', 'Exists'], valid: ['green', 'check', 'Valid'], confirmed: ['green', 'check', 'Confirmed'], clear: ['green', 'check', 'None'], pending: ['red', 'x', 'Pending'], blocking: ['red', 'alert', 'Blocking'], warn: ['amber', 'alert', 'Review'], 'expected-later': ['amber', 'clock', 'Expected later'], unverified: ['amber', 'info', 'Unverified'], missing: ['red', 'alert', 'Missing unexpectedly'] };
function GateRow({ label, v, note }) {
  const g = GATE_TONE[v] || ['slate', 'info', v];
  return <div className="gate">
    <Icon n={g[1]} s={14} style={{ color: g[0] === 'green' ? 'var(--green-deep)' : g[0] === 'red' ? 'var(--red-deep)' : 'var(--amber-deep)' }} />
    <span className="gn">{label}</span>
    {note ? <span className="sm mut">{note}</span> : null}
    <Pill tone={g[0]} sq>{g[2]}</Pill>
  </div>;
}
function gateOpen(g) { return g && g.grid === 'approved' && (g.policy === 'exists') && g.billing === 'valid' && g.receipt === 'confirmed' && g.exceptions === 'clear'; }
function gateReason(g) {
  if (!g) return 'Close Chain has not started for this renewal.';
  const r = [];
  if (g.grid !== 'approved') r.push('coverage grid is not approved');
  if (g.policy !== 'exists') r.push('policy record is ' + (GATE_TONE[g.policy] ? GATE_TONE[g.policy][2].toLowerCase() : g.policy));
  if (g.billing !== 'valid') r.push('billing state is not valid');
  if (g.receipt !== 'confirmed') r.push('receipt has not been confirmed');
  if (g.exceptions !== 'clear') r.push('an unresolved exception is open');
  return r.length ? r[0][0].toUpperCase() + r[0].slice(1) + (r.length > 1 ? ' (+' + (r.length - 1) + ' more)' : '') + '.' : '';
}
const VROW = { exact: ['green', 'Exact match'], map: ['amber', 'Mapping required'], mismatch: ['red', 'Value mismatch'], 'miss-ws': ['red', 'Missing from worksheet'], 'miss-lk': ['red', 'Missing from lock'], uncertain: ['amber', 'Parse uncertain'], unsupported: ['red', 'Unsupported layout'], corrected: ['green', 'Corrected · matches lock'], conflict: ['red', 'Correction conflicts with lock'] };
function VState({ v }) { const t = VROW[v] || ['slate', v]; return <Pill tone={t[0]} sq ico={t[0] === 'green' ? 'check' : 'alert'}>{t[1]}</Pill>; }

/* ---------- source health ---------- */
function healthState(app) {
  const h = window.MD.health;
  if (!app.sfWrites) return { tone: 'bad', label: 'Salesforce writes suspended' };
  if (app.staleEmail) return { tone: 'warn', label: 'Email observation stale' };
  if (h.email.groups.some((g) => g.state === 'stale')) return { tone: 'warn', label: '1 mailbox group stale' };
  return { tone: 'ok', label: 'Sources current' };
}
function SourceChip() {
  const app = useApp();
  const s = healthState(app);
  return <button className={'m-chip' + (s.tone === 'ok' ? '' : s.tone === 'warn' ? ' warn' : ' bad')} onClick={() => app.nav('#/system')}>
    <Icon n={s.tone === 'ok' ? 'check' : 'alert'} s={12} />{s.label}
  </button>;
}
function StaleNote({ what }) {
  return <div className="banner amber"><Icon n="alert" s={14} /><span><b>Response status currently unverified.</b> {what || 'Email observation for the Advisor Services mailbox group is stale (last observed 5h 40m ago).'} Absence of a reply is not treated as fact.</span></div>;
}

/* ---------- automation ---------- */
const AUTO_TONE = { 'Autopilot Enabled': ['green', 'play'], 'Eligible for Consideration': ['blue', 'info'], 'Review Required': ['slate', 'user'], 'Autopilot Suspended': ['amber', 'pause'] };
function AutoState({ v }) { const t = AUTO_TONE[v] || ['slate', 'info']; return <Pill tone={t[0]} sq ico={t[1]}>{v}</Pill>; }

/* ---------- exceptions ---------- */
function Sev({ v }) { return <Pill tone={v === 'high' ? 'red' : v === 'medium' ? 'amber' : 'slate'} sq>{v === 'high' ? 'High' : v === 'medium' ? 'Medium' : 'Low'}</Pill>; }

/* ---------- permissions ---------- */
function can(app, action) { return app.user.can.indexOf(action) >= 0; }
function Gated({ action, children, label }) {
  const app = useApp();
  if (can(app, action)) return children;
  return <span className="locked" title={'Restricted — requires ' + (label || action) + ' permission. You are signed in as ' + app.user.role + '.'}>
    <Icon n="lock" s={12} />{label || 'Restricted'}
  </span>;
}

/* ---------- tables & states ---------- */
function Th({ id, sort, setSort, children, right, w }) {
  const on = sort && sort.k === id;
  return <th onClick={() => setSort && setSort({ k: id, d: on && sort.d === 'asc' ? 'desc' : 'asc' })} style={{ textAlign: right ? 'right' : 'left', width: w }}>
    {children}{on ? <span className="sa"> {sort.d === 'asc' ? '↑' : '↓'}</span> : null}
  </th>;
}
function Empty({ ico, title, note, action }) {
  return <div className="empty"><div className="ei"><Icon n={ico || 'inbox'} s={16} /></div><div style={{ color: 'var(--text)', fontWeight: 500, marginBottom: 3 }}>{title}</div><div className="sm">{note}</div>{action}</div>;
}
function TableSkeleton({ rows = 8, cols = 7 }) {
  return <table className="m-tbl"><tbody>{Array.from({ length: rows }).map((_, i) => <tr key={i}>{Array.from({ length: cols }).map((__, j) => <td key={j}><span className="skel" style={{ display: 'block', width: j === 0 ? 150 : 60 + (j * 7) % 40 }} /></td>)}</tr>)}</tbody></table>;
}
function Toast() {
  const app = useApp();
  if (!app.toast) return null;
  return <div className="toast"><Icon n={app.toast.ico || 'check'} s={14} /><span className="grow">{app.toast.msg}</span>{app.toast.undo ? <span className="u" onClick={app.toast.undo}>Undo</span> : null}</div>;
}
function Menu({ children, onClose, style }) {
  const ref = useRef();
  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="menu" ref={ref} style={style}>{children}</div>;
}
function Sel({ v, set, opts, label, all }) {
  return <span className={'sel-w' + (v && v !== all ? ' on' : '')}>
    <select value={v} onChange={(e) => set(e.target.value)}>
      <option value={all}>{label}</option>
      {opts.map((o) => <option key={o} value={o}>{o}</option>)}
    </select>
    <span className="caret"><Icon n="chevron-down" s={11} /></span>
  </span>;
}
function Kpis({ items, active, onPick }) {
  return <div className="kpis">{items.map((k) => <button key={k.id} className={'kpi' + (active === k.id ? ' on' : '') + (k.tone ? ' ' + k.tone : '')} onClick={() => onPick(k.id)}>
    <span className="kl">{k.label}</span><span className="kn">{k.n}</span><span className="kd">{k.note}</span>
  </button>)}</div>;
}
function Views({ items, v, set }) {
  return <div className="views">{items.map((x) => <button key={x.id} className={'view' + (v === x.id ? ' on' : '')} onClick={() => set(x.id)}>{x.label}<span className="n">{x.n}</span></button>)}</div>;
}
function Tabs({ items, v, set }) {
  return <div className="tabs">{items.map((x) => <button key={x.id} className={v === x.id ? 'on' : ''} onClick={() => set(x.id)}>{x.label}{x.n !== undefined ? <span className="dim"> {x.n}</span> : ''}</button>)}</div>;
}
function Sub({ title, note, right, children }) {
  return <div className="m-sub"><div className="m-sub-h"><h1>{title}</h1>{note ? <span className="sub-note">{note}</span> : null}<span className="m-sub-r">{right}</span></div>{children}</div>;
}

Object.assign(window, {
  MCtx, useApp, Icon, dt, rel, money, pct, dayStr, Pill, Stage, Bcode, Owe, Dp, Src, Age, Bar, EngineState, Priority,
  CondCard, RecModule, EvidenceList, EvidenceDrawer, Timeline, Artifact, Rail, ShadowBar, GateRow, gateOpen, gateReason,
  VState, VROW, CLOSE_STATES, CLOSE_LABEL, SourceChip, StaleNote, healthState, AutoState, Sev, can, Gated, Th, Empty,
  TableSkeleton, Toast, Menu, Sel, Kpis, Views, Tabs, Sub, SRC_LABEL, Fragment,
});
