// Cabinet tab — accounts (live + demo), switch account, transfer, deposit, withdraw, reset.
// Matches the app's bottom-sheet language (slideUp, grab handle, success phase).

const INITIAL_ACCOUNTS = [
  { id: 'a1', login: '50128817', type: 'live', plan: 'Standard',   currency: 'USD', leverage: '1:400', server: 'CFD-Live 04', balance: 10000.00, trading: true },
  { id: 'a2', login: '50213394', type: 'live', plan: 'Raw Spread', currency: 'USD', leverage: '1:200', server: 'CFD-Live 07', balance: 2480.50 },
  { id: 'a3', login: '88102245', type: 'demo', plan: 'Standard',   currency: 'USD', leverage: '1:500', server: 'CFD-Demo 02', balance: 98740.00, resetTo: 100000 },
  { id: 'a4', login: '88102310', type: 'demo', plan: 'Swing',      currency: 'USD', leverage: '1:30',  server: 'CFD-Demo 02', balance: 41260.00, resetTo: 50000 },
];

const DEMO_COLOR = '#E0A21A';
function acctColor(acct, theme) { return acct.type === 'live' ? theme.buy : DEMO_COLOR; }
function curSym(c) { return c === 'EUR' ? '€' : c === 'GBP' ? '£' : '$'; }
function fmtBal(n, c = 'USD') {
  return curSym(c) + Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

// labelKey/subKey, not text: this array is built when the script evaluates,
// which is before initI18n() resolves, so a t() call here would bake in a miss.
// The strings are resolved in MethodGrid's render instead.
const PAY_METHODS = [
  { id: 'card',    labelKey: 'cabinet.method.card',    subKey: 'cabinet.method.sub.instant',   icon: (c) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none"><rect x="3" y="6" width="18" height="12" rx="2.5" stroke={c} strokeWidth="1.8"/><path d="M3 10h18" stroke={c} strokeWidth="1.8"/><path d="M6.5 14.5h3" stroke={c} strokeWidth="1.8" strokeLinecap="round"/></svg> },
  { id: 'bank',    labelKey: 'cabinet.method.bank',    subKey: 'cabinet.method.sub.days1to3',  icon: (c) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M4 10l8-5 8 5M5 10v7M10 10v7M14 10v7M19 10v7M3 20h18" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"/></svg> },
  { id: 'crypto',  labelKey: 'cabinet.method.crypto',  subKey: 'cabinet.method.sub.min10',     icon: (c) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="8.5" stroke={c} strokeWidth="1.7"/><path d="M10 8v8M10 8h3a2 2 0 010 4h-3m0 0h3.5a2 2 0 010 4H10M11.5 6.5v1.5M11.5 16v1.5" stroke={c} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg> },
  { id: 'ewallet', labelKey: 'cabinet.method.ewallet', subKey: 'cabinet.method.sub.instant',   icon: (c) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none"><rect x="3" y="6" width="18" height="12" rx="3" stroke={c} strokeWidth="1.8"/><path d="M16 11.5h3.5v3H16a1.5 1.5 0 010-3z" stroke={c} strokeWidth="1.6"/></svg> },
];

// ————————————————————————— shared bits —————————————————————————

function AcctBadge({ acct, theme, size = 38 }) {
  const col = acctColor(acct, theme);
  return (
    <div style={{
      width: size, height: size, borderRadius: 11, flexShrink: 0,
      background: `${col}1F`, border: `1.5px solid ${col}`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: col, fontWeight: 800, fontSize: size * 0.42, letterSpacing: 0.5,
    }}>{acct.type === 'live' ? t('accounts.badge.live') : t('accounts.badge.demo')}</div>
  );
}

function TypePill({ acct, theme }) {
  const col = acctColor(acct, theme);
  return (
    <span style={{
      padding: '1.5px 6px', borderRadius: 5, fontSize: 10, fontWeight: 700,
      textTransform: 'uppercase', letterSpacing: 0.4,
      color: col, background: `${col}22`,
    }}>{acct.type === 'live' ? t('common.accountType.live') : t('common.accountType.demo')}</span>
  );
}

function AcctMini({ acct, theme }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
      <AcctBadge acct={acct} theme={theme} size={34} />
      <div style={{ minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ fontSize: 14, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2 }}>#{acct.login}</span>
          <TypePill acct={acct} theme={theme} />
        </div>
        <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 1 }}>{acct.plan} · {fmtBal(acct.balance, acct.currency)}</div>
      </div>
    </div>
  );
}

function SheetShell({ theme, onClose, children, maxH = '94%' }) {
  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'flex-end', animation: 'fadeIn 180ms ease-out',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', background: theme.bgElevated,
        borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: '12px 16px 28px',
        borderTop: `0.5px solid ${theme.border}`, maxHeight: maxH, overflowY: 'auto',
        animation: 'slideUp 280ms cubic-bezier(0.2,0.9,0.3,1.05)',
      }}>
        <div style={{ width: 36, height: 5, borderRadius: 3, background: theme.borderStrong, margin: '0 auto 14px' }} />
        {children}
      </div>
    </div>
  );
}

function SheetHeader({ theme, title, onClose }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
      <div style={{ flex: 1, fontSize: 18, fontWeight: 700, color: theme.text, letterSpacing: -0.3 }}>{title}</div>
      <div onClick={onClose} style={{ width: 32, height: 32, borderRadius: 16, background: theme.bgSubtle, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0 }}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M18 6L6 18M6 6l12 12" stroke={theme.textSec} strokeWidth="2.2" strokeLinecap="round"/></svg>
      </div>
    </div>
  );
}

