// Modal surfaces: Command palette, HITL approval, Handoff packet, Audit export, PHI redaction

const { useState: useStateM, useEffect: useEffectM, useRef: useRefM } = React;

// ---------- Modal wrapper ----------
const Modal = ({ open, onClose, children, width = 720 }) => {
  if (!open) return null;
  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(57, 0, 99, 0.4)', backdropFilter: 'blur(4px)',
        zIndex: 100, display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
        paddingTop: 80, animation: 'fadeIn 180ms ease',
      }}
    >
      <div
        onClick={e => e.stopPropagation()}
        style={{
          width, maxWidth: 'calc(100vw - 40px)', maxHeight: 'calc(100vh - 120px)',
          background: 'white', borderRadius: 16, boxShadow: 'var(--shadow-lg)',
          display: 'flex', flexDirection: 'column', overflow: 'hidden',
          animation: 'modalIn 220ms cubic-bezier(0.2, 0.8, 0.2, 1)',
        }}
      >
        {children}
      </div>
      <style>{`
        @keyframes modalIn { from { opacity: 0; transform: translateY(-8px) scale(0.98); } to { opacity: 1; transform: none; } }
      `}</style>
    </div>
  );
};

// ---------- Command palette ----------
const CommandPalette = ({ open, onClose, onSelectTicket, tickets, onPause }) => {
  const [q, setQ] = useStateM('');
  const [idx, setIdx] = useStateM(0);
  const inputRef = useRefM(null);

  useEffectM(() => {
    if (open) { setQ(''); setIdx(0); setTimeout(() => inputRef.current?.focus(), 50); }
  }, [open]);

  const commands = [
    { id: 'assign', label: 'Assign to me', icon: 'user', hint: 'A' },
    { id: 'escalate', label: 'Escalate to human lead', icon: 'route', hint: 'E' },
    { id: 'pause', label: 'Pause Violet globally', icon: 'pause', hint: '⇧P', action: onPause },
    { id: 'retry', label: 'Retry with different strategy', icon: 'refresh', hint: 'R' },
    { id: 'cmdb', label: 'Open CMDB record', icon: 'database', hint: '⌘D' },
    { id: 'audit', label: 'Export audit log (PDF)', icon: 'download', hint: '⌘E' },
    { id: 'phi', label: 'Toggle PHI redaction view', icon: 'eye', hint: '⌘P' },
  ];

  const qlower = q.toLowerCase();
  const cmdHits = q ? commands.filter(c => c.label.toLowerCase().includes(qlower)) : commands;
  const ticketHits = q ? tickets.filter(t => t.id.toLowerCase().includes(qlower) || t.subject.toLowerCase().includes(qlower) || USERS[t.user]?.name.toLowerCase().includes(qlower)).slice(0, 5) : [];

  const allItems = [...cmdHits.map(c => ({ type: 'cmd', ...c })), ...ticketHits.map(t => ({ type: 'ticket', ...t }))];

  useEffectM(() => {
    const h = (e) => {
      if (!open) return;
      if (e.key === 'ArrowDown') { e.preventDefault(); setIdx(i => Math.min(allItems.length - 1, i + 1)); }
      if (e.key === 'ArrowUp') { e.preventDefault(); setIdx(i => Math.max(0, i - 1)); }
      if (e.key === 'Enter') {
        const item = allItems[idx];
        if (item?.type === 'ticket') { onSelectTicket(item.id); onClose(); }
        if (item?.type === 'cmd') { item.action?.(); onClose(); }
      }
      if (e.key === 'Escape') onClose();
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, idx, allItems]);

  if (!open) return null;
  return (
    <Modal open={open} onClose={onClose} width={620}>
      <div style={{padding: '16px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 12}}>
        <Icon name="search" size={18} color="var(--nyu-purple)"/>
        <input
          ref={inputRef}
          value={q}
          onChange={e => { setQ(e.target.value); setIdx(0); }}
          placeholder="Search tickets, users, KBAs, or run a command…"
          style={{border: 'none', outline: 'none', fontSize: 16, flex: 1, fontFamily: 'var(--font-body)'}}
        />
        <span className="kbd">ESC</span>
      </div>
      <div className="scroll" style={{maxHeight: 380, overflowY: 'auto', padding: 8}}>
        {cmdHits.length > 0 && (
          <div>
            <div style={{padding: '8px 12px', fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--nyu-disabled)'}}>Commands</div>
            {cmdHits.map((c, i) => {
              const active = idx === i;
              return (
                <div key={c.id} onMouseEnter={() => setIdx(i)} onClick={() => { c.action?.(); onClose(); }} style={{
                  display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
                  background: active ? 'var(--purple-tint)' : 'transparent',
                  borderRadius: 8, cursor: 'pointer',
                }}>
                  <Icon name={c.icon} size={15} color={active ? 'var(--nyu-purple)' : 'var(--nyu-text)'}/>
                  <span style={{flex: 1, fontSize: 14, color: active ? 'var(--nyu-purple)' : 'var(--nyu-black)', fontWeight: active ? 600 : 500}}>{c.label}</span>
                  <span className="kbd">{c.hint}</span>
                </div>
              );
            })}
          </div>
        )}
        {ticketHits.length > 0 && (
          <div style={{marginTop: 8}}>
            <div style={{padding: '8px 12px', fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--nyu-disabled)'}}>Tickets</div>
            {ticketHits.map((t, i) => {
              const active = idx === cmdHits.length + i;
              return (
                <div key={t.id} onMouseEnter={() => setIdx(cmdHits.length + i)} onClick={() => { onSelectTicket(t.id); onClose(); }} style={{
                  display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px',
                  background: active ? 'var(--purple-tint)' : 'transparent',
                  borderRadius: 8, cursor: 'pointer',
                }}>
                  <span className="mono" style={{fontSize: 11, color: 'var(--nyu-text)', minWidth: 92}}>{t.id}</span>
                  <span style={{flex: 1, fontSize: 13, fontWeight: 500}}>{t.subject}</span>
                  <span style={{fontSize: 11, color: 'var(--nyu-text)'}}>{USERS[t.user]?.name}</span>
                </div>
              );
            })}
          </div>
        )}
        {q && allItems.length === 0 && (
          <div style={{padding: 40, textAlign: 'center', color: 'var(--nyu-disabled)', fontSize: 13}}>No matches for "{q}"</div>
        )}
      </div>
      <div style={{padding: '10px 20px', borderTop: '1px solid var(--border)', display: 'flex', gap: 16, fontSize: 11, color: 'var(--nyu-text)'}}>
        <span><span className="kbd">↑↓</span> Navigate</span>
        <span><span className="kbd">↵</span> Open</span>
        <span><span className="kbd">ESC</span> Close</span>
      </div>
    </Modal>
  );
};

// ---------- HITL approval modal (for lost device wipe) ----------
// TypingLine — char-by-char reveal for streamed tool output
const TypingLine = ({ text }) => {
  const [n, setN] = useStateM(0);
  useEffectM(() => {
    setN(0);
    let i = 0;
    const t = setInterval(() => {
      i += Math.max(1, Math.round(text.length / 40));
      if (i >= text.length) { setN(text.length); clearInterval(t); }
      else setN(i);
    }, 40);
    return () => clearInterval(t);
  }, [text]);
  return <span>{text.slice(0, n)}<span style={{opacity: n < text.length ? 1 : 0, animation: 'blink 800ms steps(2) infinite'}}>▍</span></span>;
};

const HitlApprovalModal = ({ open, onClose, ticket }) => {
  if (!ticket) return null;
  const user = USERS[ticket.user];
  const device = DEVICES[ticket.device];

  // execution state machine: idle → editing → idle → executing → success
  //                                  ↘ rejecting → [branch chosen] → rejected
  const [phase, setPhase] = useStateM('idle');
  const [rejectBranch, setRejectBranch] = useStateM(null); // 'replan' | 'escalate' | 'close'
  const [rejectReason, setRejectReason] = useStateM('');
  const [holdProgress, setHoldProgress] = useStateM(0); // 0..1 for press-and-hold
  const [currentStep, setCurrentStep] = useStateM(-1); // which step index is running
  const [stepStates, setStepStates] = useStateM({}); // { idx: 'done' | 'running' | 'queued', duration, output }
  const [elapsedMs, setElapsedMs] = useStateM(0);
  const [plan, setPlan] = useStateM([]);
  const [verifyState, setVerifyState] = useStateM(null); // null | 'checking' | 'valid' | 'blocked'
  const holdTimerRef = useRefM(null);
  const holdStartRef = useRefM(null);
  const executionStartRef = useRefM(null);
  const elapsedTimerRef = useRefM(null);

  const basePlan = [
    { tool: 'ldap-mcp', action: 'suspend_account', target: `upn=${user?.upn}`, risk: 'medium', reversible: true,
      durations: [780, 1180], output: 'account.status → SUSPENDED · sessions revoked: 3' },
    { tool: 'ldap-mcp', action: 'revoke_mfa_tokens', target: `upn=${user?.upn}`, risk: 'low', reversible: true,
      durations: [420, 620], output: 'push_duo, webauthn, sms → all revoked · token count: 4' },
    { tool: 'intune-mcp', action: 'selective_wipe', target: `device=${device?.id}`, risk: 'DESTRUCTIVE', reversible: false,
      durations: [2400, 3800], output: 'wipe command queued · device heartbeat 18m ago · will execute on next check-in' },
    { tool: 'comms-mcp', action: 'file_security_incident', target: 'security@nyulangone.org', risk: 'low', reversible: true,
      durations: [560, 840], output: 'INC-2826 filed · PHI potential flagged · assigned to on-call SOC' },
    { tool: 'catalog-mcp', action: 'allocate_loaner', target: 'L-022 · 1 PA concierge', risk: 'low', reversible: true,
      durations: [640, 920], output: 'L-022 reserved · pickup window 15:00–17:00 · SMS sent to +1-***-**47' },
  ];

  useEffectM(() => {
    if (open) {
      setPhase('idle');
      setCurrentStep(-1);
      setStepStates({});
      setElapsedMs(0);
      setHoldProgress(0);
      setPlan(basePlan.map((p, i) => ({ ...p, id: `s-${i}`, edited: false, originalIdx: i })));
      setVerifyState(null);
      setRejectBranch(null);
      setRejectReason('');
    }
  }, [open, ticket?.id]);

  // drive step execution sequentially
  useEffectM(() => {
    if (phase !== 'executing') return;
    executionStartRef.current = Date.now();
    elapsedTimerRef.current = setInterval(() => {
      setElapsedMs(Date.now() - executionStartRef.current);
    }, 50);
    let cancelled = false;
    (async () => {
      for (let i = 0; i < plan.length; i++) {
        if (cancelled) return;
        setCurrentStep(i);
        setStepStates(prev => ({ ...prev, [i]: { status: 'running' } }));
        const [min, max] = plan[i].durations;
        const dur = Math.round(min + Math.random() * (max - min));
        await new Promise(r => setTimeout(r, dur));
        if (cancelled) return;
        setStepStates(prev => ({ ...prev, [i]: { status: 'done', duration: dur, output: plan[i].output } }));
      }
      if (cancelled) return;
      setCurrentStep(-1);
      clearInterval(elapsedTimerRef.current);
      setPhase('success');
    })();
    return () => { cancelled = true; clearInterval(elapsedTimerRef.current); };
  }, [phase]);

  // press-and-hold handlers
  const startHold = () => {
    if (phase !== 'idle') return;
    holdStartRef.current = Date.now();
    holdTimerRef.current = setInterval(() => {
      const elapsed = Date.now() - holdStartRef.current;
      const progress = Math.min(elapsed / 600, 1);
      setHoldProgress(progress);
      if (progress >= 1) {
        clearInterval(holdTimerRef.current);
        setPhase('executing');
        setHoldProgress(0);
      }
    }, 16);
  };
  const cancelHold = () => {
    if (holdTimerRef.current) clearInterval(holdTimerRef.current);
    setHoldProgress(0);
  };

  const totalDone = Object.values(stepStates).filter(s => s.status === 'done').length;
  const totalDuration = Object.values(stepStates).reduce((a, s) => a + (s.duration || 0), 0);

  // edit handlers
  const updateStep = (id, patch) => {
    setPlan(prev => prev.map(s => s.id === id ? { ...s, ...patch, edited: true } : s));
    setVerifyState(null); // invalidate prior verification
  };
  const deleteStep = (id) => {
    setPlan(prev => prev.filter(s => s.id !== id));
    setVerifyState(null);
  };
  const moveStep = (id, dir) => {
    setPlan(prev => {
      const idx = prev.findIndex(s => s.id === id);
      const newIdx = idx + dir;
      if (newIdx < 0 || newIdx >= prev.length) return prev;
      const next = [...prev];
      [next[idx], next[newIdx]] = [next[newIdx], next[idx]];
      return next;
    });
    setVerifyState(null);
  };
  const addStep = () => {
    const newStep = {
      id: `s-new-${Date.now()}`,
      tool: 'sccm-mcp', action: 'force_remote_lock',
      target: `device=${device?.id}`,
      risk: 'medium', reversible: true, edited: true, added: true,
      durations: [600, 900], output: 'remote lock command queued · device must check in within 4h',
    };
    setPlan(prev => [...prev, newStep]);
    setVerifyState(null);
  };
  const reVerify = () => {
    setVerifyState('checking');
    setTimeout(() => {
      // Count destructive steps
      const destructiveCount = plan.filter(s => s.risk === 'DESTRUCTIVE').length;
      // Added steps
      const addedSteps = plan.filter(s => s.added);
      // Check for out-of-scope tools (everything except ldap, comms, catalog is restricted)
      const outOfScope = plan.filter(s => s.edited && ['sccm-mcp', 'epic-ops-mcp'].includes(s.tool));

      const tooMany = plan.length > 7;
      const wipeRemoved = !plan.some(s => s.risk === 'DESTRUCTIVE');

      if (tooMany) {
        setVerifyState({ status: 'blocked', reason: 'Plan exceeds 7-step limit for single-approver scope. Split into sub-plans.' });
      } else if (wipeRemoved) {
        setVerifyState({ status: 'blocked', reason: 'Removing the wipe leaves PHI exposed on a lost device. Violation of policy POL-0019.' });
      } else if (destructiveCount >= 2) {
        setVerifyState({ status: 'needs_second',
          reason: 'Plan now contains 2 destructive actions.',
          rule: 'POL-0042 · Multi-destructive plans require 2 approvers',
          eligibleApprovers: ['Dana Reyes (IT Security Lead, on-call)', 'Marcus Chen (CISO delegate)'] });
      } else if (outOfScope.length > 0) {
        setVerifyState({ status: 'needs_second',
          reason: `Step uses ${outOfScope[0].tool}, which is outside your single-approver allow-list.`,
          rule: 'POL-0019 · Cross-system grants require 2 approvers',
          eligibleApprovers: ['Dana Reyes (IT Security Lead, on-call)', 'Priya Shah (Radiology Dept manager)'] });
      } else if (addedSteps.length > 0 && addedSteps[0].tool !== 'ldap-mcp') {
        setVerifyState({ status: 'needs_second',
          reason: 'Added step expands plan scope beyond the original runbook.',
          rule: 'POL-0031 · Scope expansions require 2 approvers',
          eligibleApprovers: ['Dana Reyes (IT Security Lead, on-call)'] });
      } else {
        setVerifyState({ status: 'valid', confidence: (0.92 + Math.random() * 0.07).toFixed(2) });
      }
    }, 900);
  };

  const editedCount = plan.filter(s => s.edited).length;
  const deletedCount = basePlan.length - plan.filter(s => s.originalIdx !== undefined).length;
  const hasChanges = editedCount > 0 || plan.length !== basePlan.length
    || plan.some((s, i) => s.originalIdx !== undefined && s.originalIdx !== i);

  const handleClose = () => {
    if (phase === 'executing') return; // prevent close during execution
    onClose();
  };

  const headerConfig = {
    idle: { iconBg: 'var(--orange-tint)', iconColor: '#8A4E00', icon: 'shield',
      title: 'Destructive-action approval', subtitle: 'Violet has staged a 5-step plan. Review each step, then approve or edit.' },
    editing: { iconBg: 'var(--purple-tint)', iconColor: 'var(--nyu-purple)', icon: 'settings',
      title: 'Edit plan', subtitle: `Modify steps, reorder, or add new tool calls. Changes re-verify against policy before approval.` },
    rejecting: { iconBg: 'var(--red-tint)', iconColor: 'var(--nyu-red)', icon: 'x',
      title: 'Reject plan', subtitle: 'Choose what happens next. Your reason becomes a training signal.' },
    rejected: { iconBg: 'var(--nyu-gray-100)', iconColor: 'var(--nyu-text)', icon: 'check',
      title: rejectBranch === 'replan' ? 'Sent back to Violet' : rejectBranch === 'escalate' ? 'Escalated to human queue' : 'Closed as no-action',
      subtitle: 'Decision logged · Violet learns from this signal' },
    executing: { iconBg: 'var(--purple-tint)', iconColor: 'var(--nyu-purple)', icon: 'bolt',
      title: 'Executing approved plan', subtitle: `Step ${Math.max(currentStep + 1, 1)}/${plan.length} · Violet is calling tools under your SSO identity` },
    success: { iconBg: 'var(--mint-tint)', iconColor: '#006B51', icon: 'check',
      title: 'Plan executed · all steps verified', subtitle: `${plan.length} tool calls in ${(totalDuration/1000).toFixed(1)}s · user notified · audit entry signed` },
  }[phase] || {};

  return (
    <Modal open={open} onClose={handleClose} width={780}>
      <div style={{padding: '18px 24px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 14}}>
        <div style={{width: 36, height: 36, borderRadius: 10, background: headerConfig.iconBg, display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 300ms'}}>
          {phase === 'executing'
            ? <span className="ring-spin" style={{width: 16, height: 16, borderWidth: 2, borderColor: 'var(--nyu-purple)', borderTopColor: 'transparent'}}></span>
            : <Icon name={headerConfig.icon} size={18} color={headerConfig.iconColor}/>}
        </div>
        <div>
          <div style={{fontSize: 16, fontWeight: 700, color: 'var(--nyu-deep-purple)'}}>{headerConfig.title}</div>
          <div style={{fontSize: 12, color: 'var(--nyu-text)', marginTop: 2}}>{headerConfig.subtitle}</div>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={handleClose} style={{marginLeft: 'auto'}} disabled={phase === 'executing'}><Icon name="x" size={14}/></button>
      </div>

      <div className="scroll" style={{flex: 1, overflowY: 'auto', padding: '20px 24px'}}>
        {/* Context */}
        <div style={{display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 20}}>
          <div style={{padding: 12, background: 'var(--nyu-gray-50)', borderRadius: 10}}>
            <div className="hint">User</div>
            <div style={{fontSize: 14, fontWeight: 600, marginTop: 2}}>{user?.name}</div>
            <div style={{fontSize: 12, color: 'var(--nyu-text)'}}>{user?.dept} · {user?.title}</div>
          </div>
          <div style={{padding: 12, background: 'var(--nyu-gray-50)', borderRadius: 10}}>
            <div className="hint">Device</div>
            <div style={{fontSize: 14, fontWeight: 600, marginTop: 2}}>{device?.id}</div>
            <div style={{fontSize: 12, color: 'var(--nyu-text)'}}>{device?.model} · BitLocker ✓</div>
          </div>
          <div style={{padding: 12, background: 'var(--red-tint)', borderRadius: 10}}>
            <div className="hint" style={{color: '#B00045'}}>PHI exposure</div>
            <div style={{fontSize: 14, fontWeight: 600, marginTop: 2, color: '#B00045'}}>Potential</div>
            <div style={{fontSize: 12, color: '#B00045'}}>User self-disclosed cached imaging</div>
          </div>
        </div>

        <div style={{display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 12}}>
          <div style={{fontSize: 13, fontWeight: 700, letterSpacing: '-0.01em'}}>
            {phase === 'idle' && `Proposed plan · ${plan.length} step${plan.length !== 1 ? 's' : ''}${hasChanges ? ' · edited' : ''}`}
            {phase === 'editing' && `Editing plan · ${plan.length} step${plan.length !== 1 ? 's' : ''}`}
            {phase === 'rejecting' && 'Reject this plan'}
            {phase === 'rejected' && 'Decision recorded'}
            {phase === 'awaiting_second' && 'Awaiting second approver'}
            {phase === 'executing' && 'Executing plan · live tool stream'}
            {phase === 'success' && 'Execution log · all steps verified'}
          </div>
          {phase === 'executing' && (
            <div className="mono" style={{fontSize: 11, color: 'var(--nyu-text)'}}>
              {totalDone}/{plan.length} done · {(elapsedMs/1000).toFixed(1)}s
            </div>
          )}
          {phase === 'editing' && editedCount + (basePlan.length - plan.filter(s => s.originalIdx !== undefined).length) > 0 && (
            <div className="mono" style={{fontSize: 11, color: 'var(--nyu-purple)', fontWeight: 600}}>
              {editedCount} edit{editedCount !== 1 ? 's' : ''} pending
            </div>
          )}
        </div>

        {/* Diff strip — shows in idle phase if plan was edited, above the step list */}
        {phase === 'idle' && hasChanges && (
          <div style={{
            marginBottom: 12, padding: '10px 14px', borderRadius: 10,
            background: 'var(--purple-tint)', border: '1px solid var(--nyu-purple)',
            fontSize: 12,
          }}>
            <div style={{fontWeight: 700, color: 'var(--nyu-deep-purple)', marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6}}>
              <Icon name="sparkle" size={12} color="var(--nyu-purple)"/>
              Plan edited by a.kotler · Violet will remember this pattern
            </div>
            <div style={{color: 'var(--nyu-text)', lineHeight: 1.5}}>
              {editedCount > 0 && <span>{editedCount} step{editedCount !== 1 ? 's' : ''} modified · </span>}
              Re-verified against POL-0019 · confidence {verifyState?.status === 'valid' ? verifyState.confidence : '0.94'}
            </div>
          </div>
        )}

        {/* Reject branch selector */}
        {phase === 'rejecting' && (
          <div style={{display: 'flex', flexDirection: 'column', gap: 10}}>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', lineHeight: 1.5, marginBottom: 4}}>
              Pick what happens next. Your reason becomes training signal — if 3+ techs reject with the same reason, Violet flags it as a pattern.
            </div>
            {[
              { id: 'replan', icon: 'refresh', color: 'var(--nyu-purple)', bg: 'var(--purple-tint)', border: 'var(--nyu-purple)',
                title: 'Send back to Violet',
                desc: 'Violet re-plans with your critique as context. Ticket stays assigned to the agent.',
                badge: 'Most common' },
              { id: 'escalate', icon: 'route', color: '#8A4E00', bg: 'var(--orange-tint)', border: '#FFB74D',
                title: 'Escalate to human tech',
                desc: 'Drops into the regular queue with this draft plan attached as context. Next available tech takes it.',
                badge: null },
              { id: 'close', icon: 'x', color: 'var(--nyu-text)', bg: 'var(--nyu-gray-50)', border: 'var(--border)',
                title: 'Close as no-action',
                desc: 'Resolved with no changes. User gets a gentle "we looked, it\'s not an issue" message.',
                badge: null },
            ].map(branch => (
              <button key={branch.id}
                onClick={() => setRejectBranch(branch.id)}
                style={{
                  display: 'flex', alignItems: 'flex-start', gap: 12,
                  padding: '12px 14px', borderRadius: 10, cursor: 'pointer',
                  background: rejectBranch === branch.id ? branch.bg : 'white',
                  border: `${rejectBranch === branch.id ? '2px' : '1px'} solid ${rejectBranch === branch.id ? branch.border : 'var(--border)'}`,
                  textAlign: 'left', width: '100%',
                  transition: 'all 160ms',
                }}>
                <div style={{
                  width: 32, height: 32, borderRadius: 8, flexShrink: 0,
                  background: branch.bg, display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  <Icon name={branch.icon} size={16} color={branch.color}/>
                </div>
                <div style={{flex: 1, minWidth: 0}}>
                  <div style={{display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2}}>
                    <div style={{fontSize: 14, fontWeight: 700}}>{branch.title}</div>
                    {branch.badge && <span className="chip chip-outline" style={{fontSize: 10}}>{branch.badge}</span>}
                  </div>
                  <div style={{fontSize: 12, color: 'var(--nyu-text)', lineHeight: 1.5}}>{branch.desc}</div>
                </div>
                <div style={{
                  width: 18, height: 18, borderRadius: '50%', flexShrink: 0, marginTop: 6,
                  border: `2px solid ${rejectBranch === branch.id ? branch.border : 'var(--border)'}`,
                  background: rejectBranch === branch.id ? branch.border : 'white',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>
                  {rejectBranch === branch.id && <Icon name="check" size={10} color="white" strokeWidth={3}/>}
                </div>
              </button>
            ))}
            {rejectBranch && (
              <div style={{marginTop: 6}}>
                <label style={{fontSize: 12, fontWeight: 600, color: 'var(--nyu-text)', display: 'block', marginBottom: 6}}>
                  {rejectBranch === 'replan' ? 'What should Violet do differently?' :
                   rejectBranch === 'escalate' ? 'Context for the next tech' :
                   'Reason for closing'}
                </label>
                <textarea
                  value={rejectReason}
                  onChange={e => setRejectReason(e.target.value)}
                  placeholder={
                    rejectBranch === 'replan' ? 'e.g. "Wrong user identified — check device enrollment history"' :
                    rejectBranch === 'escalate' ? 'e.g. "Requester\'s manager needs to confirm device was issued"' :
                    'e.g. "User found the laptop"'
                  }
                  rows={3}
                  style={{
                    width: '100%', padding: 10, borderRadius: 8,
                    border: '1px solid var(--border)', fontSize: 13,
                    fontFamily: 'inherit', resize: 'vertical', lineHeight: 1.5,
                  }}/>
                <div style={{display: 'flex', gap: 6, marginTop: 8, flexWrap: 'wrap'}}>
                  <span style={{fontSize: 11, color: 'var(--nyu-text)', marginRight: 4, padding: '4px 0'}}>Quick tags:</span>
                  {(rejectBranch === 'replan'
                    ? ['Wrong user', 'Scope too broad', 'Missing verification', 'Wrong tool']
                    : rejectBranch === 'escalate'
                    ? ['Needs manager approval', 'PHI ambiguity', 'Policy gap']
                    : ['User resolved', 'Duplicate', 'Spam/test']
                  ).map(tag => (
                    <button key={tag} onClick={() => setRejectReason(r => r ? `${r} · ${tag}` : tag)}
                      style={{
                        padding: '4px 10px', fontSize: 11, borderRadius: 999,
                        border: '1px solid var(--border)', background: 'white',
                        color: 'var(--nyu-text)', cursor: 'pointer',
                      }}>{tag}</button>
                  ))}
                </div>
              </div>
            )}
          </div>
        )}

        {/* Rejected (after submit) */}
        {phase === 'rejected' && (
          <div style={{padding: 20, background: 'var(--nyu-gray-50)', borderRadius: 12, textAlign: 'center'}}>
            <div style={{width: 48, height: 48, borderRadius: '50%', background: 'var(--nyu-deep-purple)',
              margin: '0 auto 12px', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
              <Icon name={rejectBranch === 'replan' ? 'refresh' : rejectBranch === 'escalate' ? 'route' : 'check'} size={22} color="white" strokeWidth={2}/>
            </div>
            <div style={{fontSize: 15, fontWeight: 700, marginBottom: 6}}>
              {rejectBranch === 'replan' && 'Back to Violet'}
              {rejectBranch === 'escalate' && 'In the human queue'}
              {rejectBranch === 'close' && 'Ticket closed'}
            </div>
            <div style={{fontSize: 13, color: 'var(--nyu-text)', marginBottom: 16, lineHeight: 1.5}}>
              {rejectBranch === 'replan' && `Violet received your critique and is re-planning. New plan expected in ~15s. You'll get a notification.`}
              {rejectBranch === 'escalate' && `Ticket moved to the general queue with your context attached. Currently 2 techs available.`}
              {rejectBranch === 'close' && `James Okoye will receive a friendly message. Violet logged "${rejectReason || 'no reason'}" as a learning signal.`}
            </div>
            <div style={{
              padding: 10, background: 'white', borderRadius: 8, border: '1px solid var(--border)',
              fontSize: 12, color: 'var(--nyu-text)', textAlign: 'left',
            }}>
              <div style={{fontWeight: 600, marginBottom: 4, color: 'var(--nyu-deep-purple)'}}>Audit entry</div>
              <div className="mono" style={{fontSize: 11, lineHeight: 1.5}}>
                a.kotler rejected plan · branch={rejectBranch} · reason="{rejectReason || '(none)'}"<br/>
                AUD-{ticket.id}-{Date.now().toString().slice(-5)} · signed at {new Date().toLocaleTimeString('en-US', {hour12: false})}
              </div>
            </div>
          </div>
        )}
        {phase === 'awaiting_second' && (
          <div style={{padding: 20, background: 'var(--nyu-gray-50)', borderRadius: 12}}>
            <div style={{display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16}}>
              <div style={{width: 44, height: 44, borderRadius: '50%', background: '#B06A00',
                display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0}}>
                <Icon name="clock" size={20} color="white" strokeWidth={2}/>
              </div>
              <div style={{flex: 1}}>
                <div style={{fontSize: 15, fontWeight: 700, color: 'var(--nyu-deep-purple)'}}>Waiting on Dana Reyes</div>
                <div style={{fontSize: 12, color: 'var(--nyu-text)', marginTop: 2}}>
                  Request sent via Teams at {new Date(Date.now() - 23000).toLocaleTimeString('en-US', {hour12: false})} · avg response 3m 12s
                </div>
              </div>
              <div className="mono" style={{fontSize: 11, color: 'var(--nyu-text)', padding: '4px 8px', background: 'white', borderRadius: 6, border: '1px solid var(--border)'}}>
                23s elapsed
              </div>
            </div>

            <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 14}}>
              {[
                { name: 'A. Kotler', role: 'You · Tier 2', status: 'approved', time: '00:47:02', initials: 'AK' },
                { name: 'Dana Reyes', role: 'IT Security Lead', status: 'pending', time: '—', initials: 'DR' },
              ].map((a, i) => (
                <div key={i} style={{
                  padding: 12, background: 'white', borderRadius: 10,
                  border: a.status === 'approved' ? '1px solid #8FD9B8' : '1px dashed #F3C78B',
                }}>
                  <div style={{display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6}}>
                    <div style={{width: 28, height: 28, borderRadius: '50%',
                      background: a.status === 'approved' ? 'var(--mint-tint)' : '#FFF4E5',
                      color: a.status === 'approved' ? '#006B51' : '#7A4A00',
                      fontSize: 11, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
                      {a.initials}
                    </div>
                    <div style={{flex: 1, minWidth: 0}}>
                      <div style={{fontSize: 12, fontWeight: 700}}>{a.name}</div>
                      <div style={{fontSize: 10, color: 'var(--nyu-text)'}}>{a.role}</div>
                    </div>
                  </div>
                  <div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 11}}>
                    <span style={{
                      color: a.status === 'approved' ? '#006B51' : '#7A4A00',
                      fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4,
                    }}>
                      {a.status === 'approved' ? <><Icon name="check" size={10} color="#006B51" strokeWidth={3}/> Approved</> : <>⋯ Pending</>}
                    </span>
                    <span className="mono" style={{color: 'var(--nyu-text)', fontSize: 10}}>{a.time}</span>
                  </div>
                </div>
              ))}
            </div>

            <div style={{padding: 10, background: 'white', borderRadius: 8, border: '1px solid var(--border)', fontSize: 12}}>
              <div style={{fontWeight: 600, marginBottom: 4, color: 'var(--nyu-deep-purple)'}}>Reason flagged by Violet</div>
              <div style={{color: 'var(--nyu-text)', marginBottom: 6}}>{verifyState?.reason}</div>
              <div className="mono" style={{fontSize: 11, color: 'var(--nyu-text)'}}>{verifyState?.rule}</div>
            </div>

            <div style={{marginTop: 14, fontSize: 11, color: 'var(--nyu-text)', display: 'flex', alignItems: 'center', gap: 6}}>
              <Icon name="info" size={11} color="var(--nyu-text)"/>
              Plan is frozen. Tickets don't time out — Dana has the same context you do.
            </div>
          </div>
        )}
        {!['rejecting', 'rejected', 'awaiting_second'].includes(phase) && plan.map((p, i) => {
          const state = stepStates[i];
          const status = state?.status || (phase === 'idle' ? 'pending' : 'queued');
          const isRunning = status === 'running';
          const isDone = status === 'done';
          const isDestructive = p.risk === 'DESTRUCTIVE';
          const isEditing = phase === 'editing';

          let bg = 'var(--nyu-gray-50)', border = '1px solid var(--border)';
          if (isDestructive && !isDone) { bg = 'var(--red-tint)'; border = '1px solid #FFB7C8'; }
          if (isRunning) { bg = 'var(--purple-tint)'; border = '1px solid var(--nyu-purple)'; }
          if (isDone) { bg = 'var(--mint-tint)'; border = '1px solid #8FD9B8'; }
          if (isEditing && p.edited) { bg = 'var(--purple-tint)'; border = '1px dashed var(--nyu-purple)'; }
          if (isEditing && p.added) { bg = 'var(--mint-tint)'; border = '1px dashed #006B51'; }

          let badgeBg = 'var(--nyu-purple)';
          if (isDestructive && !isDone) badgeBg = 'var(--nyu-red)';
          if (isDone) badgeBg = '#006B51';
          if (status === 'queued') badgeBg = 'var(--nyu-disabled)';

          // ----- EDITING ROW -----
          if (isEditing) {
            return (
              <div key={p.id} style={{
                display: 'flex', gap: 10, padding: '12px 14px', marginBottom: 8,
                background: bg, border, borderRadius: 10,
              }}>
                {/* reorder controls */}
                <div style={{display: 'flex', flexDirection: 'column', gap: 2, paddingTop: 2}}>
                  <button className="btn btn-ghost" style={{padding: 2, minHeight: 0, opacity: i === 0 ? 0.3 : 1}}
                    disabled={i === 0} onClick={() => moveStep(p.id, -1)}>
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m6 15 6-6 6 6"/></svg>
                  </button>
                  <button className="btn btn-ghost" style={{padding: 2, minHeight: 0, opacity: i === plan.length - 1 ? 0.3 : 1}}
                    disabled={i === plan.length - 1} onClick={() => moveStep(p.id, 1)}>
                    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m6 9 6 6 6-6"/></svg>
                  </button>
                </div>
                <div style={{
                  width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
                  background: badgeBg, color: 'white',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 12, fontWeight: 700,
                }}>{i + 1}</div>
                <div style={{flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 6}}>
                  <div style={{display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap'}}>
                    <select value={p.tool} onChange={e => updateStep(p.id, { tool: e.target.value })}
                      className="mono" style={{
                        fontSize: 12, fontWeight: 600, padding: '4px 6px', borderRadius: 6,
                        border: '1px solid var(--border)', background: 'white',
                      }}>
                      {['ldap-mcp', 'intune-mcp', 'epic-ops-mcp', 'comms-mcp', 'catalog-mcp', 'sccm-mcp'].map(t => <option key={t}>{t}</option>)}
                    </select>
                    <input value={p.action} onChange={e => updateStep(p.id, { action: e.target.value })}
                      style={{
                        flex: 1, minWidth: 140, fontSize: 14, fontWeight: 600,
                        padding: '4px 8px', borderRadius: 6, border: '1px solid var(--border)',
                      }}/>
                    <button onClick={() => deleteStep(p.id)}
                      style={{
                        width: 24, height: 24, borderRadius: 6, border: 'none',
                        background: 'var(--red-tint)', color: 'var(--nyu-red)',
                        cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
                      }} title="Remove step">
                      <Icon name="x" size={12} strokeWidth={2.5}/>
                    </button>
                  </div>
                  <input value={p.target} onChange={e => updateStep(p.id, { target: e.target.value })}
                    className="mono" style={{
                      fontSize: 12, padding: '4px 8px', borderRadius: 6,
                      border: '1px solid var(--border)', color: 'var(--nyu-text)',
                      background: 'white',
                    }}/>
                  {(p.edited || p.added) && (
                    <div style={{fontSize: 10, color: 'var(--nyu-purple)', fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase'}}>
                      {p.added ? 'Added by you' : 'Modified by you'}
                    </div>
                  )}
                </div>
              </div>
            );
          }

          // ----- NORMAL (idle / executing / success) ROW -----
          return (
            <div key={p.id || i} style={{
              display: 'flex', gap: 12, padding: '12px 14px', marginBottom: 8,
              background: bg, border, borderRadius: 10,
              transition: 'background 300ms, border-color 300ms',
              opacity: status === 'queued' ? 0.55 : 1,
            }}>
              <div style={{
                width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
                background: badgeBg, color: 'white',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 12, fontWeight: 700,
                transition: 'background 300ms',
              }}>
                {isRunning
                  ? <span className="ring-spin" style={{width: 12, height: 12, borderWidth: 1.5, borderColor: 'white', borderTopColor: 'transparent'}}></span>
                  : isDone
                    ? <Icon name="check" size={13} strokeWidth={3} color="white"/>
                    : (i + 1)}
              </div>
              <div style={{flex: 1, minWidth: 0}}>
                <div style={{display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4, flexWrap: 'wrap'}}>
                  <span className="mono" style={{fontSize: 12, fontWeight: 600}}>{p.tool}</span>
                  <span style={{fontSize: 14, fontWeight: 600}}>{p.action}</span>
                  {p.edited && phase === 'idle' && <span className="chip chip-purple" style={{fontSize: 10}}>Edited</span>}
                  {p.added && phase === 'idle' && <span className="chip chip-mint" style={{fontSize: 10}}>Added</span>}
                  {isDestructive && !isDone && <span className="chip chip-red">Destructive · irreversible</span>}
                  {p.reversible && !isDone && !p.edited && !p.added && <span className="chip chip-outline" style={{fontSize: 10}}>Reversible</span>}
                  {isDone && state.duration && <span className="mono" style={{fontSize: 11, color: '#006B51', fontWeight: 600, marginLeft: 'auto'}}>{state.duration}ms ✓</span>}
                  {isRunning && <span className="mono" style={{fontSize: 11, color: 'var(--nyu-purple)', fontWeight: 600, marginLeft: 'auto'}}>streaming…</span>}
                </div>
                <div className="mono" style={{fontSize: 12, color: 'var(--nyu-text)'}}>{p.target}</div>
                {(isRunning || isDone) && (
                  <div className="mono" style={{
                    fontSize: 11, marginTop: 6, padding: '6px 8px', borderRadius: 6,
                    background: isDone ? 'rgba(255,255,255,0.7)' : 'rgba(255,255,255,0.8)',
                    color: isDone ? '#006B51' : 'var(--nyu-purple)',
                    border: `1px solid ${isDone ? '#8FD9B8' : 'var(--nyu-purple)'}`,
                    lineHeight: 1.4,
                  }}>
                    {isDone ? '→ ' : '… '}
                    {isRunning ? <TypingLine text={p.output}/> : state.output}
                  </div>
                )}
              </div>
            </div>
          );
        })}
        {phase === 'editing' && (
          <>
            <button
              onClick={addStep}
              style={{
                width: '100%', padding: 12, marginBottom: 12, marginTop: 4,
                border: '1px dashed var(--nyu-purple)', borderRadius: 10,
                background: 'transparent', color: 'var(--nyu-purple)',
                fontSize: 13, fontWeight: 600, cursor: 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
              }}>
              <Icon name="plus" size={14}/> Add step
            </button>

            {/* Verification toast */}
            {verifyState === 'checking' && (
              <div style={{
                padding: '10px 14px', borderRadius: 10, background: 'var(--nyu-gray-50)',
                border: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 10,
                fontSize: 12, color: 'var(--nyu-text)',
              }}>
                <span className="ring-spin" style={{width: 12, height: 12, borderWidth: 1.5, borderColor: 'var(--nyu-purple)', borderTopColor: 'transparent'}}></span>
                Re-checking plan against policy POL-0019…
              </div>
            )}
            {verifyState?.status === 'valid' && (
              <div style={{
                padding: '10px 14px', borderRadius: 10, background: 'var(--mint-tint)',
                border: '1px solid #8FD9B8', display: 'flex', alignItems: 'center', gap: 10,
                fontSize: 12, color: '#006B51', fontWeight: 600,
              }}>
                <Icon name="check" size={12} color="#006B51" strokeWidth={3}/>
                Plan valid · confidence {verifyState.confidence} · ready for approval
              </div>
            )}
            {verifyState?.status === 'blocked' && (
              <div style={{
                padding: '10px 14px', borderRadius: 10, background: 'var(--red-tint)',
                border: '1px solid #FFB7C8', display: 'flex', alignItems: 'flex-start', gap: 10,
                fontSize: 12, color: '#B00045',
              }}>
                <Icon name="alert" size={14} color="#B00045" strokeWidth={2}/>
                <div>
                  <div style={{fontWeight: 700, marginBottom: 2}}>Plan blocked</div>
                  <div>{verifyState.reason}</div>
                </div>
              </div>
            )}
            {verifyState?.status === 'needs_second' && (
              <div style={{
                padding: '12px 14px', borderRadius: 10, background: '#FFF4E5',
                border: '1px solid #F3C78B', fontSize: 12, color: '#7A4A00',
              }}>
                <div style={{display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 10}}>
                  <div style={{marginTop: 1}}>
                    <Icon name="alert" size={14} color="#B06A00" strokeWidth={2}/>
                  </div>
                  <div style={{flex: 1}}>
                    <div style={{fontWeight: 700, marginBottom: 2, color: '#7A4A00'}}>Second approver required</div>
                    <div style={{marginBottom: 4}}>{verifyState.reason}</div>
                    <div style={{fontSize: 11, color: '#9A6B1A', fontFamily: 'var(--font-mono)'}}>{verifyState.rule}</div>
                  </div>
                </div>
                <div style={{fontSize: 11, fontWeight: 600, color: '#7A4A00', marginBottom: 6, textTransform: 'uppercase', letterSpacing: '0.04em'}}>Eligible approvers</div>
                <div style={{display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 10}}>
                  {verifyState.eligibleApprovers.map((name, i) => (
                    <div key={i} style={{display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: '#7A4A00'}}>
                      <div style={{width: 18, height: 18, borderRadius: '50%', background: '#F3C78B', color: '#7A4A00', fontSize: 10, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
                        {name.split(' ').map(w => w[0]).slice(0, 2).join('')}
                      </div>
                      {name}
                    </div>
                  ))}
                </div>
                <button
                  className="btn btn-sm"
                  onClick={() => setPhase('awaiting_second')}
                  style={{background: '#B06A00', color: 'white', border: 'none', width: '100%', justifyContent: 'center'}}>
                  <Icon name="user" size={12}/> Request second approver
                </button>
              </div>
            )}
          </>
        )}
        {phase === 'success' && (
          <div style={{marginTop: 14, padding: 14, background: 'var(--mint-tint)', border: '1px solid #8FD9B8', borderRadius: 10, display: 'flex', alignItems: 'center', gap: 12}}>
            <div style={{width: 32, height: 32, borderRadius: '50%', background: '#006B51', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
              <Icon name="check" size={16} strokeWidth={3} color="white"/>
            </div>
            <div style={{flex: 1}}>
              <div style={{fontSize: 13, fontWeight: 700, color: '#006B51'}}>Plan complete · user and SOC notified</div>
              <div style={{fontSize: 12, color: '#006B51', marginTop: 2}}>
                Activity entries posted to ticket · audit record AUD-{ticket.id}-{Date.now().toString().slice(-5)} signed · loaner pickup SMS sent
              </div>
            </div>
          </div>
        )}
      </div>

      <div style={{padding: '14px 24px', borderTop: '1px solid var(--border)', display: 'flex', gap: 10, alignItems: 'center'}}>
        {phase === 'idle' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1}}>
              <Icon name="info" size={12} color="var(--nyu-text)"/> Approval logged with your SSO identity · hold to confirm
            </div>
            <button className="btn btn-secondary btn-sm" onClick={() => setPhase('editing')}>
              <Icon name="settings" size={12}/> Edit plan
            </button>
            <button className="btn btn-danger btn-sm" onClick={() => setPhase('rejecting')}>Reject</button>
            <button
              className="btn btn-primary btn-sm"
              onMouseDown={startHold} onMouseUp={cancelHold} onMouseLeave={cancelHold}
              onTouchStart={startHold} onTouchEnd={cancelHold}
              style={{position: 'relative', overflow: 'hidden', minWidth: 180}}>
              <span style={{
                position: 'absolute', inset: 0, background: 'rgba(255,255,255,0.25)',
                width: `${holdProgress * 100}%`, transition: 'width 60ms linear',
              }}></span>
              <span style={{position: 'relative', display: 'flex', alignItems: 'center', gap: 6}}>
                {holdProgress > 0 ? `Hold to confirm… ${Math.round(holdProgress * 100)}%` : 'Hold to approve & execute'}
                <Icon name="arrow" size={12}/>
              </span>
            </button>
          </>
        )}
        {phase === 'editing' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1}}>
              <Icon name="sparkle" size={12} color="var(--nyu-purple)"/>
              {' '}{editedCount + Math.max(0, basePlan.length - plan.filter(s => s.originalIdx !== undefined).length)} change{(editedCount + Math.max(0, basePlan.length - plan.filter(s => s.originalIdx !== undefined).length)) !== 1 ? 's' : ''} · changes re-verify on save
            </div>
            <button className="btn btn-ghost btn-sm" onClick={() => {
              setPlan(basePlan.map((p, i) => ({ ...p, id: `s-${i}`, edited: false, originalIdx: i })));
              setPhase('idle');
              setVerifyState(null);
            }}>Cancel</button>
            <button className="btn btn-secondary btn-sm" onClick={reVerify} disabled={verifyState === 'checking'}>
              {verifyState === 'checking' ? 'Checking…' : 'Re-verify plan'}
            </button>
            <button
              className="btn btn-primary btn-sm"
              disabled={verifyState?.status === 'blocked' || verifyState === 'checking'}
              onClick={() => {
                // if not yet verified, verify first then return to idle; if valid, go to idle
                if (!verifyState) {
                  reVerify();
                  setTimeout(() => setPhase('idle'), 950);
                } else {
                  setPhase('idle');
                }
              }}>
              Save &amp; return
              <Icon name="arrow" size={12}/>
            </button>
          </>
        )}
        {phase === 'executing' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1, display: 'flex', alignItems: 'center', gap: 8}}>
              <span className="ring-spin" style={{width: 10, height: 10, borderWidth: 1.5, borderColor: 'var(--nyu-purple)', borderTopColor: 'transparent'}}></span>
              <span>Executing {plan[currentStep]?.tool || ''}.{plan[currentStep]?.action || ''}…</span>
            </div>
            <div style={{
              minWidth: 180, padding: '6px 14px', borderRadius: 8,
              background: 'var(--purple-tint)', color: 'var(--nyu-deep-purple)',
              fontSize: 12, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 8,
              border: '1px solid var(--nyu-purple)',
            }}>
              <div style={{flex: 1, height: 4, background: 'rgba(87,43,182,0.2)', borderRadius: 2, overflow: 'hidden'}}>
                <div style={{
                  height: '100%', background: 'var(--nyu-purple)',
                  width: `${(totalDone / plan.length) * 100}%`,
                  transition: 'width 400ms ease',
                }}></div>
              </div>
              <span className="mono">{totalDone}/{plan.length}</span>
            </div>
          </>
        )}
        {phase === 'rejecting' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1}}>
              <Icon name="info" size={12} color="var(--nyu-text)"/> {rejectBranch ? 'Reason becomes a training signal · logged to audit trail' : 'Pick a branch to continue'}
            </div>
            <button className="btn btn-ghost btn-sm" onClick={() => { setPhase('idle'); setRejectBranch(null); setRejectReason(''); }}>Back</button>
            <button
              className="btn btn-danger btn-sm"
              disabled={!rejectBranch}
              onClick={() => setPhase('rejected')}
              style={{opacity: rejectBranch ? 1 : 0.5, minWidth: 160}}>
              {rejectBranch === 'replan' && 'Send back to Violet'}
              {rejectBranch === 'escalate' && 'Escalate'}
              {rejectBranch === 'close' && 'Close ticket'}
              {!rejectBranch && 'Confirm reject'}
              <Icon name="arrow" size={12}/>
            </button>
          </>
        )}
        {phase === 'rejected' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1, fontWeight: 600}}>
              <Icon name="check" size={12} color="var(--nyu-purple)" strokeWidth={3}/> Logged at {new Date().toLocaleTimeString('en-US', {hour12: false})} · ticket status updated
            </div>
            <button className="btn btn-primary btn-sm" onClick={onClose}>Done</button>
          </>
        )}
        {phase === 'awaiting_second' && (
          <>
            <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1, display: 'flex', alignItems: 'center', gap: 6}}>
              <Icon name="clock" size={12} color="#B06A00"/>
              <span>Plan frozen · waiting on 1 approver</span>
            </div>
            <button className="btn btn-ghost btn-sm" onClick={() => setPhase('idle')}>Cancel request</button>
            <button
              className="btn btn-sm"
              onClick={() => {
                // Simulate Dana approving → kick off execution
                setPhase('executing');
                executionStartRef.current = Date.now();
              }}
              style={{background: '#B06A00', color: 'white', border: 'none'}}
              title="Demo shortcut: simulate Dana's approval">
              ⚡ Simulate approval
            </button>
          </>
        )}
        {phase === 'success' && (
          <>
            <div style={{fontSize: 12, color: '#006B51', flex: 1, display: 'flex', alignItems: 'center', gap: 8, fontWeight: 600}}>
              <Icon name="check" size={12} color="#006B51" strokeWidth={3}/>
              All {plan.length} steps verified · {(totalDuration/1000).toFixed(1)}s total · TKT-{ticket.id.slice(-5)} resolved
            </div>
            <button className="btn btn-secondary btn-sm" onClick={onClose}>View audit record</button>
            <button className="btn btn-primary btn-sm" onClick={onClose}>Done</button>
          </>
        )}
      </div>
    </Modal>
  );
};

// ---------- Handoff packet modal ----------
const HandoffPacketModal = ({ open, onClose, ticket }) => {
  if (!ticket) return null;
  const user = USERS[ticket.user];
  return (
    <Modal open={open} onClose={onClose} width={780}>
      <div style={{padding: '18px 24px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 14}}>
        <div style={{width: 36, height: 36, borderRadius: 10, background: 'var(--purple-tint)', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
          <Icon name="file" size={18} color="var(--nyu-purple)"/>
        </div>
        <div>
          <div style={{fontSize: 16, fontWeight: 700, color: 'var(--nyu-deep-purple)'}}>Handoff packet</div>
          <div style={{fontSize: 12, color: 'var(--nyu-text)', marginTop: 2}}>What the human lead receives when Violet escalates. Generated from {ticket.id}.</div>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={onClose} style={{marginLeft: 'auto'}}><Icon name="x" size={14}/></button>
      </div>

      <div className="scroll" style={{flex: 1, overflowY: 'auto', padding: '20px 24px'}}>
        <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 20}}>
          <div className="card" style={{padding: 14}}>
            <div className="hint">Requester</div>
            <div style={{fontSize: 14, fontWeight: 600, marginTop: 4}}>{user?.name}, {user?.title}</div>
            <div style={{fontSize: 12, color: 'var(--nyu-text)'}}>{user?.dept} · {user?.location}</div>
          </div>
          <div className="card" style={{padding: 14}}>
            <div className="hint">Suggested approver</div>
            <div style={{fontSize: 14, fontWeight: 600, marginTop: 4}}>Sarah Kim, MD</div>
            <div style={{fontSize: 12, color: 'var(--nyu-text)'}}>On-call EM lead · available now</div>
          </div>
        </div>

        <div style={{fontSize: 13, fontWeight: 700, marginBottom: 8}}>Why Violet handed this off</div>
        <div style={{padding: 14, background: 'var(--nyu-gray-50)', borderRadius: 10, fontSize: 13, lineHeight: 1.5, marginBottom: 20}}>
          Confidence <span className="mono" style={{fontWeight: 700}}>0.62</span> on the entitlement-grant path. The specialist's allow-list does not include <span className="mono">epic-ops-mcp.grant</span>, which is a clinical policy boundary — grants need a named approver who can see the patient panel. I've prepared the ACL diff below so approval is one click.
        </div>

        <div style={{fontSize: 13, fontWeight: 700, marginBottom: 8}}>Proposed ACL diff</div>
        <div style={{padding: 14, background: '#1A0330', borderRadius: 10, fontFamily: 'var(--font-mono)', fontSize: 12, color: '#E9DAF5', lineHeight: 1.6, marginBottom: 20}}>
          <div>epic.inbasket.acl[rpatel]:</div>
          <div style={{color: '#00DCA5'}}>+ coverage: [sliang]</div>
          <div style={{color: '#00DCA5'}}>+ coverage_window: 2026-04-21T00:00 → 2026-05-01T23:59</div>
          <div style={{color: '#00DCA5'}}>+ scope: read, reply, route</div>
          <div style={{color: '#FF0057'}}>- scope excludes: sign_orders, release_results</div>
        </div>

        <div style={{fontSize: 13, fontWeight: 700, marginBottom: 8}}>What I tried first</div>
        <ul style={{fontSize: 13, color: 'var(--nyu-text)', lineHeight: 1.6, paddingLeft: 18, margin: 0}}>
          <li>Read-only entitlement inspection via <span className="mono">epic-ops-mcp</span> — confirmed both users exist and the requestor has grantable coverage authority.</li>
          <li>Matched against policy POL-0019 (EPIC entitlement — who can approve what) — grants outside attending-to-attending scope require HITL.</li>
          <li>Searched for a pre-approved templated grant — none matched this time-boxed pattern.</li>
        </ul>
      </div>

      <div style={{padding: '14px 24px', borderTop: '1px solid var(--border)', display: 'flex', gap: 10, alignItems: 'center'}}>
        <div style={{fontSize: 12, color: 'var(--nyu-text)', flex: 1}}>
          <Icon name="clock" size={12}/> ETA offered to requester: ≤60 min
        </div>
        <button className="btn btn-secondary btn-sm"><Icon name="copy" size={12}/> Copy packet</button>
        <button className="btn btn-primary btn-sm"><Icon name="check" size={12}/> Accept &amp; take ownership</button>
      </div>
    </Modal>
  );
};

// ---------- Audit export preview (PDF-style) ----------
const AuditExportModal = ({ open, onClose, ticket }) => {
  if (!ticket) return null;
  const user = USERS[ticket.user];
  return (
    <Modal open={open} onClose={onClose} width={760}>
      <div style={{padding: '14px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 12}}>
        <Icon name="file" size={16} color="var(--nyu-purple)"/>
        <div style={{flex: 1}}>
          <div style={{fontSize: 14, fontWeight: 700}}>Audit export preview</div>
          <div style={{fontSize: 11, color: 'var(--nyu-text)'}}>{ticket.id} · signed · {ticket.trace.length} actions</div>
        </div>
        <button className="btn btn-secondary btn-sm"><Icon name="download" size={12}/> Download PDF</button>
        <button className="btn btn-ghost btn-sm" onClick={onClose}><Icon name="x" size={14}/></button>
      </div>

      <div className="scroll" style={{flex: 1, overflowY: 'auto', padding: '28px 44px', background: 'var(--nyu-gray-50)'}}>
        {/* Page */}
        <div style={{background: 'white', padding: 40, boxShadow: '0 2px 16px rgba(0,0,0,0.06)', fontFamily: 'var(--font-body)', fontSize: 11, color: 'var(--nyu-black)', lineHeight: 1.5}}>
          <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24, paddingBottom: 16, borderBottom: '2px solid var(--nyu-purple)'}}>
            <div>
              <div style={{fontFamily: 'var(--font-head)', fontSize: 18, fontWeight: 800, color: 'var(--nyu-deep-purple)'}}>NYU Langone Health</div>
              <div style={{fontSize: 10, color: 'var(--nyu-text)', letterSpacing: '0.06em'}}>DESKTOP SUPPORT · VIOLET AUDIT TRAIL</div>
            </div>
            <div style={{textAlign: 'right'}}>
              <div className="mono" style={{fontSize: 12, fontWeight: 700}}>{ticket.id}</div>
              <div className="mono" style={{fontSize: 10, color: 'var(--nyu-text)'}}>Exported: 2026-04-20 14:22:14 UTC</div>
            </div>
          </div>

          <div style={{display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10, marginBottom: 20, fontSize: 10}}>
            <div><div style={{color: 'var(--nyu-text)', textTransform: 'uppercase', fontWeight: 600, letterSpacing: '0.06em', marginBottom: 2}}>Requester</div><div style={{fontWeight: 600}}>{user?.name}</div></div>
            <div><div style={{color: 'var(--nyu-text)', textTransform: 'uppercase', fontWeight: 600, letterSpacing: '0.06em', marginBottom: 2}}>Category</div><div style={{fontWeight: 600}}>{ticket.category}</div></div>
            <div><div style={{color: 'var(--nyu-text)', textTransform: 'uppercase', fontWeight: 600, letterSpacing: '0.06em', marginBottom: 2}}>Disposition</div><div style={{fontWeight: 600}}>{ticket.disposition}</div></div>
            <div><div style={{color: 'var(--nyu-text)', textTransform: 'uppercase', fontWeight: 600, letterSpacing: '0.06em', marginBottom: 2}}>Confidence</div><div style={{fontWeight: 600}} className="mono">{ticket.confidence.toFixed(2)}</div></div>
          </div>

          <div style={{fontFamily: 'var(--font-head)', fontSize: 12, fontWeight: 700, marginBottom: 8, letterSpacing: '-0.01em'}}>Action log</div>
          <table style={{width: '100%', borderCollapse: 'collapse', fontSize: 10}}>
            <thead>
              <tr style={{borderBottom: '1px solid var(--border)'}}>
                <th style={{textAlign: 'left', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600, width: 70}}>Time</th>
                <th style={{textAlign: 'left', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600, width: 78}}>Agent</th>
                <th style={{textAlign: 'left', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600, width: 96}}>Tool</th>
                <th style={{textAlign: 'left', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600}}>Action</th>
                <th style={{textAlign: 'right', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600, width: 50}}>Dur</th>
                <th style={{textAlign: 'left', padding: '6px 4px', color: 'var(--nyu-text)', fontWeight: 600, width: 60}}>Outcome</th>
              </tr>
            </thead>
            <tbody>
              {ticket.trace.map((s, i) => (
                <tr key={i} style={{borderBottom: '1px solid var(--nyu-gray-100)'}}>
                  <td className="mono" style={{padding: '6px 4px'}}>{s.t || '—'}</td>
                  <td style={{padding: '6px 4px', fontWeight: 600}}>{s.agent}</td>
                  <td className="mono" style={{padding: '6px 4px'}}>{s.tool || '—'}</td>
                  <td style={{padding: '6px 4px'}}>{s.action}</td>
                  <td className="mono" style={{padding: '6px 4px', textAlign: 'right'}}>{s.duration ? `${s.duration}ms` : '—'}</td>
                  <td style={{padding: '6px 4px', color: s.status === 'done' ? '#006B51' : s.status === 'hitl' ? '#8A4E00' : 'var(--nyu-text)', fontWeight: 600, textTransform: 'uppercase', fontSize: 9, letterSpacing: '0.04em'}}>{s.status}</td>
                </tr>
              ))}
            </tbody>
          </table>

          <div style={{marginTop: 20, paddingTop: 12, borderTop: '1px solid var(--border)', fontSize: 9, color: 'var(--nyu-text)', display: 'flex', justifyContent: 'space-between'}}>
            <div>SHA-256: 7a3f…c219 · Signed by UltraViolet audit ledger</div>
            <div>Page 1 / 1</div>
          </div>
        </div>
      </div>
    </Modal>
  );
};

// ---------- PHI redaction viewer (side-by-side) ----------
const PhiRedactionModal = ({ open, onClose, ticket }) => {
  if (!ticket) return null;
  const userMsg = ticket.userMessages?.find(m => m.from === 'user');
  const redacted = userMsg?.redactedBody || userMsg?.body;
  const tokens = redacted?.match(/\[PHI-REDACTED:[^\]]+\]|\[TRANSIT\]/g) || [];

  // Render redacted text with highlighted tokens
  const renderRedacted = (text) => {
    const parts = text.split(/(\[PHI-REDACTED:[^\]]+\]|\[TRANSIT\])/g);
    return parts.map((p, i) => {
      if (p.startsWith('[PHI')) {
        const content = p.replace(/\[PHI-REDACTED:|\]/g, '');
        return <span key={i} style={{background: 'var(--nyu-deep-purple)', color: 'var(--purple-tint-2)', padding: '1px 6px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: 11}}>▪▪▪ PHI ▪▪▪</span>;
      }
      if (p === '[TRANSIT]') {
        return <span key={i} style={{background: 'var(--nyu-gray-100)', padding: '1px 6px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--nyu-text)'}}>{'{transit}'}</span>;
      }
      return p;
    });
  };

  return (
    <Modal open={open} onClose={onClose} width={820}>
      <div style={{padding: '14px 20px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 12}}>
        <div style={{width: 32, height: 32, borderRadius: 8, background: 'var(--red-tint)', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>
          <Icon name="shield" size={16} color="#B00045"/>
        </div>
        <div style={{flex: 1}}>
          <div style={{fontSize: 15, fontWeight: 700}}>PHI redaction · what leaves the HIPAA boundary</div>
          <div style={{fontSize: 11, color: 'var(--nyu-text)'}}>Left: original inside the boundary. Right: redacted form sent to any non-BAA model.</div>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={onClose}><Icon name="x" size={14}/></button>
      </div>

      <div style={{padding: 20, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16}}>
        <div style={{border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden'}}>
          <div style={{padding: '8px 12px', background: 'var(--nyu-gray-50)', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 8}}>
            <Icon name="eye" size={13} color="var(--nyu-purple)"/>
            <span style={{fontSize: 12, fontWeight: 600}}>Original · inside NYULH boundary</span>
          </div>
          <div style={{padding: 14, fontSize: 13, lineHeight: 1.55}}>{userMsg?.body}</div>
        </div>
        <div style={{border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden'}}>
          <div style={{padding: '8px 12px', background: 'var(--nyu-deep-purple)', color: 'white', borderBottom: '1px solid var(--nyu-deep-purple)', display: 'flex', alignItems: 'center', gap: 8}}>
            <Icon name="eyeOff" size={13} color="white"/>
            <span style={{fontSize: 12, fontWeight: 600}}>Redacted · outbound to non-BAA models</span>
          </div>
          <div style={{padding: 14, fontSize: 13, lineHeight: 1.55}}>{renderRedacted(redacted)}</div>
        </div>
      </div>

      <div style={{padding: '12px 20px', borderTop: '1px solid var(--border)', background: 'var(--nyu-gray-50)'}}>
        <div style={{fontSize: 12, color: 'var(--nyu-text)', lineHeight: 1.55}}>
          <b>Detected {tokens.length} redactable tokens.</b> Classifier: <span className="mono">phi-classifier v2.1</span> · Confidence {0.94}. Policy: strings in this class are hashed in audit, redacted in outbound prompts, never logged in plaintext to non-boundary systems.
        </div>
      </div>
    </Modal>
  );
};

// ---------- Tweaks panel ----------
const TweaksPanel = ({ open, tweaks, setTweaks }) => {
  if (!open) return null;
  return (
    <div style={{
      position: 'fixed', bottom: 20, right: 20, width: 300, zIndex: 50,
      background: 'white', borderRadius: 14, boxShadow: 'var(--shadow-lg)',
      border: '1px solid var(--border)', overflow: 'hidden',
    }}>
      <div style={{padding: '12px 16px', background: 'var(--nyu-deep-purple)', color: 'white', display: 'flex', alignItems: 'center', gap: 8}}>
        <Icon name="settings" size={14} color="white"/>
        <span style={{fontSize: 13, fontWeight: 700, letterSpacing: '-0.01em'}}>Tweaks</span>
      </div>
      <div style={{padding: 16, display: 'flex', flexDirection: 'column', gap: 16}}>
        <div>
          <div className="hint" style={{marginBottom: 8}}>Confidence gate</div>
          <input type="range" min="0.5" max="0.99" step="0.01" value={tweaks.gate}
            onChange={e => setTweaks(t => ({...t, gate: parseFloat(e.target.value)}))}
            style={{width: '100%', accentColor: 'var(--nyu-purple)'}}
          />
          <div style={{display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--nyu-text)', marginTop: 2}}>
            <span>0.50</span>
            <span className="mono" style={{fontWeight: 700, color: 'var(--nyu-purple)'}}>{tweaks.gate.toFixed(2)}</span>
            <span>0.99</span>
          </div>
        </div>
        <div>
          <div className="hint" style={{marginBottom: 8}}>Density</div>
          <div style={{display: 'flex', gap: 6}}>
            {['comfortable', 'compact'].map(d => (
              <button key={d} onClick={() => setTweaks(t => ({...t, density: d}))}
                className={`btn btn-xs ${tweaks.density === d ? 'btn-primary' : 'btn-secondary'}`}
                style={{flex: 1, textTransform: 'capitalize'}}>
                {d}
              </button>
            ))}
          </div>
        </div>
        <div>
          <div className="hint" style={{marginBottom: 8}}>Demo speed</div>
          <div style={{display: 'flex', gap: 6}}>
            {['1×', '2×', '4×'].map(s => (
              <button key={s} onClick={() => setTweaks(t => ({...t, speed: s}))}
                className={`btn btn-xs ${tweaks.speed === s ? 'btn-primary' : 'btn-secondary'}`}
                style={{flex: 1}}>
                {s}
              </button>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { Modal, CommandPalette, HitlApprovalModal, HandoffPacketModal, AuditExportModal, PhiRedactionModal, TweaksPanel });
