/* Madison Renewal Agent — shell, router, store, actions (agent-first direction) */
const { useState: vS, useEffect: vE } = React;
const nowIso = () => new Date(window.MD.TODAY).toISOString();

const V_NAV = [
  { g: 'Today' },
  { id: 'today', label: "Today's work", ico: 'inbox', live: true },
  { id: 'outreach', label: 'Prepared outreach', ico: 'mail', live: true },
  { g: 'The agent' },
  { id: 'activity', label: 'Agent activity', ico: 'activity', live: true },
  { id: 'autonomy', label: 'Agent autonomy', ico: 'sliders' },
  { id: 'sources', label: 'What I can see', ico: 'database' },
  { g: 'The book' },
  { id: 'book', label: 'Renewal book', ico: 'building' },
  { id: 'advisors', label: 'Advisor briefings', ico: 'users' },
  { id: 'close', label: 'Close worklist', ico: 'layers' },
];

function ActivityScreen() {
  const app = useApp(); const AG = window.AG;
  const { runEvents, human } = AG.activity();
  const c = AG.runCounts();
  return <div className="ag-canvas">
    <div className="ph"><div className="ph-row"><div className="grow">
      <h1>Agent activity</h1>
      <p className="sub">Everything the renewal agent did today, and everything a person decided. <b>{c.auto} actions completed autonomously</b>, all of it traceable to the renewal it touched.</p>
    </div><span className="ph-badge"><Icon n="check" s={14} />Run complete 06:14</span></div></div>

    <div className="secwrap">
      <div className="sec"><span className="st">Morning run</span><span className="snote">Tue 17 Nov · 06:09 to 06:28</span></div>
      <div className="feed-list">{runEvents.map((e, i) => <div className="feed-event" key={i}>
        <span className={'fe-icon ' + e.tone}><Icon n={e.tone === 'alert' ? 'alert' : e.tone === 'dismiss' ? 'ban' : e.tone === 'flag' ? 'layers' : e.tone === 'override' ? 'arrow-up' : e.tone === 'approve' ? 'check' : 'activity'} s={14} /></span>
        <div className="grow"><div className="fe-text">{e.text}</div><div className="fe-meta"><span className="fe-actor">Renewal agent</span><span>·</span><span>{e.at}</span></div></div>
      </div>)}</div>
    </div>

    <div className="secwrap">
      <div className="sec"><span className="st">Human decisions</span><span className="sn">{human.length}</span><span className="snote">What a person changed, approved, or overrode</span></div>
      {human.length ? <div className="feed-list">{human.map((e, i) => <div className="feed-event" key={i}>
        <span className={'fe-icon ' + (e.tone === 'approve' ? 'approve' : 'override')}><Icon n={e.tone === 'approve' ? 'send' : 'user'} s={14} /></span>
        <div className="grow"><div className="fe-text"><b>{e.text}</b></div><div className="fe-meta"><span>{e.meta}</span></div></div>
        <button className="see-btn" onClick={() => app.nav('#/renewal/' + e.renewal)}>Open</button>
      </div>)}</div> : <div className="empty"><div className="ei"><Icon n="user" s={17} /></div><b>No human decisions yet today</b>Approve, adjust, or hold something on the worklist and it appears here.</div>}
    </div>
  </div>;
}