function SheetSuccess({ theme, title, sub }) {
  return (
    <div style={{ padding: '28px 0 22px', textAlign: 'center' }}>
      <div style={{ width: 56, height: 56, borderRadius: 28, background: theme.buyBg, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' }}>
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none"><path d="M5 12l5 5L20 7" stroke={theme.buy} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </div>
      <div style={{ fontSize: 18, fontWeight: 700, color: theme.text }}>{title}</div>
      {sub && <div style={{ fontSize: 13, color: theme.textSec, marginTop: 4 }}>{sub}</div>}
    </div>
  );
}

function AmountField({ value, setValue, currency, theme, color }) {
  const [editing, setEditing] = React.useState(false);
  const [text, setText] = React.useState('');
  const sym = curSym(currency);
  const col = color || theme.text;
  if (editing) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <span style={{ fontSize: 30, fontWeight: 700, color: col, marginInlineEnd: 1 }}>{sym}</span>
        <input
          autoFocus inputMode="decimal" value={text}
          onChange={e => setText(e.target.value.replace(/[^0-9.]/g, ''))}
          onBlur={() => { let n = parseFloat(text); if (isNaN(n)) n = 0; setValue(Math.round(n * 100) / 100); setEditing(false); }}
          onKeyDown={e => { if (e.key === 'Enter') e.currentTarget.blur(); }}
          style={{ width: 200, textAlign: 'center', background: 'transparent', border: 'none', borderBottom: `2px solid ${col}`, outline: 'none', color: col, fontSize: 42, fontWeight: 700, letterSpacing: -1, fontVariantNumeric: 'tabular-nums', fontFamily: 'inherit', padding: '0 0 2px' }}
        />
      </div>
    );
  }
  return (
    <div onClick={() => { setText(value ? String(value) : ''); setEditing(true); }} style={{ cursor: 'text', textAlign: 'center', whiteSpace: 'nowrap' }}>
      <span style={{ fontSize: 42, fontWeight: 700, color: value ? col : theme.textTer, fontVariantNumeric: 'tabular-nums', letterSpacing: -1 }}>
        {sym}{value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
      </span>
    </div>
  );
}

function Chips({ chips, theme }) {
  return (
    <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
      {chips.map(ch => (
        <div key={ch.label} onClick={ch.onClick} style={{ flex: 1, padding: '9px 0', textAlign: 'center', borderRadius: 10, background: theme.bgSubtle, color: theme.text, fontSize: 13, fontWeight: 600, cursor: 'pointer', userSelect: 'none' }}>{ch.label}</div>
      ))}
    </div>
  );
}

function ConfirmButton({ theme, color, disabled, onClick, children }) {
  return (
    <div onClick={() => !disabled && onClick()} style={{
      padding: '16px', borderRadius: 14, textAlign: 'center', fontWeight: 700, fontSize: 16, letterSpacing: -0.2,
      background: disabled ? theme.bgSubtle : color, color: disabled ? theme.textTer : '#fff',
      cursor: disabled ? 'default' : 'pointer',
      boxShadow: disabled ? 'none' : `0 8px 20px ${color}55`, transition: 'background 140ms',
    }}>{children}</div>
  );
}

// Selectable account row with inline expanding picker
function AcctSelect({ label, value, options, theme, onSelect }) {
  const [open, setOpen] = React.useState(false);
  const single = options.length <= 1;
  return (
    <div style={{ borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}`, overflow: 'hidden' }}>
      <div onClick={() => !single && setOpen(o => !o)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 13px', cursor: single ? 'default' : 'pointer' }}>
        <span style={{ fontSize: 11, color: theme.textTer, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5, width: 38, flexShrink: 0 }}>{label}</span>
        <div style={{ flex: 1, minWidth: 0 }}><AcctMini acct={value} theme={theme} /></div>
        {!single && (
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 180ms', flexShrink: 0 }}>
            <path d="M6 9l6 6 6-6" stroke={theme.textSec} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        )}
      </div>
      {open && (
        <div style={{ borderTop: `0.5px solid ${theme.border}` }}>
          {options.map(a => (
            <div key={a.id} onClick={() => { onSelect(a); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 13px 10px 46px', cursor: 'pointer', borderTop: `0.5px solid ${theme.border}` }}>
              <div style={{ flex: 1, minWidth: 0 }}><AcctMini acct={a} theme={theme} /></div>
              {a.id === value.id && <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M5 12l5 5L20 7" stroke={theme.accent} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"/></svg>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function MethodGrid({ theme, value, onChange }) {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
      {PAY_METHODS.map(m => {
        const sel = value === m.id;
        const col = sel ? theme.accent : theme.textSec;
        return (
          <div key={m.id} onClick={() => onChange(m.id)} style={{
            display: 'flex', alignItems: 'center', gap: 10, padding: '11px 12px', borderRadius: 12, cursor: 'pointer',
            background: theme.bgCard, border: `1.5px solid ${sel ? theme.accent : theme.border}`,
          }}>
            {m.icon(col)}
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: theme.text }}>{t(m.labelKey)}</div>
              <div style={{ fontSize: 11, color: theme.textTer }}>{t(m.subKey)}</div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ————————————————————————— sheets —————————————————————————

function TransferSheet({ accounts, fromAcct, theme, onClose, onConfirm }) {
  const [from, setFrom] = React.useState(fromAcct);
  const eligible = (f) => accounts.filter(a => a.type === f.type && a.currency === f.currency && a.id !== f.id);
  const [to, setTo] = React.useState(() => eligible(fromAcct)[0] || null);
  const [amount, setAmount] = React.useState(0);
  const [phase, setPhase] = React.useState('form');

  const targets = to ? eligible(from) : [];
  React.useEffect(() => {
    // `list`, not `t` — t is the global translator now.
    const list = eligible(from);
    if (!list.find(x => x.id === (to && to.id))) setTo(list[0] || null);
  }, [from]);

  const avail = from.balance;
  const valid = to && amount > 0 && amount <= avail;
  const swap = () => { if (to) { const f = from; setFrom(to); setTo(f); } };
  const submit = () => {
    setPhase('confirming');
    setTimeout(() => { onConfirm(from.id, to.id, amount); setPhase('done'); setTimeout(onClose, 1000); }, 550);
  };

  return (
    <SheetShell theme={theme} onClose={onClose}>
      {phase === 'done' ? (
        <SheetSuccess theme={theme} title={t('cabinet.transfer.doneTitle')} sub={t('cabinet.summary.toAccount', { amount: fmtBal(amount, from.currency), login: to.login })} />
      ) : (
        <>
          <SheetHeader theme={theme} title={t('cabinet.transfer.title')} onClose={onClose} />
          <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
            <AcctSelect label={t('cabinet.field.from')} value={from} options={accounts} theme={theme} onSelect={setFrom} />
            <AcctSelect label={t('cabinet.field.to')} value={to || from} options={targets} theme={theme} onSelect={setTo} />
            <div onClick={swap} style={{ position: 'absolute', insetInlineEnd: 16, top: '50%', transform: 'translateY(-50%)', width: 32, height: 32, borderRadius: 16, background: theme.bgElevated, border: `0.5px solid ${theme.borderStrong}`, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', zIndex: 2 }}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M7 4v13M7 17l-3-3M7 17l3-3M17 20V7M17 7l-3 3M17 7l3 3" stroke={theme.text} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>
            </div>
          </div>

          {!to ? (
            // One key per account type rather than a <br/> between two halves:
            // the pair is a single sentence to translate, so the break rides in
            // the string and the div renders it with whiteSpace: pre-line.
            <div style={{ padding: '18px 0', textAlign: 'center', fontSize: 13, color: theme.textSec, whiteSpace: 'pre-line' }}>
              {from.type === 'live' ? t('cabinet.transfer.noTargetLive') : t('cabinet.transfer.noTargetDemo')}
            </div>
          ) : (
            <>
              <div style={{ padding: '6px 0 4px' }}>
                <AmountField value={amount} setValue={setAmount} currency={from.currency} theme={theme} />
                <div style={{ textAlign: 'center', fontSize: 12, color: amount > avail ? theme.sell : theme.textSec, marginTop: 8 }}>
                  {amount > avail ? t('cabinet.amount.exceedsBalance') : t('cabinet.amount.available', { amount: fmtBal(avail, from.currency) })}
                </div>
              </div>
              <Chips theme={theme} chips={[
                { label: t('cabinet.chip.plus', { n: 100 }), onClick: () => setAmount(v => Math.round((v + 100) * 100) / 100) },
                { label: t('cabinet.chip.plus', { n: 500 }), onClick: () => setAmount(v => Math.round((v + 500) * 100) / 100) },
                { label: t('cabinet.chip.plusThousands', { n: 1 }), onClick: () => setAmount(v => Math.round((v + 1000) * 100) / 100) },
                { label: t('cabinet.chip.max'), onClick: () => setAmount(avail) },
                { label: t('cabinet.chip.clear'), onClick: () => setAmount(0) },
              ]} />
              <div style={{ height: 18 }} />
              <ConfirmButton theme={theme} color={theme.accent} disabled={!valid || phase !== 'form'} onClick={submit}>
                {phase === 'confirming' ? t('cabinet.transfer.busy') : valid ? t('cabinet.transfer.cta', { amount: fmtBal(amount, from.currency) }) : t('cabinet.amount.enter')}
              </ConfirmButton>
            </>
          )}
        </>
      )}
    </SheetShell>
  );
}

function DepositSheet({ accounts, toAcct, theme, onClose, onConfirm }) {
  const virtual = toAcct.type === 'demo';
  const [acct, setAcct] = React.useState(toAcct);
  const sameType = accounts.filter(a => a.type === toAcct.type);
  const [amount, setAmount] = React.useState(0);
  const [method, setMethod] = React.useState('card');
  const [phase, setPhase] = React.useState('form');
  const accent = virtual ? DEMO_COLOR : theme.buy;
  const valid = amount > 0 && (virtual || !!method);
  const submit = () => {
    setPhase('confirming');
    setTimeout(() => { onConfirm(acct.id, amount); setPhase('done'); setTimeout(onClose, 1000); }, 550);
  };
  return (
    <SheetShell theme={theme} onClose={onClose}>
      {phase === 'done' ? (
        <SheetSuccess theme={theme} title={virtual ? t('cabinet.deposit.doneVirtual') : t('cabinet.deposit.doneReceived')} sub={t('cabinet.summary.toAccount', { amount: fmtBal(amount, acct.currency), login: acct.login })} />
      ) : (
        <>
          <SheetHeader theme={theme} title={virtual ? t('cabinet.deposit.titleVirtual') : t('cabinet.deposit.title')} onClose={onClose} />
          <div style={{ marginBottom: 16 }}>
            <AcctSelect label={t('cabinet.field.to')} value={acct} options={sameType} theme={theme} onSelect={setAcct} />
          </div>
          <div style={{ padding: '4px 0 2px' }}>
            <AmountField value={amount} setValue={setAmount} currency={acct.currency} theme={theme} color={accent} />
          </div>
          <Chips theme={theme} chips={[
            { label: virtual ? t('cabinet.chip.plusThousands', { n: 10 }) : t('cabinet.chip.plus', { n: 100 }), onClick: () => setAmount(v => Math.round((v + (virtual ? 10000 : 100)) * 100) / 100) },
            { label: virtual ? t('cabinet.chip.plusThousands', { n: 25 }) : t('cabinet.chip.plus', { n: 500 }), onClick: () => setAmount(v => Math.round((v + (virtual ? 25000 : 500)) * 100) / 100) },
            { label: virtual ? t('cabinet.chip.plusThousands', { n: 50 }) : t('cabinet.chip.plusThousands', { n: 1 }), onClick: () => setAmount(v => Math.round((v + (virtual ? 50000 : 1000)) * 100) / 100) },
            { label: t('cabinet.chip.clear'), onClick: () => setAmount(0) },
          ]} />
          {!virtual && (
            <div style={{ marginTop: 20 }}>
              <div style={{ fontSize: 11, color: theme.textTer, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginBottom: 10 }}>{t('cabinet.deposit.method')}</div>
              <MethodGrid theme={theme} value={method} onChange={setMethod} />
            </div>
          )}
          <div style={{ height: 18 }} />
          <ConfirmButton theme={theme} color={accent} disabled={!valid || phase !== 'form'} onClick={submit}>
            {phase === 'confirming' ? t('cabinet.deposit.busy')
              : valid ? (virtual ? t('cabinet.deposit.ctaVirtual', { amount: fmtBal(amount, acct.currency) }) : t('cabinet.deposit.cta', { amount: fmtBal(amount, acct.currency) }))
              : t('cabinet.amount.enter')}
          </ConfirmButton>
        </>
      )}
    </SheetShell>
  );
}

function WithdrawSheet({ accounts, fromAcct, theme, onClose, onConfirm }) {
  const [acct, setAcct] = React.useState(fromAcct);
  const live = accounts.filter(a => a.type === 'live');
  const [amount, setAmount] = React.useState(0);
  const [method, setMethod] = React.useState('card');
  const [phase, setPhase] = React.useState('form');
  const avail = acct.balance;
  const valid = amount > 0 && amount <= avail && !!method;
  const submit = () => {
    setPhase('confirming');
    setTimeout(() => { onConfirm(acct.id, amount); setPhase('done'); setTimeout(onClose, 1000); }, 550);
  };
  return (
    <SheetShell theme={theme} onClose={onClose}>
      {phase === 'done' ? (
        <SheetSuccess theme={theme} title={t('cabinet.withdraw.doneTitle')} sub={t('cabinet.summary.fromAccount', { amount: fmtBal(amount, acct.currency), login: acct.login })} />
      ) : (
        <>
          <SheetHeader theme={theme} title={t('cabinet.withdraw.title')} onClose={onClose} />
          <div style={{ marginBottom: 16 }}>
            <AcctSelect label={t('cabinet.field.from')} value={acct} options={live} theme={theme} onSelect={setAcct} />
          </div>
          <div style={{ padding: '4px 0 2px' }}>
            <AmountField value={amount} setValue={setAmount} currency={acct.currency} theme={theme} />
            <div style={{ textAlign: 'center', fontSize: 12, color: amount > avail ? theme.sell : theme.textSec, marginTop: 8 }}>
              {amount > avail ? t('cabinet.amount.exceedsBalance') : t('cabinet.amount.available', { amount: fmtBal(avail, acct.currency) })}
            </div>
          </div>
          <Chips theme={theme} chips={[
            { label: '+100', onClick: () => setAmount(v => Math.min(avail, Math.round((v + 100) * 100) / 100)) },
            { label: '+500', onClick: () => setAmount(v => Math.min(avail, Math.round((v + 500) * 100) / 100)) },
            { label: t('cabinet.chip.max'), onClick: () => setAmount(avail) },
            { label: t('cabinet.chip.clear'), onClick: () => setAmount(0) },
          ]} />
          <div style={{ marginTop: 20 }}>
            <div style={{ fontSize: 11, color: theme.textTer, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginBottom: 10 }}>{t('cabinet.withdraw.method')}</div>
            <MethodGrid theme={theme} value={method} onChange={setMethod} />
          </div>
          <div style={{ height: 18 }} />
          <ConfirmButton theme={theme} color={theme.sell} disabled={!valid || phase !== 'form'} onClick={submit}>
            {phase === 'confirming' ? t('cabinet.withdraw.busy') : valid ? t('cabinet.withdraw.cta', { amount: fmtBal(amount, acct.currency) }) : t('cabinet.amount.enter')}
          </ConfirmButton>
        </>
      )}
    </SheetShell>
  );
}

function ResetSheet({ acct, theme, onClose, onConfirm }) {
  const [phase, setPhase] = React.useState('form');
  const target = acct.resetTo || 50000;
  const submit = () => {
    setPhase('confirming');
    setTimeout(() => { onConfirm(acct.id, target); setPhase('done'); setTimeout(onClose, 1000); }, 500);
  };
  return (
    <SheetShell theme={theme} onClose={onClose}>
      {phase === 'done' ? (
        <SheetSuccess theme={theme} title={t('cabinet.reset.doneTitle')} sub={t('cabinet.reset.doneSub', { login: acct.login, amount: fmtBal(target, acct.currency) })} />
      ) : (
        <>
          <SheetHeader theme={theme} title={t('cabinet.reset.title')} onClose={onClose} />
          <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '2px 0 16px' }}>
            <AcctBadge acct={acct} theme={theme} />
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <span style={{ fontSize: 15, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums' }}>#{acct.login}</span>
                <TypePill acct={acct} theme={theme} />
              </div>
              <div style={{ fontSize: 12, color: theme.textSec, marginTop: 1 }}>{t('cabinet.reset.current', { amount: fmtBal(acct.balance, acct.currency) })}</div>
            </div>
          </div>
          <div style={{ padding: '14px', borderRadius: 13, background: theme.bgSubtle, textAlign: 'center', marginBottom: 18 }}>
            <div style={{ fontSize: 11, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{t('cabinet.reset.newBalance')}</div>
            <div style={{ fontSize: 28, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.6, marginTop: 2 }}>{fmtBal(target, acct.currency)}</div>
          </div>
          <ConfirmButton theme={theme} color={DEMO_COLOR} disabled={phase !== 'form'} onClick={submit}>
            {phase === 'confirming' ? t('cabinet.reset.busy') : t('cabinet.reset.cta', { amount: fmtBal(target, acct.currency) })}
          </ConfirmButton>
        </>
      )}
    </SheetShell>
  );
}

// Tap-an-account action sheet → switch + contextual money actions
function AccountActionSheet({ acct, active, equity, theme, onClose, onSwitch, openSheet }) {
  const isLive = acct.type === 'live';
  const isActive = acct.id === active.id;
  const row = (label, val, col) => (
    <div style={{ display: 'flex', justifyContent: 'space-between', padding: '9px 2px', borderBottom: `0.5px solid ${theme.border}` }}>
      <span style={{ fontSize: 13, color: theme.textSec }}>{label}</span>
      <span style={{ fontSize: 13, color: col || theme.text, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }}>{val}</span>
    </div>
  );
  const actBtn = (label, kind, col) => (
    <div onClick={() => openSheet(kind, acct)} style={{ flex: 1, padding: '13px 4px', borderRadius: 13, background: theme.bgSubtle, color: col || theme.text, textAlign: 'center', fontWeight: 700, fontSize: 13.5, cursor: 'pointer' }}>{label}</div>
  );
  return (
    <SheetShell theme={theme} onClose={onClose}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 16 }}>
        <AcctBadge acct={acct} theme={theme} size={42} />
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
            <span style={{ fontSize: 17, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2 }}>#{acct.login}</span>
            <TypePill acct={acct} theme={theme} />
            {/* The middle dot stays literal JSX; only the word is translated,
                and the uppercasing moves to CSS so non-Latin scripts survive. */}
            {isActive && <span style={{ fontSize: 10.5, fontWeight: 700, color: theme.accent, textTransform: 'uppercase' }}>· {t('accounts.active')}</span>}
          </div>
          <div style={{ fontSize: 12.5, color: theme.textSec, marginTop: 1 }}>{acct.plan} · {acct.server}</div>
        </div>
        <div onClick={onClose} style={{ width: 32, height: 32, borderRadius: 16, background: theme.bgSubtle, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M18 6L6 18M6 6l12 12" stroke={theme.textSec} strokeWidth="2.2" strokeLinecap="round"/></svg>
        </div>
      </div>

      <div style={{ padding: '2px 0 16px' }}>
        {row(t('common.balance'), fmtBal(acct.balance, acct.currency))}
        {row(t('common.equity'), fmtBal(equity, acct.currency))}
        {row(t('accounts.leverage'), acct.leverage)}
        {row(t('accounts.currency'), acct.currency)}
        {row(t('accounts.server'), acct.server)}
      </div>

      {!isActive && (
        <div style={{ marginBottom: 10 }}>
          <ConfirmButton theme={theme} color={theme.accent} onClick={() => { onSwitch(acct.id); onClose(); }}>
            {t('accounts.switchTo')}
          </ConfirmButton>
        </div>
      )}

      <div style={{ display: 'flex', gap: 8 }}>
        {isLive ? (
          <>
            {actBtn(t('cabinet.action.deposit'), 'deposit', theme.buy)}
            {actBtn(t('cabinet.action.withdraw'), 'withdraw')}
            {actBtn(t('cabinet.action.transfer'), 'transfer')}
          </>
        ) : (
          <>
            {actBtn(t('cabinet.action.addFunds'), 'deposit', DEMO_COLOR)}
            {actBtn(t('cabinet.action.reset'), 'reset')}
            {actBtn(t('cabinet.action.transfer'), 'transfer')}
          </>
        )}
      </div>
    </SheetShell>
  );
}

// ————————————————————————— the tab —————————————————————————

function AccountRow({ acct, active, prices, theme, equity, onTap, last }) {
  const isActive = acct.id === active.id;
  const col = acctColor(acct, theme);
  return (
    <div onClick={() => onTap(acct)} style={{
      display: 'flex', alignItems: 'center', gap: 12, padding: '13px 14px', cursor: 'pointer',
      borderBottom: last ? 'none' : `0.5px solid ${theme.border}`,
      background: isActive ? `${theme.accent}0E` : 'transparent',
    }}>
      <AcctBadge acct={acct} theme={theme} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{ fontSize: 15, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2 }}>#{acct.login}</span>
          <TypePill acct={acct} theme={theme} />
          {isActive && <span style={{ width: 6, height: 6, borderRadius: 3, background: theme.accent }} />}
        </div>
        <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 2 }}>{acct.plan} · {acct.leverage}</div>
      </div>
      <div style={{ textAlign: 'end' }}>
        <div style={{ fontSize: 15.5, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.3 }}>{fmtBal(equity, acct.currency)}</div>
        <div style={{ fontSize: 11, color: theme.textTer, marginTop: 1 }}>{acct.currency}</div>
      </div>
    </div>
  );
}

function CabinetTab({ accounts, activeId, totalPL, prices, theme, onTapAccount, onSwitch, openSheet }) {
  const active = accounts.find(a => a.id === activeId) || accounts[0];
  const equityOf = (a) => a.balance + (a.trading ? totalPL : 0);
  const activeEquity = equityOf(active);
  const live = accounts.filter(a => a.type === 'live');
  const demo = accounts.filter(a => a.type === 'demo');
  const isLive = active.type === 'live';
  const accent = isLive ? theme.buy : DEMO_COLOR;

  const heroBtn = (label, kind, primary, col) => (
    <div onClick={() => openSheet(kind, active)} style={{
      flex: 1, padding: '12px 4px', borderRadius: 13, textAlign: 'center', cursor: 'pointer',
      fontWeight: 700, fontSize: 13.5, letterSpacing: -0.1,
      background: primary ? col : theme.bgSubtle, color: primary ? '#fff' : theme.text,
      boxShadow: primary ? `0 6px 16px ${col}44` : 'none',
    }}>{label}</div>
  );

  // module-scope-safe helper: plain function returning JSX (not a component
  // type), so re-renders update DOM in place instead of remounting it.
  const renderGroup = (title, list) => (
    <div style={{ marginTop: 6 }}>
      <div style={{ padding: '14px 18px 8px', display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <span style={{ fontSize: 11.5, fontWeight: 700, color: theme.textSec, textTransform: 'uppercase', letterSpacing: 0.6 }}>{title}</span>
        <span style={{ fontSize: 11.5, color: theme.textTer, fontWeight: 600 }}>{list.length === 1 ? t('cabinet.group.countOne', { n: list.length }) : t('cabinet.group.countMany', { n: list.length })}</span>
      </div>
      <div style={{ background: theme.bgCard, margin: '0 16px', borderRadius: 14, border: `0.5px solid ${theme.border}`, overflow: 'hidden' }}>
        {list.map((a, i) => (
          <AccountRow key={a.id} acct={a} active={active} prices={prices} theme={theme} equity={equityOf(a)} onTap={onTapAccount} last={i === list.length - 1} />
        ))}
      </div>
    </div>
  );

  return (
    <div>
      <div style={{ padding: '8px 16px 0' }}>
        <div style={{ fontSize: 30, fontWeight: 700, color: theme.text, letterSpacing: -0.5 }}>{t('cabinet.title')}</div>
      </div>

      {/* Active account hero */}
      <div style={{ margin: '14px 16px 0', borderRadius: 18, overflow: 'hidden', border: `0.5px solid ${theme.border}`, background: theme.bgCard }}>
        <div style={{ padding: '16px 16px 14px', background: `linear-gradient(135deg, ${accent}1A, ${accent}05)` }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
            <span style={{ fontSize: 10.5, fontWeight: 700, color: theme.textSec, textTransform: 'uppercase', letterSpacing: 0.6 }}>{t('cabinet.activeAccount')}</span>
            <span style={{ marginInlineStart: 'auto', fontSize: 13, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums' }}>#{active.login}</span>
            <TypePill acct={active} theme={theme} />
          </div>
          <div style={{ fontSize: 38, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -1.2, lineHeight: 1 }}>
            {fmtBal(activeEquity, active.currency)}
          </div>
          <div style={{ fontSize: 12.5, color: theme.textSec, marginTop: 6 }}>
            {active.plan} · {active.currency} · {active.leverage}
            {active.trading && (
              <span style={{ color: totalPL >= 0 ? theme.buy : theme.sell, fontWeight: 600, marginInlineStart: 8, fontVariantNumeric: 'tabular-nums' }}>
                {t('cabinet.pl', { amount: fmtMoney(totalPL) })}
              </span>
            )}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8, padding: '12px 14px 14px' }}>
          {isLive ? (
            <>
              {heroBtn(t('cabinet.action.deposit'), 'deposit', true, theme.buy)}
              {heroBtn(t('cabinet.action.withdraw'), 'withdraw', false)}
              {heroBtn(t('cabinet.action.transfer'), 'transfer', false)}
            </>
          ) : (
            <>
              {heroBtn(t('cabinet.action.addFunds'), 'deposit', true, DEMO_COLOR)}
              {heroBtn(t('cabinet.action.reset'), 'reset', false)}
              {heroBtn(t('cabinet.action.transfer'), 'transfer', false)}
            </>
          )}
        </div>
      </div>

      {live.length > 0 && renderGroup(t('cabinet.group.live'), live)}
      {demo.length > 0 && renderGroup(t('cabinet.group.demo'), demo)}

      <div style={{ height: 100 }} />
    </div>
  );
}

// ————————————————————————— header account switcher (dropdown) —————————————————————————

const SWITCH_ICONS = {
  deposit:  (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><path d="M12 4v10M8 12l4 4 4-4M5 20h14" stroke={c} strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  withdraw: (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><path d="M12 16V6M8 10l4-4 4 4M5 20h14" stroke={c} strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  reset:    (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><path d="M4 12a8 8 0 108-8 8 8 0 00-6.2 3M4 4v4h4" stroke={c} strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  transfer: (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><path d="M7 5v13M7 18l-3-3M7 18l3-3M17 19V6M17 6l-3 3M17 6l3 3" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>,
  open:     (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="8.5" stroke={c} strokeWidth="1.8"/><path d="M12 8.5v7M8.5 12h7" stroke={c} strokeWidth="1.9" strokeLinecap="round"/></svg>,
  // API access: a key — the token IS the key to the account.
  api:      (c) => <svg width="19" height="19" viewBox="0 0 24 24" fill="none"><circle cx="8" cy="14" r="4.2" stroke={c} strokeWidth="1.8"/><path d="M11 11l8-8M17 5l2 2M15 7l2 2" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>,
};

function ActionTile({ kind, label, color, theme, onClick }) {
  return (
    <div onClick={onClick} style={{
      flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 7,
      padding: '12px 4px', borderRadius: 14, background: theme.bgCard,
      border: `0.5px solid ${theme.border}`, cursor: 'pointer',
    }}>
      <div style={{ width: 38, height: 38, borderRadius: 19, background: `${color}1F`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {SWITCH_ICONS[kind](color)}
      </div>
      <span style={{ fontSize: 11, fontWeight: 600, color: theme.text, letterSpacing: -0.1 }}>{label}</span>
    </div>
  );
}

function SwitcherRow({ acct, active, equity, theme, onTap, last }) {
  const isActive = acct.id === active.id;
  return (
    <div onClick={() => onTap(acct)} style={{
      display: 'flex', alignItems: 'center', gap: 11, padding: '10px 12px', cursor: 'pointer',
      borderRadius: 12, background: isActive ? `${theme.accent}14` : 'transparent',
    }}>
      <AcctBadge acct={acct} theme={theme} size={36} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{ fontSize: 14.5, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2 }}>#{acct.login}</span>
          <TypePill acct={acct} theme={theme} />
        </div>
        <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 2 }}>{acct.plan} · {acct.leverage}</div>
      </div>
      <div style={{ textAlign: 'end' }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -0.2 }}>{fmtBal(equity, acct.currency)}</div>
        <div style={{ fontSize: 10.5, color: theme.textTer, marginTop: 1 }}>{acct.currency}</div>
      </div>
      <div style={{ width: 18, flexShrink: 0, display: 'flex', justifyContent: 'center' }}>
        {isActive && <svg width="17" height="17" viewBox="0 0 24 24" fill="none"><path d="M5 12l5 5L20 7" stroke={theme.accent} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"/></svg>}
      </div>
    </div>
  );
}

function AccountSwitcher({ accounts, activeId, totalPL, theme, onSwitch, onClose, onAction, onLogout }) {
  const active = accounts.find(a => a.id === activeId) || accounts[0];
  const equityOf = (a) => a.balance + (a.trading ? totalPL : 0);
  const live = accounts.filter(a => a.type === 'live');
  const demo = accounts.filter(a => a.type === 'demo');
  const isLive = active.type === 'live';
  const tap = (a) => { onSwitch(a.id); onClose(); };
  const act = (kind) => { onClose(); onAction(kind, active); };

  // Parameter is `text`, not `t` — `t` is the global translator.
  const groupLabel = (text) => (
    <div style={{ padding: '4px 6px 6px', fontSize: 10.5, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.6 }}>{text}</div>
  );
  const renderGroup = (list, i) => (
    <div key={i} style={{ marginTop: i ? 6 : 0 }}>
      {groupLabel(list[0].type === 'live' ? t('accounts.typeName.live') : t('accounts.typeName.demo'))}
      {list.map((a, k) => <SwitcherRow key={a.id} acct={a} active={active} equity={equityOf(a)} theme={theme} onTap={tap} last={k === list.length - 1} />)}
    </div>
  );

  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 90,
      background: 'rgba(0,0,0,0.45)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)',
      animation: 'fadeIn 160ms ease-out',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        position: 'absolute', top: 100, insetInline: 10,
        background: theme.bgElevated, borderRadius: 22, border: `0.5px solid ${theme.border}`,
        boxShadow: '0 24px 60px rgba(0,0,0,0.55)', padding: 12,
        maxHeight: 720, overflowY: 'auto',
        animation: 'dropDown 240ms cubic-bezier(0.2,0.9,0.3,1.04)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', padding: '2px 6px 10px' }}>
          <span style={{ flex: 1, fontSize: 13, fontWeight: 700, color: theme.text, letterSpacing: -0.2 }}>{t('switcher.title')}</span>
          <span style={{ fontSize: 11.5, color: theme.textTer, fontWeight: 600 }}>{t('switcher.countTotal', { n: accounts.length })}</span>
        </div>

        {[live, demo].filter(g => g.length).map(renderGroup)}

        <div style={{ height: 1, background: theme.border, margin: '12px 2px' }} />

        <div style={{ padding: '0 6px 8px', fontSize: 10.5, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.6 }}>{t('switcher.manageFunds')}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          {isLive ? (
            <>
              <ActionTile kind="deposit"  label={t('cabinet.action.deposit')}  color={theme.buy}   theme={theme} onClick={() => act('deposit')} />
              <ActionTile kind="withdraw" label={t('cabinet.action.withdraw')} color={theme.text}  theme={theme} onClick={() => act('withdraw')} />
            </>
          ) : (
            <>
              <ActionTile kind="deposit" label={t('cabinet.action.addFunds')} color={DEMO_COLOR} theme={theme} onClick={() => act('deposit')} />
              <ActionTile kind="reset"   label={t('cabinet.action.reset')}    color={theme.text}  theme={theme} onClick={() => act('reset')} />
            </>
          )}
          <ActionTile kind="transfer" label={t('cabinet.action.transfer')} color={theme.text} theme={theme} onClick={() => act('transfer')} />
          <ActionTile kind="open"     label={t('cabinet.action.openNew')}  color={theme.accent} theme={theme} onClick={() => act('openaccount')} />
        </div>

        {/* Automation lives next to funds, not buried: revoking a bot's access
            is the same class of action as moving money. */}
        <div style={{ padding: '14px 6px 7px', fontSize: 10.5, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.6 }}>{t('switcher.automation')}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <ActionTile kind="api" label={t('cabinet.action.apiAccess')} color={theme.accent} theme={theme} onClick={() => act('api')} />
          <div style={{ flex: 1 }} />
          <div style={{ flex: 1 }} />
        </div>

        {/* Language (F-i18n-2, ID4). The cabinet sheet is where this shell
            keeps everything belonging to the PERSON rather than to a single
            account — funds, automation, sign-out — and a locale is a user
            attribute, not an account one, so it belongs here.

            Each locale is named in its OWN language (the endonym, served by
            the catalogue alongside the bundle). Someone who has landed in a
            script they cannot read still has to find their way out, and
            "Español" is findable in a list where "Spanish" is not. */}
        <div style={{ padding: '14px 6px 7px', fontSize: 10.5, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.6 }}>{t('common.language')}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
          {i18nCatalogue().map(lang => {
            const on = lang.tag === i18nLocale();
            return (
              <div key={lang.tag}
                onClick={() => { if (!on) setLocale(lang.tag).then(() => apiSaveLocale(lang.tag)); }}
                style={{
                  padding: '9px 13px', borderRadius: 11, cursor: on ? 'default' : 'pointer',
                  background: on ? theme.accent : theme.bgSubtle,
                  color: on ? '#fff' : theme.text,
                  fontSize: 13, fontWeight: on ? 700 : 600,
                  border: `0.5px solid ${on ? 'transparent' : theme.border}`,
                }}>
                {/* The label is in the target script, so it declares its own
                    direction rather than inheriting the sheet's — otherwise
                    "العربية" in an LTR sheet (or "English" in an RTL one)
                    is reordered against its chip. */}
                <bdi>{lang.label}</bdi>
              </div>
            );
          })}
        </div>

        <div style={{ height: 1, background: theme.border, margin: '12px 2px' }} />
        <div data-testid="logout" onClick={() => { onClose(); onLogout(); }} style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          padding: '13px', borderRadius: 13, background: theme.bgSubtle, cursor: 'pointer',
          color: theme.sell, fontWeight: 700, fontSize: 13.5,
        }}>
          <svg width="17" height="17" viewBox="0 0 24 24" fill="none"><path d="M16 17l5-5-5-5M21 12H9M12 21H6a2 2 0 01-2-2V5a2 2 0 012-2h6" stroke={theme.sell} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
          {t('switcher.logout')}
        </div>
      </div>
    </div>
  );
}

// New account creation
// Plan names are Backoffice-sourced display copy: docs/i18n.md §5 puts them
// (C9, with payment-method copy C8) explicitly OUT of v1 i18n scope, flagged
// into the Backoffice webservice contract instead (IO4). They stay hardcoded
// here on purpose — do not move them behind t().
const OA_PLANS = ['Standard', 'Raw Spread', 'Pro', 'Swing'];
const OA_LEVS = ['1:30', '1:100', '1:200', '1:400', '1:500'];

function OpenAccountSheet({ theme, onClose, onConfirm }) {
  const [type, setType] = React.useState('demo');
  const [plan, setPlan] = React.useState('Standard');
  const [leverage, setLeverage] = React.useState('1:400');
  const [phase, setPhase] = React.useState('form');
  const accent = type === 'live' ? theme.buy : DEMO_COLOR;

  const submit = () => {
    setPhase('confirming');
    const login = (type === 'live' ? '50' : '88') + Math.floor(100000 + Math.random() * 899999);
    const acct = {
      id: 'a' + Date.now(), login, type, plan, currency: 'USD', leverage,
      server: type === 'live' ? 'CFD-Live 04' : 'CFD-Demo 02',
      balance: type === 'demo' ? 10000 : 0,
      resetTo: type === 'demo' ? 10000 : undefined,
    };
    setTimeout(() => { onConfirm(acct); setPhase('done'); setTimeout(onClose, 1100); }, 550);
  };

  // Plain JSX helpers (not component types) — inline component definitions
  // remount their DOM on every render, eating clicks and input focus.
  // An option is either a bare string (value doubles as label, e.g. plan and
  // leverage names, which are data) or a { v, label } pair so a localized label
  // can ride along without touching the value the comparison and onChange use.
  const renderSeg = (val, options, onChange) => (
    <div style={{ display: 'flex', gap: 3, padding: 3, borderRadius: 10, background: theme.bgSubtle }}>
      {options.map(o => {
        const v = typeof o === 'string' ? o : o.v;
        const lbl = typeof o === 'string' ? o : o.label;
        return (
          <div key={v} onClick={() => onChange(v)} style={{
            flex: 1, textAlign: 'center', padding: '8px 4px', borderRadius: 7, fontSize: 12.5, fontWeight: 600, cursor: 'pointer', letterSpacing: -0.1,
            background: val === v ? theme.bgCard : 'transparent', color: val === v ? theme.text : theme.textSec,
            border: val === v ? `0.5px solid ${theme.borderStrong}` : '0.5px solid transparent',
          }}>{lbl}</div>
        );
      })}
    </div>
  );
  const renderField = (label, children) => (
    <div style={{ marginBottom: 14 }}>
      <div style={{ fontSize: 11, color: theme.textTer, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, marginBottom: 8 }}>{label}</div>
      {children}
    </div>
  );

  return (
    <SheetShell theme={theme} onClose={onClose}>
      {phase === 'done' ? (
        <SheetSuccess theme={theme} title={t('cabinet.open.doneTitle')} sub={t('cabinet.open.doneSub', { type: type === 'live' ? t('accounts.typeName.live') : t('accounts.typeName.demo'), plan })} />
      ) : (
        <>
          <SheetHeader theme={theme} title={t('cabinet.open.title')} onClose={onClose} />
          {renderField(t('cabinet.open.field.type'), <>
            {renderSeg(type, [{ v: 'demo', label: t('accounts.type.demo') }, { v: 'live', label: t('accounts.type.live') }], v => setType(v))}
            <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 7 }}>
              {type === 'demo' ? t('cabinet.open.hintDemo') : t('cabinet.open.hintLive')}
            </div>
          </>)}
          {renderField(t('cabinet.open.field.plan'), renderSeg(plan, OA_PLANS, setPlan))}
          {renderField(t('accounts.leverage'), renderSeg(leverage, OA_LEVS, setLeverage))}
          <div style={{ height: 4 }} />
          <ConfirmButton theme={theme} color={accent} disabled={phase !== 'form'} onClick={submit}>
            {phase === 'confirming' ? t('cabinet.open.busy') : (type === 'live' ? t('cabinet.open.ctaLive') : t('cabinet.open.ctaDemo'))}
          </ConfirmButton>
        </>
      )}
    </SheetShell>
  );
}

// ————————————————————————— API access (Track P P1) —————————————————————————
//
// The human's view of every piece of automation that can touch their accounts:
// list, mint, revoke. The scopes and the plaintext-once rule ARE the security
// model, so the copy states them plainly instead of hiding them in a tooltip.
//
// P1 is list / mint / revoke. P3 adds live state (connected, caps usage), the
// per-session message feed, and the higher kill levels (cancel orders, flatten).

// Keys, not text: both maps are module-scope, so they are built before
// initI18n() resolves. They are resolved through t() at render time.
const SCOPE_LABEL_KEY = {
  read: 'apiaccess.scope.read',
  'trade:demo': 'apiaccess.scope.tradeDemo',
  'trade:live': 'apiaccess.scope.tradeLive',
};

const SCOPE_BLURB_KEY = {
  read: 'apiaccess.scopeBlurb.read',
  'trade:demo': 'apiaccess.scopeBlurb.tradeDemo',
  'trade:live': 'apiaccess.scopeBlurb.tradeLive',
};

function scopeColor(scope, theme) {
  if (scope === 'trade:live') return theme.sell;
  if (scope === 'trade:demo') return DEMO_COLOR;
  return theme.textSec;
}

function ScopeBadge({ scope, theme }) {
  const col = scopeColor(scope, theme);
  return (
    <span style={{
      fontSize: 9.5, fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase',
      color: col, background: `${col}1F`, border: `0.5px solid ${col}55`,
      padding: '2.5px 6px', borderRadius: 5, whiteSpace: 'nowrap',
    }}>{SCOPE_LABEL_KEY[scope] ? t(SCOPE_LABEL_KEY[scope]) : scope}</span>
  );
}

// fmtWhen renders a unix-millis stamp as a short relative age — nobody reading
// this list is asking for the exact timestamp.
function fmtWhen(ms) {
  if (!ms) return t('apiaccess.age.never');
  const s = Math.max(0, (Date.now() - ms) / 1000);
  if (s < 60) return t('apiaccess.age.justNow');
  if (s < 3600) return t('apiaccess.age.minutes', { n: Math.floor(s / 60) });
  if (s < 86400) return t('apiaccess.age.hours', { n: Math.floor(s / 3600) });
  if (s < 86400 * 30) return t('apiaccess.age.days', { n: Math.floor(s / 86400) });
  return new Date(ms).toLocaleDateString();
}

// The plaintext token, shown once. Loud on purpose: this is the only chance the
// user gets, and the cost of dismissing it is re-minting.
function TokenReveal({ token, theme, onDone }) {
  const [copied, setCopied] = React.useState(false);
  const copy = async () => {
    const ok = await copyText(token);
    setCopied(ok ? 'yes' : 'no');
    setTimeout(() => setCopied(false), 2200);
  };
  return (
    <div>
      <SheetSuccess theme={theme} title={t('apiaccess.reveal.title')} sub={t('apiaccess.reveal.sub')} />
      <div style={{
        padding: '12px 13px', borderRadius: 13, background: theme.bgCard,
        border: `1.5px solid ${theme.accent}`, marginBottom: 10,
      }}>
        <div style={{
          fontSize: 12, fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
          color: theme.text, wordBreak: 'break-all', lineHeight: 1.5,
        }}>{token}</div>
      </div>
      <div onClick={copy} style={{
        padding: '13px', borderRadius: 13, textAlign: 'center', cursor: 'pointer',
        fontWeight: 700, fontSize: 14, background: theme.accent, color: '#fff', marginBottom: 12,
      }}>{copied === 'yes' ? t('common.action.copied') : copied === 'no' ? t('apiaccess.reveal.copyFailed') : t('apiaccess.reveal.copy')}</div>
      {/* One sentence, one key. `Authorization: Bearer …` is a wire literal, not
          copy, so it stays untranslated and keeps its mono face: the string
          carries a {header} placeholder and the split puts the node back in. */}
      <div style={{ fontSize: 11.5, color: theme.textSec, lineHeight: 1.5, marginBottom: 14 }}>
        {t('apiaccess.reveal.usage').split('{header}').map((part, i) => (
          <React.Fragment key={i}>
            {i > 0 && <span style={{ fontFamily: 'ui-monospace, monospace', color: theme.text }}>Authorization: Bearer …</span>}
            {part}
          </React.Fragment>
        ))}
      </div>
      {/* Secondary on purpose: copying is the action, dismissing is the exit. */}
      <div onClick={onDone} style={{
        padding: '15px', borderRadius: 14, textAlign: 'center', cursor: 'pointer',
        fontWeight: 700, fontSize: 15, background: theme.bgSubtle, color: theme.text,
      }}>{t('apiaccess.reveal.saved')}</div>
    </div>
  );
}

function MintTokenForm({ accounts, theme, liveTradingEnabled, onCancel, onMinted }) {
  const [name, setName] = React.useState('');
  const [acct, setAcct] = React.useState(accounts.find(a => a.type === 'demo' && a.trading) || accounts[0]);
  const [scope, setScope] = React.useState('read');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  // A trade:demo token must be bound to a demo account (PD15). The server
  // enforces it; the form simply never offers the impossible combination.
  const demoBound = !!acct && acct.type === 'demo';
  const scopeOff = (s) =>
    (s === 'trade:live' && !liveTradingEnabled) || (s === 'trade:demo' && !demoBound);

  React.useEffect(() => {
    if (scopeOff(scope)) setScope('read');
  }, [acct && acct.id]);

  const submit = async () => {
    if (busy || !name.trim() || !acct) return;
    setBusy(true); setErr(null);
    try {
      const res = await apiMintAgentSession({
        name: name.trim(), platform: acct.platform, account: acct.id, scope,
      });
      onMinted(res);
    } catch (e) {
      setErr(String((e && e.message) || e));
      setBusy(false);
    }
  };

  // Parameter is `text`, not `t` — `t` is the global translator.
  const label = (text) => (
    <div style={{ fontSize: 11, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.5, margin: '14px 2px 7px' }}>{text}</div>
  );

  return (
    <div>
      {label(t('apiaccess.field.name'))}
      <input
        value={name}
        onChange={e => setName(e.target.value.slice(0, 64))}
        placeholder={t('apiaccess.name.placeholder')}
        style={{
          width: '100%', boxSizing: 'border-box', padding: '13px', borderRadius: 13,
          background: theme.bgCard, border: `0.5px solid ${theme.border}`, color: theme.text,
          fontSize: 15, fontWeight: 600, outline: 'none',
        }}
      />

      {label(t('apiaccess.field.account'))}
      {acct
        ? <AcctSelect label={t('cabinet.field.acct')} value={acct} options={accounts} theme={theme} onSelect={setAcct} />
        : <div style={{ fontSize: 12.5, color: theme.textSec, padding: '4px 2px' }}>{t('apiaccess.noAccount')}</div>}
      <div style={{ fontSize: 11.5, color: theme.textTer, margin: '7px 2px 0', lineHeight: 1.45 }}>
        {t('apiaccess.accountHint')}
      </div>

      {label(t('apiaccess.field.scope'))}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
        {['read', 'trade:demo', 'trade:live'].map(s => {
          const off = scopeOff(s);
          const sel = scope === s;
          return (
            <div key={s} onClick={() => !off && setScope(s)} style={{
              display: 'flex', alignItems: 'flex-start', gap: 10, padding: '11px 12px', borderRadius: 12,
              background: theme.bgCard, border: `1.5px solid ${sel ? theme.accent : theme.border}`,
              cursor: off ? 'default' : 'pointer', opacity: off ? 0.42 : 1,
            }}>
              <div style={{
                width: 17, height: 17, borderRadius: 9, flexShrink: 0, marginTop: 1,
                border: `1.5px solid ${sel ? theme.accent : theme.borderStrong}`,
                background: sel ? theme.accent : 'transparent',
              }} />
              <div style={{ minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                  <ScopeBadge scope={s} theme={theme} />
                  {off && <span style={{ fontSize: 10.5, color: theme.textTer, fontWeight: 600 }}>
                    {s === 'trade:live' ? t('apiaccess.scopeOff.notEnabled') : t('apiaccess.scopeOff.needsDemo')}
                  </span>}
                </div>
                <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 4, lineHeight: 1.45 }}>{t(SCOPE_BLURB_KEY[s])}</div>
              </div>
            </div>
          );
        })}
      </div>

      {err && (
        <div style={{ marginTop: 12, padding: '10px 12px', borderRadius: 11, background: `${theme.sell}18`, color: theme.sell, fontSize: 12.5, fontWeight: 600 }}>{err}</div>
      )}

      <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
        <div onClick={onCancel} style={{
          padding: '16px 20px', borderRadius: 14, textAlign: 'center', fontWeight: 700, fontSize: 15,
          background: theme.bgSubtle, color: theme.text, cursor: 'pointer',
        }}>{t('common.action.cancel')}</div>
        <div style={{ flex: 1 }}>
          <ConfirmButton theme={theme} color={theme.accent} disabled={busy || !name.trim() || !acct} onClick={submit}>
            {busy ? t('apiaccess.mint.busy') : t('apiaccess.mint.cta')}
          </ConfirmButton>
        </div>
      </div>
    </div>
  );
}

// ————————————————————————— agent sessions (Track P P3) —————————————————————————
//
// The phone's supervision screen (docs/public-api.md PD10). One sheet does the
// lot, because a phone has no room for two: the session list with live caps
// meters, the message feed, the escalating kill, and P1's mint/reveal behind
// the same button they were always behind.
//
// The caps meters are the part that earns the screen. A human who cannot see a
// ceiling has to take "it is rate limited" on trust; a meter that fills is the
// difference between supervision and faith.

// AgentMeter — one cap as a spent bar plus its figures.
//
// All three read the same way: the bar is how much of the allowance is GONE.
// The request bucket arrives as headroom REMAINING (it refills continuously, so
// a "used" figure would fall on its own) and is converted by the caller — two
// bars that fill as you spend and one that empties is exactly the inconsistency
// that gets misread under pressure. The tooltip carries the nuance.
function AgentMeter({ label, used, limit, title, theme }) {
  const pct = limit > 0 ? Math.max(0, Math.min(100, (used / limit) * 100)) : 0;
  const hot = limit > 0 && used >= limit;
  const col = hot ? theme.sell : pct >= 80 ? DEMO_COLOR : theme.buy;
  return (
    <div title={title} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '2.5px 0' }}>
      <span style={{ width: 62, flexShrink: 0, fontSize: 10, fontWeight: 700, color: theme.textTer, letterSpacing: 0.2 }}>{label}</span>
      <div
        role="progressbar"
        aria-label={title}
        aria-valuemin={0}
        aria-valuemax={limit}
        aria-valuenow={used}
        style={{ flex: 1, height: 4, borderRadius: 2, background: theme.bgSubtle, overflow: 'hidden' }}
      >
        {/* The fill grows from the leading edge, so it mirrors with the row
            under RTL — a physical `left` would leave the bar running away from
            its own label in Arabic. */}
        <div style={{ width: pct + '%', height: '100%', background: col, transition: 'width 240ms' }} />
      </div>
      {/* ONE isolated run for the pair (docs/i18n.md §6.3). "14 / 60" is two
          numbers joined by a neutral: isolate the halves separately and the
          digits are fine while the ORDER reverses, which is a plausible wrong
          value rather than visible garbage. */}
      <span style={{
        width: 60, textAlign: 'end', fontSize: 11, fontWeight: 600,
        color: hot ? theme.sell : theme.textSec,
        fontVariantNumeric: 'tabular-nums', direction: 'ltr', unicodeBidi: 'isolate',
      }}>{used} / {limit}</span>
    </div>
  );
}

// AgentSessionRow — one token as the panel shows it.
//
// A revoked token renders as history: dimmed, struck through, no actions. The
// kill endpoint is owner-scoped and L3/L4 do still work on a revoked token, but
// a live-looking button that answers "already revoked" teaches the human that
// the panel lies, which is the last thing a panic button should do.
function AgentSessionRow({ tok, theme, onFeed, onKill, first }) {
  const dead = !!tok.revoked;
  const usage = tok.usage || {};
  const reqLimit = usage.reqLimit || 0;
  const reqUsed = Math.max(0, reqLimit - (usage.reqRemaining || 0));
  const connected = tok.connected > 0;
  return (
    <div style={{ padding: '12px 13px', borderTop: first ? 'none' : `0.5px solid ${theme.border}`, opacity: dead ? 0.5 : 1 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
        {!dead && (
          <span style={{
            width: 7, height: 7, borderRadius: 4, flexShrink: 0,
            background: connected ? theme.buy : theme.textTer,
          }} />
        )}
        <span style={{
          fontSize: 14, fontWeight: 700, color: theme.text, letterSpacing: -0.2, minWidth: 0,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          textDecoration: dead ? 'line-through' : 'none',
        }}>{tok.name}</span>
        <ScopeBadge scope={tok.scope} theme={theme} />
        {dead && <span style={{ fontSize: 10, fontWeight: 700, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.4 }}>{t('apiaccess.revoked')}</span>}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11.5, color: theme.textSec, marginTop: 4, fontVariantNumeric: 'tabular-nums' }}>
        <span style={{ direction: 'ltr', unicodeBidi: 'isolate' }}>#{tok.account}</span>
        <span>{tok.demo ? t('accounts.type.demo') : t('accounts.type.live')}</span>
        <span style={{ marginInlineStart: 'auto', color: theme.textTer }}>
          {dead
            ? t('agent.state.revokedAt', { when: fmtWhen(tok.revoked) })
            : connected
              ? tn('agent.connections', tok.connected)
              : tok.lastRequest
                ? t('agent.state.lastRequest', { when: fmtWhen(tok.lastRequest) })
                : tok.lastUsed
                  ? t('agent.state.lastUsed', { when: fmtWhen(tok.lastUsed) })
                  : t('agent.state.neverUsed')}
        </span>
      </div>

      {!dead && (
        <div style={{ marginTop: 8 }}>
          <AgentMeter theme={theme} label={t('agent.caps.orders')} used={usage.ordersUsed || 0} limit={usage.ordersLimit || 0} title={t('agent.caps.ordersTitle')} />
          <AgentMeter theme={theme} label={t('agent.caps.notify')} used={usage.notifyUsed || 0} limit={usage.notifyLimit || 0} title={t('agent.caps.notifyTitle')} />
          <AgentMeter theme={theme} label={t('agent.caps.requests')} used={reqUsed} limit={reqLimit} title={t('agent.caps.requestsTitle')} />
        </div>
      )}

      <div style={{ display: 'flex', gap: 7, marginTop: 10 }}>
        <div onClick={() => onFeed(tok)} style={{
          padding: '8px 14px', borderRadius: 10, background: theme.bgSubtle, color: theme.text,
          fontSize: 12.5, fontWeight: 700, cursor: 'pointer',
        }}>{t('agent.action.messages')}</div>
        {!dead && (
          <div onClick={() => onKill(tok)} style={{
            padding: '8px 14px', borderRadius: 10, border: `0.5px solid ${theme.sell}66`,
            color: theme.sell, fontSize: 12.5, fontWeight: 700, cursor: 'pointer',
          }}>{t('agent.kill.cta')}</div>
        )}
      </div>
    </div>
  );
}

// ————————————————————————— feed —————————————————————————

// Level → tint. PD9's vocabulary comes off the wire, so the LABEL renders from
// the code through tCode('agentLevel', …) (FR4) and this map is only colour.
function agentLevelTint(level, theme) {
  if (level === 'success') return theme.buy;
  if (level === 'warning') return DEMO_COLOR;
  if (level === 'alert') return theme.sell;
  return theme.textSec;
}

// AgentFeedRow — one thing an agent said. `body` is agent-authored text and is
// rendered as TEXT: nothing on this path touches dangerouslySetInnerHTML, and
// `pre-wrap` is what lets a bot format with newlines without being handed
// markup.
function AgentFeedRow({ msg, theme, onFilter, last }) {
  const col = agentLevelTint(msg.level, theme);
  const ref = msg.ref || null;
  const stamp = new Date(msg.createdAt);
  const chip = (text) => (
    <span style={{
      padding: '2px 7px', borderRadius: 6, fontSize: 10.5, fontWeight: 600,
      background: theme.bgSubtle, color: theme.textSec,
    }}>{text}</span>
  );
  return (
    <div style={{
      padding: '11px 13px', borderBottom: last ? 'none' : `0.5px solid ${theme.border}`,
      borderInlineStart: `3px solid ${col}`,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
        <span style={{ fontSize: 10, fontWeight: 800, letterSpacing: 0.4, textTransform: 'uppercase', color: col }}>
          {tCode('agentLevel', msg.level)}
        </span>
        <span onClick={() => onFilter && onFilter(msg.tokenId)} style={{
          fontSize: 11.5, color: theme.textSec, cursor: 'pointer', minWidth: 0,
          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
        }}>{msg.tokenName}</span>
        <span style={{ marginInlineStart: 'auto', fontSize: 11, color: theme.textTer, flexShrink: 0, fontVariantNumeric: 'tabular-nums' }}>
          {fmtMonthDayTime(stamp, fmtClock(stamp))}
        </span>
      </div>
      <div style={{ fontSize: 14, fontWeight: 700, color: theme.text, marginTop: 3, letterSpacing: -0.2 }}>{msg.title}</div>
      {msg.body && (
        <div style={{ fontSize: 12.5, color: theme.textSec, marginTop: 3, lineHeight: 1.5, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{msg.body}</div>
      )}
      {ref && (ref.symbol || ref.ticket || ref.positionId) && (
        <div style={{ display: 'flex', gap: 6, marginTop: 7, flexWrap: 'wrap' }}>
          {/* An instrument code is canonical and never translated (docs/i18n.md
              §5), so it rides bare. Each id is composed with its # INSIDE one
              isolate, so the pair cannot come apart in an RTL row. */}
          {ref.symbol ? chip(posPair(ref.symbol)) : null}
          {ref.ticket ? chip(t('agent.feed.ref.ticket', { id: bidiIsolate('#' + ref.ticket) })) : null}
          {ref.positionId ? chip(t('agent.feed.ref.position', { id: bidiIsolate('#' + ref.positionId) })) : null}
        </div>
      )}
    </div>
  );
}

function AgentFeedView({ tokenId, sessions, theme, onFilter }) {
  const store = useAgentStore();
  const feed = agentFeed(tokenId);
  const named = tokenId ? (sessions.find(x => x.id === tokenId) || null) : null;

  React.useEffect(() => {
    if (!feed.loaded && !feed.loading) apiLoadAgentMessages(tokenId, false);
    // The store version is in the deps so a feed created by a filter change is
    // fetched on the render that first shows it.
  }, [tokenId, store.version]);

  React.useEffect(() => { agentMarkRead(); }, [store.version]);

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
        <span style={{ flex: 1, minWidth: 0, fontSize: 12, color: theme.textSec, lineHeight: 1.45 }}>
          {named ? t('agent.feed.filter.only', { name: named.name }) : t('agent.feed.oneWay')}
        </span>
        {tokenId ? (
          <span onClick={() => onFilter(0)} style={{
            flexShrink: 0, padding: '7px 12px', borderRadius: 10, background: theme.bgSubtle,
            color: theme.text, fontSize: 12, fontWeight: 700, cursor: 'pointer',
          }}>{t('agent.feed.filter.clear')}</span>
        ) : null}
      </div>

      {feed.err && (
        <div style={{ padding: '10px 12px', borderRadius: 11, background: `${theme.sell}18`, color: theme.sell, fontSize: 12.5, fontWeight: 600, marginBottom: 12 }}>
          {t('agent.feed.error')}
        </div>
      )}

      {!feed.loaded && feed.loading && (
        <div style={{ padding: '18px 0', textAlign: 'center', fontSize: 13, color: theme.textTer }}>{t('agent.feed.loading')}</div>
      )}

      {feed.loaded && !feed.rows.length && (
        <div style={{ padding: '22px 12px', textAlign: 'center', borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}` }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, color: theme.text }}>{t('agent.feed.empty.title')}</div>
          <div style={{ fontSize: 12, color: theme.textSec, marginTop: 4, lineHeight: 1.45 }}>{t('agent.feed.empty.body')}</div>
        </div>
      )}

      {feed.rows.length > 0 && (
        <div style={{ borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}`, overflow: 'hidden' }}>
          {feed.rows.map((msg, i) => (
            <AgentFeedRow key={msg.id} msg={msg} theme={theme} onFilter={onFilter} last={i === feed.rows.length - 1} />
          ))}
        </div>
      )}

      {feed.loaded && feed.rows.length > 0 && (
        <div style={{ padding: '14px 0 2px', textAlign: 'center' }}>
          {feed.nextBefore ? (
            <span onClick={() => apiLoadAgentMessages(tokenId, true)} style={{
              display: 'inline-block', padding: '10px 18px', borderRadius: 12,
              background: theme.bgSubtle, color: theme.text, fontSize: 13, fontWeight: 700, cursor: 'pointer',
            }}>{feed.loading ? t('agent.feed.loading') : t('agent.feed.loadOlder')}</span>
          ) : (
            <span style={{ fontSize: 11.5, color: theme.textTer }}>{t('agent.feed.end')}</span>
          )}
        </div>
      )}
    </div>
  );
}

// ————————————————————————— kill —————————————————————————

// The four levels, in escalation order. Keys, not text: the table is built at
// script-eval time, before initI18n() resolves.
const AGENT_KILL_STEPS = [
  { level: 1, nameKey: 'agent.kill.level1.name', bodyKey: 'agent.kill.level1.body' },
  { level: 2, nameKey: 'agent.kill.level2.name', bodyKey: 'agent.kill.level2.body' },
  { level: 3, nameKey: 'agent.kill.level3.name', bodyKey: 'agent.kill.level3.body' },
  { level: 4, nameKey: 'agent.kill.level4.name', bodyKey: 'agent.kill.level4.body' },
];

// AgentKillOutcome renders the response HONESTLY.
//
// The rule this exists for: a partial failure must never look like a success.
// The failures get their own block, in sell red, each with the backend's own
// reason — and a flatten that ran out of PASSES rather than out of work
// (`converged: false`) is called out first, because that is the difference
// between "you are flat" and "you are probably not".
function AgentKillOutcome({ res, theme, onDone }) {
  const failed = res.failed || [];
  const cancelled = res.cancelled || [];
  const closed = res.closed || [];
  const notConverged = res.converged === false;
  const bad = failed.length > 0 || notConverged;

  const itemLabel = (fail) => {
    if (fail.kind === 'order') return t('agent.kill.result.item.order', { id: bidiIsolate('#' + fail.id) });
    if (fail.kind === 'position') return t('agent.kill.result.item.position', { id: bidiIsolate('#' + fail.id) });
    return t('agent.kill.result.item.sweep');
  };
  const line = (text, dim) => (
    <div style={{ display: 'flex', gap: 8, padding: '4px 0', fontSize: 13, color: dim ? theme.textTer : theme.textSec, lineHeight: 1.45 }}>
      <span style={{ color: dim ? theme.textTer : theme.buy, flexShrink: 0 }}>·</span>
      <span>{text}</span>
    </div>
  );

  return (
    <div>
      <div style={{ fontSize: 16, fontWeight: 700, color: bad ? theme.sell : theme.buy, marginBottom: 10 }}>
        {bad ? t('agent.kill.result.partialTitle') : t('agent.kill.result.title')}
      </div>

      {notConverged && (
        <div style={{ marginBottom: 12, padding: '11px 13px', borderRadius: 12, background: `${theme.sell}18`, border: `1px solid ${theme.sell}`, lineHeight: 1.5 }}>
          <div style={{ fontSize: 13, fontWeight: 700, color: theme.sell }}>{tn('agent.kill.result.notConverged', res.passes || 0)}</div>
          <div style={{ fontSize: 12, color: theme.textSec, marginTop: 3 }}>{t('agent.kill.result.notConvergedNote')}</div>
        </div>
      )}

      <div style={{ marginBottom: failed.length ? 12 : 4 }}>
        {res.streamsClosed > 0 ? line(tn('agent.kill.result.streams', res.streamsClosed)) : line(t('agent.kill.result.noStreams'), true)}
        {res.level >= 2 && (res.revoked ? line(t('agent.kill.result.revoked')) : line(t('agent.kill.result.alreadyRevoked'), true))}
        {res.level === 1 && line(t('agent.kill.result.tokenValid'), true)}
        {res.level >= 3 && (cancelled.length ? line(tn('agent.kill.result.cancelled', cancelled.length)) : line(t('agent.kill.result.noneCancelled'), true))}
        {res.level >= 4 && (closed.length ? line(tn('agent.kill.result.closed', closed.length)) : line(t('agent.kill.result.noneClosed'), true))}
        {res.level >= 4 && res.converged === true && line(t('agent.kill.result.converged'), true)}
      </div>

      {failed.length > 0 && (
        <div style={{ borderRadius: 12, background: `${theme.sell}14`, border: `0.5px solid ${theme.sell}55`, overflow: 'hidden', marginBottom: 14 }}>
          <div style={{ padding: '9px 12px', fontSize: 12.5, fontWeight: 700, color: theme.sell, borderBottom: `0.5px solid ${theme.sell}33` }}>
            {tn('agent.kill.result.failed', failed.length)}
          </div>
          {failed.map((fail, i) => (
            <div key={i} style={{ padding: '9px 12px', borderTop: i ? `0.5px solid ${theme.sell}22` : 'none' }}>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: theme.text }}>{itemLabel(fail)}</div>
              {/* The backend's own words. This body goes to the account holder,
                  not to a third party, so there is nothing to redact — and a
                  reason they cannot read is a failure they cannot act on. */}
              <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 2, lineHeight: 1.45, overflowWrap: 'anywhere' }}>{fail.reason}</div>
            </div>
          ))}
          <div style={{ padding: '9px 12px', fontSize: 11.5, color: theme.textSec, borderTop: `0.5px solid ${theme.sell}22`, lineHeight: 1.45 }}>
            {t('agent.kill.result.failedNote')}
          </div>
        </div>
      )}

      <ConfirmButton theme={theme} color={theme.accent} onClick={onDone}>{t('agent.kill.result.done')}</ConfirmButton>
    </div>
  );
}

// AgentKillSheet — the escalating control (PD10).
//
// The escalation must be LEGIBLE: all four levels are shown together, each
// stating what it ADDS to the one above it, and L3/L4 carry a live count of
// what they would act on — so the difference between "stop it trading" and
// "close my positions" is a number, not a word.
//
// L4 confirms TWICE, and the two confirmations deliberately say different
// things rather than asking the same question twice: the first is what
// flattening MEANS (positions close at market, P&L is realised now), the second
// is that there is no undo. Two identical dialogs train a reflex; two different
// ones make the second one get read.
//
// ——— why one double-TAP must not walk through both ———
//
// It did, and the phone was the worse of the two shells: both phases rendered
// the same markup at the same position, so React kept the DOM node and only
// swapped its label, and the CTA moved ZERO pixels between them — a
// pixel-identical hit target, elementFromPoint at the first CTA's centre
// returning the second. A double-tap, the commonest input error there is when
// someone is panicking at a phone, closed every position the agent had opened
// without ever showing the "there is no undo" dialog.
//
// Three layers, matching the desktop: `key={phase}` so the node remounts,
// AGENT_KILL_ARM_MS so a freshly-arrived confirmation's CTA is inert for half a
// second (BOTH of them — a tap that skips from the pick sheet to the last
// confirmation is a human who never read the first), and the two buttons
// STACKED with their order swapped between the confirmations.
//
// Stacked rather than side by side, on this shell only, because side by side
// did not separate them: cancel is auto-width, so swapping the row moved the
// CTA by 94px while the CTA itself is 276px wide, and elementFromPoint at the
// first CTA's centre still landed on the second (measured). The sheet is
// bottom-anchored, so stacking puts confirm1's CTA on the upper row and
// confirm2's on the lower one — a full button height apart, with cancel
// occupying whichever row the previous CTA used.
const AGENT_KILL_ARM_MS = 500;

// killTint maps killPreviewText's semantic tone onto this shell's palette.
const killTint = (tone, theme) => (tone === 'danger' ? theme.sell : tone === 'warn' ? DEMO_COLOR : theme.textTer);

function AgentKillSheet({ tok, theme, onCancel, onBusy, onDone }) {
  const [level, setLevel] = React.useState(1);
  const [phase, setPhase] = React.useState('pick'); // pick | confirm1 | confirm2 | busy | done
  const [res, setRes] = React.useState(null);
  const [err, setErr] = React.useState(null);
  // The server's own dry run of the sweep, once per sheet — the client could
  // not answer this honestly for a token on an account this phone is not
  // streaming, and previewed {0,0}. See useKillPreview.
  const preview = useKillPreview(tok.id);

  // Layer 2: a confirmation's CTA arms a beat after the sheet appears.
  const [armed, setArmed] = React.useState(true);
  React.useEffect(() => {
    if (phase !== 'confirm1' && phase !== 'confirm2') { setArmed(true); return undefined; }
    setArmed(false);
    const id = setTimeout(() => setArmed(true), AGENT_KILL_ARM_MS);
    return () => clearTimeout(id);
  }, [phase]);

  // N1: while the POST is in flight the sheet must not be dismissable — the
  // kill completes either way and the outcome report, including a partial
  // failure, would never be shown. The dismissal lives on the shell above, so
  // the state has to travel up.
  React.useEffect(() => {
    if (!onBusy) return undefined;
    onBusy(phase === 'busy');
    return () => onBusy(false);
  }, [phase, onBusy]);

  const fire = async (lvl) => {
    setPhase('busy'); setErr(null);
    try {
      const out = await apiKillAgentSession(tok.id, lvl);
      setRes(out);
      setPhase('done');
      onDone();
    } catch (e) {
      setErr(String((e && e.message) || e));
      setPhase('pick');
    }
  };

  if (phase === 'done' && res) {
    return <AgentKillOutcome res={res} theme={theme} onDone={onCancel} />;
  }

  if (phase === 'confirm1' || phase === 'confirm2') {
    const second = phase === 'confirm2';
    const shot = killPreviewText(preview, 'positions');
    const cancelBtn = (
      <div key="cancel" onClick={() => setPhase('pick')} style={{
        padding: '16px 20px', borderRadius: 14, textAlign: 'center', fontWeight: 700, fontSize: 15,
        background: theme.bgSubtle, color: theme.text, cursor: 'pointer',
      }}>{t('common.action.cancel')}</div>
    );
    const confirmBtn = (
      <div key="confirm">
        <ConfirmButton theme={theme} color={theme.sell} disabled={!armed}
          onClick={() => (second ? fire(4) : setPhase('confirm2'))}>
          {second ? t('agent.kill.confirm2.cta') : t('agent.kill.confirm1.cta')}
        </ConfirmButton>
      </div>
    );
    return (
      // Layer 1. Without the key React reuses the DOM node and one tap counts
      // twice.
      <div key={phase}>
        <div style={{ padding: '4px 0 14px' }}>
          <div style={{ fontSize: 18, fontWeight: 700, color: theme.sell, letterSpacing: -0.3 }}>
            {second ? t('agent.kill.confirm2.title') : t('agent.kill.confirm1.title')}
          </div>
          <div style={{ fontSize: 13.5, color: theme.text, marginTop: 8, lineHeight: 1.55 }}>
            {second ? t('agent.kill.confirm2.body') : t('agent.kill.confirm1.body', { name: tok.name })}
          </div>
          {/* Always rendered, in every preview state: an absent box used to be
              how "I could not check" looked, which is exactly how "there is
              nothing to close" looked too. */}
          <div style={{
            marginTop: 12, padding: '11px 13px', borderRadius: 12,
            background: preview.positions > 0 ? `${theme.sell}18` : theme.bgSubtle,
            border: `0.5px solid ${preview.positions > 0 ? `${theme.sell}55` : theme.border}`,
          }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: killTint(shot.tone, theme) }}>{shot.text}</div>
            {/* The note explains a figure. With no figure — the preview failed,
                or has not landed — "checked with the server just now" under
                "could not check" is a contradiction, so it waits. */}
            {!preview.loading && !preview.err && (
              <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 4, lineHeight: 1.45 }}>{t('agent.kill.previewNote')}</div>
            )}
          </div>
        </div>
        {/* Layer 3. Stacked, and the order flips between the two: confirm1's
            CTA is the upper row, confirm2's the lower one, so the second tap of
            a double-tap cannot land on the next dialog's CTA. */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {second ? [cancelBtn, confirmBtn] : [confirmBtn, cancelBtn]}
        </div>
      </div>
    );
  }

  const busy = phase === 'busy';
  return (
    <div>
      <div style={{ fontSize: 12.5, color: theme.textSec, lineHeight: 1.5, marginBottom: 12 }}>{t('agent.kill.intro')}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
        {AGENT_KILL_STEPS.map(step => {
          const on = level === step.level;
          // Everything at or below the chosen level is CONTAINED by it, and the
          // list shows that: choosing L4 lights the three steps it includes
          // rather than only itself. The containment is the whole model.
          const within = level >= step.level;
          const danger = step.level === 4;
          const edge = on ? (danger ? theme.sell : theme.accent) : within ? theme.borderStrong : theme.border;
          return (
            <div key={step.level} onClick={() => setLevel(step.level)} style={{
              display: 'flex', alignItems: 'flex-start', gap: 10, padding: '11px 12px', borderRadius: 12,
              background: on ? (danger ? `${theme.sell}14` : theme.bgCard) : theme.bgCard,
              border: `1.5px solid ${edge}`, cursor: 'pointer',
            }}>
              <span style={{
                width: 20, height: 20, borderRadius: 6, flexShrink: 0, marginTop: 1,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 11, fontWeight: 700, fontVariantNumeric: 'tabular-nums',
                color: within ? (danger && on ? theme.sell : theme.accent) : theme.textTer,
                border: `1.5px solid ${within ? (danger && on ? theme.sell : theme.accent) : theme.borderStrong}`,
              }}>{step.level}</span>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 700, color: danger && on ? theme.sell : theme.text, letterSpacing: -0.2 }}>{t(step.nameKey)}</div>
                <div style={{ fontSize: 11.5, color: theme.textSec, marginTop: 4, lineHeight: 1.45 }}>{t(step.bodyKey)}</div>
                {/* The two levels that ACT carry a line in every preview state
                    — a count, "checking", or "could not check". Never nothing:
                    silence is what made an unpreviewable L4 look idle. */}
                {(step.level === 3 || step.level === 4) && (() => {
                  const line = killPreviewText(preview, step.level === 3 ? 'orders' : 'positions');
                  return (
                    <div style={{ fontSize: 11.5, color: killTint(line.tone, theme), marginTop: 5, fontWeight: 600 }}>{line.text}</div>
                  );
                })()}
              </div>
            </div>
          );
        })}
      </div>

      {!preview.loading && !preview.err && (
        <div style={{ fontSize: 11, color: theme.textTer, marginTop: 10, lineHeight: 1.45 }}>{t('agent.kill.previewNote')}</div>
      )}

      {err && (
        <div style={{ marginTop: 12, padding: '10px 12px', borderRadius: 11, background: `${theme.sell}18`, color: theme.sell, fontSize: 12.5, fontWeight: 600 }}>
          {t('agent.kill.failed')}
        </div>
      )}

      <div style={{ display: 'flex', gap: 8, marginTop: 16 }}>
        <div onClick={onCancel} style={{
          padding: '16px 20px', borderRadius: 14, textAlign: 'center', fontWeight: 700, fontSize: 15,
          background: theme.bgSubtle, color: theme.text, cursor: 'pointer',
        }}>{t('common.action.cancel')}</div>
        <div style={{ flex: 1 }}>
          <ConfirmButton
            theme={theme}
            color={level === 4 ? theme.sell : theme.accent}
            disabled={busy}
            onClick={() => (level === 4 ? setPhase('confirm1') : fire(level))}
          >{busy ? t('agent.kill.busy') : t(AGENT_KILL_STEPS[level - 1].nameKey)}</ConfirmButton>
        </div>
      </div>
    </div>
  );
}

// ————————————————————————— the sheet —————————————————————————

// ApiAccessSheet — one screen for everything automation on this phone.
//
// A React.memo barrier: the mobile App re-renders on every price tick, and the
// panel must not come along for the ride. That holds only while every prop is
// stable, which is why App memoises its theme object and useCallbacks its
// close handler — a fresh object in either would defeat this entirely.
const ApiAccessSheet = React.memo(function ApiAccessSheet({ accounts, theme, initialView, onClose }) {
  const [state, setState] = React.useState({ loading: true, rows: [], live: false, err: null });
  // list | mint | reveal | feed | kill
  const [mode, setMode] = React.useState(initialView === 'feed' ? 'feed' : 'list');
  const [revealed, setRevealed] = React.useState(null);
  const [filter, setFilter] = React.useState(0);
  const [killing, setKilling] = React.useState(null);
  // True only while a kill POST is in flight. See `dismiss`/`back` below.
  const [killBusy, setKillBusy] = React.useState(false);

  const load = React.useCallback(async () => {
    try {
      const res = await apiAgentSessions();
      setState({ loading: false, rows: res.sessions || [], live: !!res.liveTradingEnabled, err: null });
    } catch (e) {
      setState(s => ({ loading: false, rows: s.rows, live: s.live, err: String((e && e.message) || e) }));
    }
  }, []);

  // Poll while the sessions list is up: the caps meters are live state, and a
  // human watching a looping agent expects to see them move. Only in `list`,
  // because the other modes do not show a meter and a background refetch under
  // a confirmation dialog is work nobody asked for.
  React.useEffect(() => {
    load();
    if (mode !== 'list') return undefined;
    const id = setInterval(load, 5000);
    return () => clearInterval(id);
  }, [load, mode]);

  const showFeed = React.useCallback((tok) => { setFilter(tok.id); setMode('feed'); }, []);
  const onFilter = React.useCallback((id) => { setFilter(id); setMode('feed'); }, []);

  const title = mode === 'mint' ? t('apiaccess.mint.title')
    : mode === 'reveal' ? t('apiaccess.reveal.sheetTitle')
    : mode === 'feed' ? t('agent.feed.title')
    : mode === 'kill' && killing ? t('agent.kill.forName', { name: killing.name })
    : t('agent.title');

  // Two things a backdrop tap must not throw away: the plaintext token, which
  // exists exactly once and has no way back, and a kill that is mid-flight —
  // N1. The POST completes either way, so dismissing it does not stop
  // anything; it only hides the outcome report, which is the one honest
  // account of what the sweep managed and what it did not.
  const dismiss = (mode === 'reveal' || killBusy) ? () => {} : onClose;
  const back = () => {
    if (killBusy) return;
    if (mode === 'reveal') { setRevealed(null); setMode('list'); load(); return; }
    if (mode === 'list') { onClose(); return; }
    setKilling(null);
    setMode('list');
  };

  return (
    <SheetShell theme={theme} onClose={dismiss}>
      <SheetHeader theme={theme} title={title} onClose={back} />

      {mode === 'reveal' && revealed && (
        <TokenReveal token={revealed} theme={theme} onDone={() => { setRevealed(null); setMode('list'); load(); }} />
      )}

      {mode === 'mint' && (
        <MintTokenForm
          accounts={accounts} theme={theme} liveTradingEnabled={state.live}
          onCancel={() => setMode('list')}
          onMinted={(res) => { setRevealed(res.token); setMode('reveal'); }}
        />
      )}

      {mode === 'feed' && (
        <AgentFeedView tokenId={filter} sessions={state.rows} theme={theme} onFilter={onFilter} />
      )}

      {mode === 'kill' && killing && (
        <AgentKillSheet
          tok={killing} theme={theme}
          onCancel={() => { setKilling(null); setMode('list'); }}
          onBusy={setKillBusy}
          onDone={load}
        />
      )}

      {mode === 'list' && (
        <div>
          <div style={{ fontSize: 12.5, color: theme.textSec, lineHeight: 1.5, marginBottom: 6 }}>
            {t('agent.blurb')}
          </div>
          <div style={{ fontSize: 11.5, color: theme.textTer, lineHeight: 1.45, marginBottom: 14 }}>
            {t('agent.caps.readOnly')}
          </div>

          <div onClick={() => { setFilter(0); setMode('feed'); }} style={{
            display: 'flex', alignItems: 'center', gap: 8, padding: '12px 13px', marginBottom: 12,
            borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}`, cursor: 'pointer',
          }}>
            <span style={{ flex: 1, fontSize: 13.5, fontWeight: 600, color: theme.text }}>{t('agent.feed.title')}</span>
            <AgentUnreadPill theme={theme} />
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" style={{ transform: i18nIsRTL() ? 'rotate(90deg)' : 'rotate(-90deg)', flexShrink: 0 }}>
              <path d="M6 9l6 6 6-6" stroke={theme.textSec} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </div>

          {state.loading && !state.rows.length && (
            <div style={{ padding: '18px 0', textAlign: 'center', fontSize: 13, color: theme.textTer }}>{t('apiaccess.loading')}</div>
          )}

          {state.err && (
            <div style={{ padding: '10px 12px', borderRadius: 11, background: `${theme.sell}18`, color: theme.sell, fontSize: 12.5, fontWeight: 600, marginBottom: 12 }}>
              {t('agent.error.sessions')}
            </div>
          )}

          {!state.loading && !state.rows.length && !state.err && (
            <div style={{ padding: '22px 12px', textAlign: 'center', borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}`, marginBottom: 14 }}>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: theme.text }}>{t('agent.empty.title')}</div>
              <div style={{ fontSize: 12, color: theme.textSec, marginTop: 4, lineHeight: 1.45 }}>{t('agent.empty.body')}</div>
            </div>
          )}

          {!!state.rows.length && (
            <div style={{ borderRadius: 13, background: theme.bgCard, border: `0.5px solid ${theme.border}`, overflow: 'hidden', marginBottom: 14 }}>
              {/* `tok`, not `t` — `t` is the global translator. */}
              {state.rows.map((tok, i) => (
                <AgentSessionRow
                  key={tok.id} tok={tok} theme={theme} first={i === 0}
                  onFeed={showFeed}
                  onKill={(row) => { setKilling(row); setMode('kill'); }}
                />
              ))}
            </div>
          )}

          <ConfirmButton theme={theme} color={theme.accent} onClick={() => setMode('mint')}>{t('apiaccess.mint.cta')}</ConfirmButton>
        </div>
      )}
    </SheetShell>
  );
});

// AgentUnreadPill subscribes to the agent store only, so an unread count can
// move without the sheet around it re-rendering.
function AgentUnreadPill({ theme }) {
  const store = useAgentStore();
  if (!store.unread) return null;
  return (
    <span
      aria-label={tn('agent.unread', store.unread)}
      style={{
        minWidth: 20, padding: '1px 6px', borderRadius: 10, background: theme.accent, color: '#fff',
        fontSize: 11, fontWeight: 700, textAlign: 'center',
        direction: 'ltr', unicodeBidi: 'isolate', flexShrink: 0,
      }}
    >{store.unread > 99 ? '99+' : store.unread}</span>
  );
}

Object.assign(window, {
  INITIAL_ACCOUNTS, CabinetTab, AccountSwitcher, OpenAccountSheet,
  AccountActionSheet, TransferSheet, DepositSheet, WithdrawSheet, ResetSheet,
  ApiAccessSheet, ScopeBadge,
  acctColor, fmtBal,
  // Track P P3 — the agent-sessions screen's parts, exported so index.html can
  // mount the unread pill on the header bell without re-deriving the store.
  AgentSessionRow, AgentFeedView, AgentKillSheet, AgentUnreadPill, AgentMeter,
});