function VApp() {
  const MD = window.MD; const AG = window.AG;
  const [route, setRoute] = vS(location.hash || '#/today');
  const [tick, bump] = vS(0);
  const [roleId, setRole] = vS('cr');
  const [toast, setToastRaw] = vS(null);
  const [menu, setMenu] = vS(false);
  vE(() => { const h = () => { setRoute(location.hash || '#/today'); const b = document.querySelector('.ag-body'); if (b) b.scrollTop = 0; }; window.addEventListener('hashchange', h); return () => window.removeEventListener('hashchange', h); }, []);
  const nav = (h) => { location.hash = h; setRoute(h); setMenu(false); };
  const user = MD.ROLES.find((r) => r.id === roleId);
  const refresh = () => bump((t) => t + 1);
  const setToast = (msg, o) => { setToastRaw(Object.assign({ msg }, o || {})); clearTimeout(window.__vt); window.__vt = setTimeout(() => setToastRaw(null), 6500); };
  const T = (r, src, title, detail) => r.timeline.unshift({ at: nowIso(), src, title, detail: detail || '' });
  const touch = (r) => { r.lastTouch = nowIso(); r.lastActivity = nowIso(); AG.openC(r).forEach((c) => { c.lastAttempt = 0; c.attempts += 1; }); };

  const act = {
    nav, setToast,
    sendDraft: (id, patch) => {
      const d = MD.drafts.find((x) => x.id === id); if (!d) return;
      if (patch) Object.assign(d, { to: patch.to, cc: patch.cc, subject: patch.subject, body: patch.body });
      const r = MD.byId[d.renewal];
      if (patch && patch.correction) { d.corrections.push({ cat: patch.correction, by: user.name, at: nowIso() }); T(r, 'user', 'Draft edited before send', user.name + ' · recorded as ' + patch.correction); }
      d.state = 'sent'; d.sentAt = nowIso();
      T(r, 'user', 'Approved and sent', user.name + ' · ' + d.category + ' · to ' + d.to);
      touch(r); r.engine = 'Await external response';
      r.rec = { action: 'Await external response', target: d.to, urgency: 'Monitor', why: ['Sent today to ' + d.to + '.', 'A five-day suppression window now applies to this party.'], approval: 'No action required', ifNothing: 'The condition is re-evaluated when a reply arrives or the window lapses.' };
      refresh(); setToast('Sent to ' + d.to, { sub: 'The agent will not compose another message to this party for five days.', ico: 'send' });
    },
    discardDraft: (id) => { const d = MD.drafts.find((x) => x.id === id); const r = MD.byId[d.renewal]; d.state = 'discarded'; T(r, 'user', 'Prepared message discarded', user.name + ' · the condition stays open'); refresh(); setToast('Discarded', { sub: 'The renewal keeps an explicit disposition — it does not disappear from the worklist.', ico: 'ban' }); },
    holdDraft: (id) => { const d = MD.drafts.find((x) => x.id === id); const r = MD.byId[d.renewal]; d.state = 'held'; d.heldReason = 'Held by ' + user.name + ' — an explicit decision, not silence.'; T(r, 'user', 'Message held', user.name); refresh(); setToast('Held', { sub: 'It stays visible under held on the outreach screen.', ico: 'pause' }); },
    sendBrief: (advId, ids) => {
      const a = MD.advisors.find((x) => x.id === advId);
      ids.forEach((rid) => {
        const r = MD.byId[rid]; if (!r) return;
        T(r, 'user', 'Advisor book brief sent', user.name + ' · one consolidated message to ' + a.name + ' covering ' + ids.length + ' renewals');
        touch(r); r.engine = 'Await external response';
        MD.drafts.filter((d) => d.renewal === rid && (d.state === 'ready' || d.state === 'held')).forEach((d) => { d.state = 'sent'; d.sentAt = nowIso(); });
      });
      a.lastTouch = nowIso(); refresh();
      setToast('Brief sent to ' + a.name, { sub: ids.length + ' renewals in one message · ' + ids.length + ' individual drafts closed.', ico: 'layers' });
    },
    humanAction: (r, kind, reason) => {
      if (kind === 'hold') { r.hold = true; r.engine = 'On hold'; if (r.conditions[0]) { r.conditions[0].status = 'held'; r.conditions[0].override = { by: user.name, at: nowIso(), reason }; } T(r, 'user', 'Renewal held', user.name + ' · ' + reason); }
      else if (kind === 'route') { r.dormant = true; r.engine = 'Routing recommended'; T(r, 'user', 'Routing recommended to operations', user.name + ' · ' + reason); }
      else T(r, 'user', 'Decision recorded', user.name + ' · ' + reason);
      refresh(); setToast('Recorded on ' + r.account, { sub: 'Human decisions stay in the renewal history and the Salesforce audit record.', ico: 'user' });
    },
    matchEmail: (exId, rid) => {
      const x = MD.exceptions.find((e) => e.id === exId); const r = MD.byId[rid];
      x.status = 'resolved'; x.resolutionNote = 'Matched to ' + r.account + ' by ' + user.name + '.';
      (x.email.attachments || []).forEach((n, i) => r.artifacts.unshift({ type: i ? 'Financial statement' : 'P&L', name: n, source: 'Email', at: nowIso(), version: 'v1', filing: 'filed', authoritative: false, confidence: 0.99 }));
      const c = r.conditions.find((k) => k.cat === 'B3' && k.status !== 'resolved');
      T(r, 'user', 'Correspondence matched', user.name + ' · ' + x.email.subject);
      T(r, 'engine', 'Documents observed and filed', (x.email.attachments || []).join(', '));
      if (c) { c.status = 'resolved'; c.resolved = { at: nowIso(), by: 'Engine', how: 'Document observed, matched, and filed after human classification' }; T(r, 'engine', 'Blocking condition resolved', c.cat + ' · ' + c.item); }
      refresh(); setToast('Matched to ' + r.account, { sub: c ? c.cat + ' resolved · the agent will not chase that document again.' : 'Filed with provenance.', ico: 'link' });
    },
    resolveException: (id, note) => { const x = MD.exceptions.find((e) => e.id === id); x.status = 'resolved'; x.resolutionNote = note; refresh(); setToast('Exception ' + id + ' resolved', { sub: note, ico: 'check' }); },
    resolveMapping: (rid, rowId, code) => {
      const r = MD.byId[rid]; const c = r.close; const row = c.rows.find((x) => x.id === rowId);
      row.code = code; row.st = row.ws === row.lk ? 'exact' : 'mismatch'; row.note = null;
      const m = MD.mappings.find((x) => x.status === 'Unmapped');
      if (m) { m.code = code; m.status = 'Active'; m.ver = 'v6'; m.reviewed = nowIso(); m.aliases = 'Equipment breakdown — contingent'; m.exception = null; }
      const x3 = MD.exceptions.find((e) => e.id === 'X3'); if (x3) { x3.status = 'resolved'; x3.resolutionNote = 'Mapped to ' + code + ' by ' + user.name + '.'; }
      T(r, 'user', 'Coverage mapping resolved', user.name + ' · ' + row.coverage + ' mapped to ' + code + ', preserved as a registry alias');
      act._verify(r); refresh(); setToast('Mapping applied', { sub: 'Registry updated to v6 — this label maps automatically next time.', ico: 'check' });
    },
    correctLine: (rid, rowId, value, reason) => {
      const r = MD.byId[rid]; const c = r.close; const row = c.rows.find((x) => x.id === rowId);
      const orig = row.ws; row.ws = value; row.st = value === row.lk ? 'corrected' : 'mismatch';
      (c.corrections = c.corrections || []).push({ row: rowId, original: orig, corrected: value, by: user.name, at: nowIso(), reason });
      T(r, 'user', 'Coverage line corrected', user.name + ' · ' + row.coverage + ' ' + money(orig) + ' → ' + money(value) + ' · ' + reason);
      if (value === row.lk) { const x4 = MD.exceptions.find((e) => e.id === 'X4' && e.renewal === rid); if (x4) { x4.status = 'resolved'; x4.resolutionNote = 'Corrected to the locked value by ' + user.name + '.'; } }
      act._verify(r); refresh();
      setToast(value === row.lk ? 'Row verified against the lock' : 'Correction still conflicts with the lock', { sub: value === row.lk ? 'Every line now reconciles.' : 'Approval stays blocked until the source conflict is resolved with AmRisk.', ico: value === row.lk ? 'check' : 'alert' });
    },
    _verify: (r) => {
      const c = r.close; const bad = c.rows.filter((x) => ['exact', 'corrected'].indexOf(x.st) < 0);
      if (bad.length || !c.rows.length) return;
      if (MD.exceptions.some((e) => e.renewal === r.id && e.status === 'open' && ['Unknown coverage mapping', 'Worksheet / lock mismatch'].indexOf(e.cat) >= 0)) return;
      c.state = 'GRID_STAGED'; c.gates.exceptions = 'clear'; r.exception = false;
      T(r, 'engine', 'Selection verified', 'All required values reconcile with the authoritative locked selection');
      T(r, 'engine', 'Coverage grid staged', c.rows.length + ' rows · awaiting approval');
      r.engine = 'Close Chain · grid staged';
    },
    approveGrid: (rid) => {
      const r = MD.byId[rid]; const c = r.close;
      c.state = 'GRID_APPROVED'; c.gates.grid = 'approved'; c.approved = { by: user.name, at: nowIso(), version: 'grid v1' };
      T(r, 'user', 'Coverage grid approved (shadow)', user.name + ' · ' + c.rows.length + ' rows · comparison recorded, nothing committed');
      refresh(); setToast('Shadow grid approved', { sub: 'Comparison recorded. No production coverage, invoice, or policy write occurred.', ico: 'check' });
    },
    rejectGrid: (rid, reason) => { const r = MD.byId[rid]; const c = r.close; (c.rejections = c.rejections || []).unshift({ by: user.name, at: nowIso(), reason, detail: 'Returned to staging' }); c.state = 'GRID_STAGED'; T(r, 'user', 'Coverage grid rejected', user.name + ' · ' + reason); refresh(); setToast('Grid rejected', { sub: 'Returned to staging as parser-learning evidence. Nothing was committed.', ico: 'ban' }); },
    confirmReceipt: (rid, pickId) => {
      const r = MD.byId[pickId] || MD.byId[rid]; const src = MD.byId[rid].close.receipt;
      if (!r.close) r.close = { state: 'PAYMENT_PENDING', worksheet: null, lock: null, rows: [], billing: { funding: 'Unknown', expected: src.expected, invoice: 'Observed manually', financing: 'Not applicable', qb: 'Manual external entry' }, receipt: null, gates: { grid: 'pending', policy: 'unverified', billing: 'valid', receipt: 'pending', exceptions: 'clear' } };
      const c = r.close;
      c.receipt = Object.assign({}, c.receipt || {}, { expected: c.billing.expected, observed: src.observed, remitter: src.remitter, ref: src.ref, at: src.at, match: 'confirmed', by: user.name, confirmedAt: nowIso() });
      c.gates.receipt = 'confirmed'; c.gates.exceptions = 'clear'; c.state = 'RECEIPT_CONFIRMED'; r.exception = false;
      const cond = r.conditions.find((k) => k.cat === 'B14' && k.status !== 'resolved');
      if (cond) { cond.status = 'resolved'; cond.resolved = { at: nowIso(), by: user.name + ' (accounting)', how: 'Receipt confirmed · ' + src.ref }; }
      MD.drafts.filter((d) => d.renewal === r.id && (d.state === 'held' || d.state === 'ready') && d.category === 'Payment reminder').forEach((d) => { d.state = 'suppressed'; d.suppressedBy = 'Receipt confirmed — a payment reminder is no longer applicable.'; });
      const x6 = MD.exceptions.find((e) => e.id === 'X6'); if (x6) { x6.status = 'resolved'; x6.resolutionNote = 'Matched to ' + r.account + ' by ' + user.name + '.'; }
      T(r, 'accounting', 'Receipt confirmed', money(src.observed) + ' · ' + src.remitter + ' · ' + src.ref + ' · ' + user.name);
      if (gateOpen(c.gates)) { c.state = 'RELEASE_ELIGIBLE'; T(r, 'engine', 'Release gate opened (shadow)', 'All gates satisfied · no production action taken'); }
      refresh(); setToast('Receipt confirmed on ' + r.account, { sub: (cond ? 'B14 resolved and the held reminder was withdrawn. ' : '') + (gateOpen(c.gates) ? 'Release gate now eligible — Phase One takes no action.' : ''), ico: 'check' });
    },
    receiptOther: (rid, kind) => { const r = MD.byId[rid]; r.close.receipt.match = kind === 'cannot-match' ? 'unmatched' : kind; T(r, 'accounting', 'Receipt could not be matched', user.name); refresh(); setToast('Recorded as unmatched', { sub: 'The receipt gate stays closed and the exception remains open.', ico: 'alert' }); },
    setAutomation: (cat, mode) => {
      const a = MD.automation.find((x) => x.cat === cat);
      a.mode = mode; a.owner = user.name; a.changedAt = nowIso();
      a.note = mode === 'Autopilot Enabled' ? 'Granted by ' + user.name + ' today. Review returns automatically if the edit rate exceeds 15% over a rolling week.' : 'Returned to review by ' + user.name + ' today — every message in this category is reviewed before sending.';
      MD.automationLog.unshift({ at: nowIso(), who: user.name, what: cat + ' → ' + mode, why: 'Control change by ' + user.role });
      refresh(); setToast(cat + ' → ' + mode, { sub: 'Recorded with your name and the time. Rollback takes effect on the next evaluation.', ico: mode === 'Autopilot Enabled' ? 'play' : 'pause' });
    },
    user, refresh,
  };

  const parts = route.replace(/^#\/?/, '').split('/');
  const sec = parts[0] || 'today';
  let body, crumb = [];
  if (sec === 'renewal' && parts[1]) { body = <BriefingScreen id={parts[1]} />; crumb = [["Today's work", '#/today'], [(MD.byId[parts[1]] || {}).account || parts[1]]]; }
  else if (sec === 'outreach') { body = <OutreachScreen />; crumb = [['Prepared outreach']]; }
  else if (sec === 'activity') { body = <ActivityScreen />; crumb = [['Agent activity']]; }
  else if (sec === 'autonomy') { body = <AutonomyScreen />; crumb = [['Agent autonomy']]; }
  else if (sec === 'sources') { body = <SourcesScreen />; crumb = [['What I can see']]; }
  else if (sec === 'book') { body = <BookScreen />; crumb = [['Renewal book']]; }
  else if (sec === 'advisor' && parts[1]) { body = <AdvisorBriefing id={parts[1]} />; crumb = [['Advisor briefings', '#/advisors'], [(MD.advisors.find((a) => a.id === parts[1]) || {}).name || parts[1]]]; }
  else if (sec === 'advisors') { body = <AdvisorsScreen />; crumb = [['Advisor briefings']]; }
  else if (sec === 'close') { body = <CloseScreen />; crumb = [['Close worklist']]; }
  else { body = <TodayScreen />; crumb = [["Today's work"]]; }

  const counts = {
    today: AG.decisions().length,
    outreach: MD.drafts.filter((d) => d.state === 'ready').length,
    close: MD.renewals.filter((r) => r.close && (r.close.state === 'GRID_STAGED' || r.close.rows.filter((x) => ['exact', 'corrected'].indexOf(x.st) < 0).length || (r.close.receipt && r.close.receipt.match === 'ambiguous'))).length,
    autonomy: MD.automation.filter((a) => a.mode === 'Eligible for Consideration').length,
  };
  const ctx = Object.assign({}, act, { tick, user, staleEmail: false, sfWrites: true, loading: false, drawer: null, openEvidence: () => {}, closeEvidence: () => {}, toast: null });

  return <MCtx.Provider value={ctx}>
    <div className="ag">
      <div className="ag-side">
        <div className="ag-brand"><b>Madison</b><span>Renewal agent</span></div>
        <div className="ag-nav">
          {V_NAV.map((n, i) => n.g ? <div className="ag-navlbl" key={'g' + i}>{n.g}</div>
            : <div key={n.id} className={'ag-item' + (sec === n.id || (sec === 'advisor' && n.id === 'advisors') ? ' on' : '')} onClick={() => nav('#/' + n.id)}>
              <Icon n={n.ico} s={15} />{n.label}
              {counts[n.id] ? <span className={'n' + (n.id === 'today' ? ' hot' : '')}>{counts[n.id]}</span> : null}
            </div>)}
        </div>
        <div className="ag-foot">
          <div className="ag-who" onClick={() => setMenu(!menu)}>
            <span className="av">{user.name.split(' ').map((x) => x[0]).join('')}</span>
            <div className="grow"><b>{user.name}</b><span>{user.role}</span></div>
            <Icon n="chevron-up" s={13} style={{ color: '#8A92A3' }} />
          </div>
          {menu ? <Menu onClose={() => setMenu(false)} style={{ bottom: 54, top: 'auto', left: 10, right: 10, minWidth: 0 }}>
            <div className="mh">Acting as</div>
            {MD.ROLES.map((r) => <button key={r.id} className={r.id === roleId ? 'on' : ''} onClick={() => { setRole(r.id); setMenu(false); setToast('Now acting as ' + r.role, { sub: 'Only the decisions this role owns are offered.', ico: 'user' }); }}>
              <Icon n={r.id === roleId ? 'check' : 'user'} s={13} />{r.role}
            </button>)}
          </Menu> : null}
        </div>
        <div className="ag-grad" />
      </div>
      <div className="ag-main">
        <div className="ag-top">
          <div className="ag-crumb">{crumb.map((c, i) => <span key={i} className="row" style={{ gap: 8, minWidth: 0 }}>{i ? <Icon n="chevron-right" s={13} style={{ color: 'var(--text-dim)' }} /> : null}{c[1] ? <span className="lk" onClick={() => nav(c[1])}>{c[0]}</span> : <b>{c[0]}</b>}</span>)}</div>
          <span className="ag-sync" onClick={() => nav('#/activity')}><i className="d" />Agent run complete 06:14</span>
          <span className="dim" style={{ fontSize: 12.5, flex: '0 0 auto' }}>Cycle 2026</span>
        </div>
        <div className="ag-body">{body}</div>
      </div>
    </div>
    {toast ? <div className="mtoast">
      <span className="ti"><Icon n={toast.ico || 'check'} s={15} /></span>
      <div className="grow"><div className="tt">{toast.msg}</div>{toast.sub ? <div className="ts">{toast.sub}</div> : null}</div>
      <button className="btn sm" onClick={() => setToastRaw(null)}>Dismiss</button>
    </div> : null}
  </MCtx.Provider>;
}

ReactDOM.createRoot(document.getElementById('root')).render(<VApp />);
