/* My account — the one page every signed-in person gets.
   Signed out: sign-in. Pending: waiting for approval. Approved: connect exchange
   accounts (Bybit live-capable; Binance / Coinbase / Kraken portfolio), choose which
   strategies each Bybit account follows, and see balances,
   positions and history. Strategies are only ever shown by their public name.
   Reuses fmtUsd / fmtNum / fmtStamp / EquityChart / Allocation / SignalHistorySubrow
   (widgets-signals.jsx). */

function W_AccountSection({ user }) {
  if (!user) {
    return (
      <GoogleSignInGate title="My account" subtitle="Sign in to continue.">
        {null}
      </GoogleSignInGate>
    );
  }

  if (user.role === 'pending') {
    return (
      <div className="card" style={{ maxWidth: 460, margin: '48px auto' }}>
        <div className="card-b" style={{ textAlign: 'center', padding: '32px 26px' }}>
          <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 8 }}>Waiting for approval</div>
          <div style={{ fontSize: 12.5, color: 'var(--text-3)', lineHeight: 1.6 }}>
            You're signed in as <span style={{ color: 'var(--text)' }}>{user.email}</span>.
            An admin needs to approve your access before you can connect an exchange account.
          </div>
        </div>
      </div>
    );
  }

  return <AcctPage isAdmin={user.role === 'admin'} role={user.role} />;
}

// ── shared bits ─────────────────────────────────────────────────────────────
function acctApi(path, method, body) {
  return fetch('/api/me' + path, {
    method: method || 'GET', credentials: 'include',
    headers: body ? { 'Content-Type': 'application/json' } : undefined,
    body: body ? JSON.stringify(body) : undefined,
  }).then(r => r.json()).catch(e => ({ ok: false, error: String(e.message || e) }));
}

const acctInp = {
  padding: '7px 9px', borderRadius: 6, fontSize: 12.5, width: '100%', boxSizing: 'border-box',
  background: 'var(--bg-elev)', border: '1px solid var(--border-soft)',
  color: 'var(--text)', fontFamily: 'var(--font-mono)',
};
const acctLabelStyle = {
  fontSize: 9.5, color: 'var(--text-4)', letterSpacing: '0.08em',
  textTransform: 'uppercase', marginBottom: 4, display: 'block',
};
const acctHint = { fontSize: 11, color: 'var(--text-4)', lineHeight: 1.5, marginTop: 4 };

function acctBtn(kind, disabled) {
  const base = {
    padding: '7px 13px', borderRadius: 7, fontSize: 12, fontFamily: 'var(--font-sans)',
    cursor: disabled ? 'wait' : 'pointer', opacity: disabled ? 0.6 : 1, whiteSpace: 'nowrap',
  };
  if (kind === 'primary') return { ...base, background: 'var(--gold)', border: 'none', color: 'var(--bg-base)', fontWeight: 600 };
  if (kind === 'danger') return { ...base, background: 'var(--bear)', border: 'none', color: '#fff', fontWeight: 600 };
  if (kind === 'danger-outline') return { ...base, background: 'transparent', border: '1px solid var(--bear)', color: 'var(--bear)' };
  return { ...base, background: 'var(--bg-elev)', border: '1px solid var(--border-soft)', color: 'var(--text-2)' };
}

function AcctModal({ title, tone, onClose, busy, children }) {
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)', zIndex: 50,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }} onClick={busy ? undefined : onClose}>
      <div onClick={e => e.stopPropagation()} className="card" style={{ width: 420, maxWidth: '100%' }}>
        <div className="card-h" style={{ justifyContent: 'space-between' }}>
          <span style={{ fontWeight: 600, fontSize: 13, color: tone || 'var(--text)' }}>{title}</span>
          <button onClick={onClose} disabled={busy} aria-label="Close" style={{
            background: 'transparent', border: 'none', cursor: 'pointer', color: 'var(--text-4)', fontSize: 16, padding: 0,
          }}>×</button>
        </div>
        <div className="card-b" style={{ fontSize: 12.5, lineHeight: 1.6 }}>{children}</div>
      </div>
    </div>
  );
}

const ACCT_STATUS = {
  active: ['connected', 'bull'], pending: ['checking key', 'gold'],
  error: ['key problem', 'bear'], disabled: ['removed', 'neutral'],
};

const VENUE_LABEL = { bybit: 'Bybit', binance: 'Binance', coinbase: 'Coinbase', kraken: 'Kraken' };
function acctCaps(account) {
  // Fail closed: missing bag ⇒ Portfolio (read-only), never full Bybit trade/follow.
  return (account && account.capabilities) || {
    read: true, trade: false, follow: false, paperLive: false, envDemoReal: false, close: false,
  };
}
function acctIsPortfolio(account) {
  const c = acctCaps(account);
  return !c.trade || !c.follow;
}

// ── browser notifications ───────────────────────────────────────────────────
// window.profitPush (public/push-client.js) already does the actual service-worker
// registration / subscribe-unsubscribe work; this is just the one place in the UI
// that turns browser push on/off at all. Which strategies actually notify this
// user once it's on is controlled per-strategy — see NotifyToggle on each
// strategy page (app.jsx), default on for strategies the user's account follows.
function NotificationsCard() {
  const [status, setStatus] = React.useState(null); // null = loading
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const refresh = React.useCallback(() => {
    if (!window.profitPush) { setStatus({ supported: false }); return; }
    window.profitPush.status().then(setStatus).catch(() => setStatus({ supported: false }));
  }, []);
  React.useEffect(() => { refresh(); }, [refresh]);

  if (!status || !status.supported) return null; // don't clutter unsupported browsers

  async function toggle() {
    setBusy(true); setErr(null);
    try {
      if (status.subscribed) await window.profitPush.disable();
      else await window.profitPush.enable();
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setBusy(false);
      refresh();
    }
  }

  const blocked = status.permission === 'denied';

  return (
    <div className="card" style={{ marginBottom: 14 }}>
      <div className="card-b" style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '12px 16px' }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Browser notifications</div>
          <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 2 }}>
            {blocked
              ? 'Blocked in this browser — allow notifications for this site to enable.'
              : status.subscribed
                ? 'On — you’ll get a browser notification for order fills, edits, and closes.'
                : 'Off — turn on to get a browser notification when an order fills, is edited, or closes.'}
          </div>
          {err && <div style={{ fontSize: 11, color: 'var(--bear)', marginTop: 4 }}>{err}</div>}
        </div>
        <button onClick={toggle} disabled={busy || blocked} style={acctBtn(status.subscribed ? 'ghost' : 'primary', busy || blocked)}>
          {busy ? '…' : status.subscribed ? 'Turn off' : 'Turn on'}
        </button>
      </div>
    </div>
  );
}

// ── page ────────────────────────────────────────────────────────────────────
function AcctPage({ isAdmin, role }) {
  const [accounts, setAccounts] = React.useState(null);
  const [selectedId, setSelectedId] = React.useState(null);
  const [adding, setAdding] = React.useState(false);
  const [error, setError] = React.useState(null);

  const load = React.useCallback(async (selectId) => {
    const j = await acctApi('/accounts');
    if (!j.ok) { setError(j.error || 'Could not load your accounts'); return; }
    setError(null);
    setAccounts(j.data);
    setSelectedId(cur => {
      const want = selectId || cur;
      return j.data.some(a => a.id === want) ? want : (j.data[0] ? j.data[0].id : null);
    });
  }, []);

  React.useEffect(() => { load(); }, [load]);

  if (error) return <div className="card"><div className="card-b" style={{ color: 'var(--bear)', fontSize: 12.5 }}>{error}</div></div>;
  if (!accounts) return <div style={{ color: 'var(--text-4)', fontSize: 12, padding: 20 }}>Loading…</div>;

  const selected = accounts.find(a => a.id === selectedId);

  if (!accounts.length || adding) {
    return (
      <div>
        <NotificationsCard />
        {!accounts.length && <AcctGettingStarted />}
        <AcctConnectForm
          onCancel={accounts.length ? () => setAdding(false) : null}
          onDone={(acct) => { setAdding(false); load(acct && acct.id); }} />
        {isAdmin && <PipelineSettings />}
      </div>
    );
  }

  return (
    <div>
      <NotificationsCard />
      <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 14, flexWrap: 'wrap' }}>
        {accounts.map(a => {
          const on = a.id === selectedId;
          return (
            <button key={a.id} onClick={() => setSelectedId(a.id)} style={{
              padding: '6px 13px', borderRadius: 7, cursor: 'pointer', fontSize: 12.5, fontFamily: 'var(--font-sans)',
              background: on ? 'var(--bg-card)' : 'transparent', color: on ? 'var(--text)' : 'var(--text-3)',
              border: '1px solid ' + (on ? 'var(--border)' : 'var(--border-soft)'), fontWeight: on ? 600 : 400,
            }}>
              {a.label}
              <span style={{ marginLeft: 7, fontSize: 10, color: 'var(--text-3)' }}>{VENUE_LABEL[a.venue] || a.venue || 'Bybit'}</span>
              {acctCaps(a).envDemoReal && (
                <span style={{ marginLeft: 7, fontSize: 10, color: a.env === 'real' ? 'var(--bear)' : 'var(--gold)' }}>{a.env === 'real' ? 'Real money' : 'Demo'}</span>
              )}
              {acctIsPortfolio(a) && <span style={{ marginLeft: 5, fontSize: 10, color: 'var(--text-4)' }}>Portfolio</span>}
              {a.mode === 'live' && acctCaps(a).trade && <span style={{ marginLeft: 5, fontSize: 10, color: 'var(--bear)', fontWeight: 700 }}>Live orders</span>}
            </button>
          );
        })}
        <button onClick={() => setAdding(true)} style={acctBtn('ghost')}>+ Add account</button>
      </div>
      {selected && <AcctDetail key={selected.id} account={selected} role={role} onChanged={() => load(selected.id)} onRemoved={() => load()} />}
      {isAdmin && <PipelineSettings />}
    </div>
  );
}

// ── getting started ─────────────────────────────────────────────────────────
// Shown while the user has no account yet. The strategy checkboxes live inside a connected
// account's page, so without this the page is just a form and nothing says what comes next.
function AcctGettingStarted() {
  const step = (n, title, body) => (
    <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
      <span style={{
        flexShrink: 0, width: 22, height: 22, borderRadius: 11, background: 'var(--bg-elev)',
        border: '1px solid var(--border-soft)', color: 'var(--gold)', fontSize: 11.5, fontWeight: 700,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>{n}</span>
      <div style={{ fontSize: 12.5, lineHeight: 1.6, color: 'var(--text-2)' }}>
        <div style={{ fontWeight: 600, color: 'var(--text)' }}>{title}</div>
        <div style={{ color: 'var(--text-3)' }}>{body}</div>
      </div>
    </div>
  );
  return (
    <div className="card" style={{ marginBottom: 14 }}>
      <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Getting started</span></div>
      <div className="card-b" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {step(1, 'Connect an exchange account',
          <span>
            Pick your exchange below and paste an API key. Where the exchange offers an account type, choose
            {' '}<b>Demo</b> to try it with practice funds, or <b>Real money</b> when you are ready. Create the
            key with the permissions the screen asks for and <b>without</b> Withdrawal: a key that can
            withdraw is refused.
          </span>)}
        {step(2, 'Choose the strategies to follow',
          <span>
            Once the account is connected, open it. Accounts marked <b>Live-capable · Follow strategies</b> have
            a <b>Strategies</b> card: tick the strategies you want, set how much to risk per trade, and press
            {' '}<b>Save</b>. Only setups posted after you turn a strategy on are copied, never older ones.
            Accounts marked <b>Portfolio · not for live copy</b> show your balances and holdings only.
          </span>)}
        {step(3, 'Start in paper mode',
          <span>
            A new account that can follow strategies only simulates: trades are tracked, but no order is sent
            to the exchange. When you are happy with what you see, switch the account to <b>live</b> in its
            settings.
          </span>)}
        <div style={{ ...acctHint, marginTop: 0 }}>
          Not ready to connect an account? Every strategy page in the sidebar already shows that strategy's
          full ledger and results, with nothing to set up.
        </div>
      </div>
    </div>
  );
}

// ── pipeline settings (admin) ───────────────────────────────────────────────
// The `control` table: pipeline-wide settings shared by every trader and account. They used
// to sit on each strategy page, which suggested they were per strategy.
function PipelineSettings() {
  const [data, setData] = React.useState({ control: [], audit: [] });
  const [draft, setDraft] = React.useState({});
  const [msg, setMsg] = React.useState('');
  const rows = data.control || [];
  const audit = data.audit || [];
  const onChange = React.useCallback(() => {
    fetch('/api/signals/control').then(r => r.json())
      .then(j => { if (j && j.ok) setData(j.data); }).catch(() => {});
  }, []);
  React.useEffect(() => { onChange(); }, [onChange]);

  async function save(key, value) {
    setMsg('');
    const r = await fetch('/api/signals/control', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ key, value }),
    }).then(x => x.json()).catch(e => ({ ok: false, error: String(e) }));
    if (!r.ok) setMsg(r.error || 'failed');
    else { setMsg(`${key} updated`); onChange && onChange(); }
  }

  const inp = {
    padding: '5px 8px', borderRadius: 6, fontSize: 11, width: 130,
    background: 'var(--bg-elev)', border: '1px solid var(--border-soft)',
    color: 'var(--text)', fontFamily: 'var(--font-mono)',
  };

  return (
    <div className="card" style={{ marginTop: 18 }}>
      <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Pipeline settings · all strategies</span></div>
      <div className="card-b">
      <div style={{
        marginBottom: 12, padding: '8px 11px', borderRadius: 7, fontSize: 11,
        background: 'var(--bg-elev)', border: '1px solid var(--border-soft)', color: 'var(--text-3)',
      }}>
        Admin only. These apply to every trader and account at once. Paper/live, risk per trade,
        max concurrent and the daily loss cap are set per exchange account, above.
        Real-money orders also need <code>signals_live_enabled</code> + <code>SIGNALS_LIVE_OK</code> on
        the server — independent switches, deliberately not one button.
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 330px), 1fr))', gap: 9 }}>
        {rows.map(r => (
          <div key={r.key} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 9px', background: 'var(--bg-card)', borderRadius: 7, border: '1px solid var(--border-soft)' }}>
            <span style={{ fontSize: 11, color: 'var(--text-2)', flex: 1 }}>{r.key}</span>
            <input style={inp} defaultValue={r.value}
                   onChange={e => setDraft(d => ({ ...d, [r.key]: e.target.value }))} />
            <button onClick={() => save(r.key, draft[r.key] !== undefined ? draft[r.key] : r.value)}
                    disabled={draft[r.key] === undefined || draft[r.key] === r.value}
                    style={{
                      padding: '4px 9px', borderRadius: 5, fontSize: 10.5, cursor: 'pointer',
                      background: 'var(--bg-elev)', border: '1px solid var(--border-soft)',
                      color: 'var(--text-2)', fontFamily: 'var(--font-sans)',
                      opacity: draft[r.key] === undefined || draft[r.key] === r.value ? 0.4 : 1,
                    }}>save</button>
          </div>
        ))}
      </div>
      {msg && <div style={{ marginTop: 10, fontSize: 11, color: msg.includes('updated') ? 'var(--bull)' : 'var(--bear)' }}>{msg}</div>}
      {audit && audit.length > 0 && (
        <div style={{ marginTop: 16 }}>
          <div style={{ fontSize: 9.5, color: 'var(--text-4)', letterSpacing: '0.09em', textTransform: 'uppercase', marginBottom: 7 }}>Recent changes</div>
          {audit.slice(0, 8).map(a => (
            <div key={a.id} style={{ fontSize: 10.5, color: 'var(--text-3)', padding: '3px 0' }}>
              <span className="mono">{a.key}</span>
              <span style={{ color: 'var(--text-4)' }}> {a.old_value} → </span>
              <span className="mono" style={{ color: 'var(--text-2)' }}>{a.new_value}</span>
              <span style={{ color: 'var(--text-4)' }}> · {agoFrom(a.at)} ago · {a.source}</span>
            </div>
          ))}
        </div>
      )}
      </div>
    </div>
  );
}



// ── connect / replace key ───────────────────────────────────────────────────
function AcctConnectForm({ onDone, onCancel, existing }) {
  const lockedVenue = existing ? (existing.venue || 'bybit') : null;
  const [step, setStep] = React.useState(lockedVenue ? 'fields' : 'venue'); // venue | fields
  const [venue, setVenue] = React.useState(lockedVenue || null);
  const [form, setForm] = React.useState({ label: '', env: 'demo', api_key: '', api_secret: '' });
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [catalog, setCatalog] = React.useState(null);
  // Bybit: Futures (default) or Spot. Bybit UK offers spot only.
  const [product, setProduct] = React.useState('linear_perp');
  // A Bybit key that failed the futures check but reads spot: { account } to offer "Connect as Spot".
  const [suggestSpot, setSuggestSpot] = React.useState(null);

  React.useEffect(() => {
    acctApi('/venues').then(j => { if (j.ok) setCatalog(j.data); });
  }, []);

  const venueMeta = (catalog || []).find(v => v.id === venue)
    || (venue ? { id: venue, label: VENUE_LABEL[venue] || venue, capabilities: acctCaps({ venue }), help: [], fields: [] } : null);
  const products = (venueMeta && venueMeta.products) || null;
  const pickedSpot = !!products && product === 'spot';
  const caps = venueMeta
    ? ((pickedSpot && venueMeta.spot_capabilities) || venueMeta.capabilities || {}) : {};
  // Binance / Kraken / Coinbase connect as spot trading accounts, and Bybit when Spot is chosen:
  // longs only, no leverage.
  const isSpotTrading = !!(caps.trade && caps.leverage === false);
  const needsAttest = !!(venueMeta && (venueMeta.fields || []).some(f => f.id === 'no_withdraw_attested'));

  function pickVenue(id) {
    setVenue(id);
    setStep('fields');
    setErr(null);
    setForm({ label: '', env: 'demo', api_key: '', api_secret: '', spot_quote: '', no_withdraw_attested: false });
    setProduct('linear_perp');
    setSuggestSpot(null);
  }

  function backToVenue() {
    if (lockedVenue) return;
    setStep('venue');
    setVenue(null);
    setErr(null);
  }

  async function submit(e) {
    e.preventDefault();
    if (!venue) { setErr('Choose an exchange first.'); return; }
    if (!form.api_key.trim() || !form.api_secret.trim()) {
      setErr(`Enter all required fields for ${venueMeta.label}.`);
      return;
    }
    if (!existing && needsAttest && !form.no_withdraw_attested) {
      setErr('Confirm that this key does not have the "Withdraw funds" permission.');
      return;
    }
    setBusy(true); setErr(null);
    const body = existing
      ? { api_key: form.api_key, api_secret: form.api_secret }
      : {
          venue,
          label: form.label,
          env: caps.envDemoReal ? form.env : 'real',
          ...(products ? { product } : {}),
          api_key: form.api_key,
          api_secret: form.api_secret,
          ...(isSpotTrading ? { spot_quote: form.spot_quote || null, no_withdraw_attested: !!form.no_withdraw_attested } : {}),
        };
    const j = existing
      ? await acctApi(`/accounts/${existing.id}`, 'PATCH', body)
      : await acctApi('/accounts', 'POST', body);
    setBusy(false);
    if (!j.ok) { setErr(j.error || 'Could not save the key'); return; }
    // The futures check failed, but the same key trades spot: say so and offer one click.
    // Nothing is switched without it (Spot trades differently: longs only, no leverage).
    if (!existing && j.validation && j.validation.suggest_product === 'spot' && j.account) {
      setSuggestSpot({ account: j.account });
      setErr(j.validation.error);
      return;
    }
    onDone(j.account, j.validation);
  }

  async function connectAsSpot() {
    if (!suggestSpot) return;
    setBusy(true); setErr(null);
    const j = await acctApi(`/accounts/${suggestSpot.account.id}/connect-as-spot`, 'POST',
      { spot_quote: form.spot_quote || null });
    setBusy(false);
    if (!j.ok) { setErr(j.error || 'Could not switch it to Spot'); return; }
    setSuggestSpot(null);
    onDone(j.account, j.validation);
  }

  const env = existing ? existing.env : form.env;
  const venueLabel = venueMeta ? venueMeta.label : 'exchange';

  if (step === 'venue') {
    return (
      <div className="card" style={{ maxWidth: 640 }}>
        <div className="card-h"><h3>Connect an exchange account</h3></div>
        <div className="card-b">
          <div style={{ fontSize: 12.5, color: 'var(--text-3)', lineHeight: 1.6, marginBottom: 14 }}>
            Bybit trades futures (longs and shorts). Binance, Kraken and Coinbase trade spot: longs only,
            no leverage — short setups are skipped on those accounts.
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            {(catalog || [
              { id: 'bybit', label: 'Bybit', capabilities: { trade: true, follow: true, leverage: true } },
              { id: 'binance', label: 'Binance', capabilities: { trade: true, follow: true, leverage: false } },
              { id: 'coinbase', label: 'Coinbase', capabilities: { trade: true, follow: true, leverage: false } },
              { id: 'kraken', label: 'Kraken', capabilities: { trade: true, follow: true, leverage: false } },
            ]).map(v => {
              const portfolio = !v.capabilities?.trade || !v.capabilities?.follow;
              return (
                <button key={v.id} type="button" onClick={() => pickVenue(v.id)} style={{
                  ...acctBtn('ghost'), textAlign: 'left', padding: '14px 14px',
                  display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'flex-start',
                }}>
                  <span style={{ fontWeight: 600, fontSize: 14, color: 'var(--text)' }}>{v.label}</span>
                  <span style={{ fontSize: 11, color: 'var(--text-4)' }}>
                    {portfolio ? 'Portfolio · not for live copy'
                      : v.products ? 'Futures or Spot · Follow strategies'
                      : v.capabilities?.leverage === false ? 'Spot · longs only · Follow strategies'
                      : 'Futures · Follow strategies'}
                  </span>
                </button>
              );
            })}
          </div>
          {onCancel && (
            <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 14 }}>
              <button type="button" onClick={onCancel} style={acctBtn('ghost')}>Cancel</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  const helpLines = (pickedSpot && venueMeta.spot_help && venueMeta.spot_help.length)
    ? venueMeta.spot_help
    : (venueMeta && venueMeta.help && venueMeta.help.length)
    ? venueMeta.help
    : (venue === 'bybit'
      ? [
          env === 'demo'
            ? 'Switch Bybit to Demo Trading first, then open API management there.'
            : 'In Bybit, open API management → Create new key → System-generated.',
          'Permissions: Contracts / Derivatives — Orders and Positions only.',
          'Do not enable Withdrawal or transfers. A key that can withdraw is refused.',
          'Restrict it to this IP address: 89.167.49.240',
        ]
      : [
          `Create a read-only API key on ${venueLabel}.`,
          'Do not enable Withdrawal or transfers.',
          'Restrict by IP when the exchange allows it: 89.167.49.240',
        ]);

  return (
    <div className="card" style={{ maxWidth: 640 }}>
      <div className="card-h" style={{ justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' }}>
        <h3 style={{ margin: 0 }}>
          {existing ? `Replace API key · ${existing.label}` : `Connect ${venueLabel}`}
        </h3>
        <span className="chip neutral" style={{ fontSize: 10 }}>{venueLabel}</span>
      </div>
      <div className="card-b">
        {!existing && !caps.trade && (
          <div style={{
            padding: '9px 12px', marginBottom: 12, borderRadius: 8, fontSize: 12,
            background: 'var(--bg-elev)', border: '1px solid var(--border-soft)', color: 'var(--text-2)',
          }}>
            <b>Portfolio · not for live copy</b> — balances and holdings only.
          </div>
        )}
        {!existing && isSpotTrading && (
          <div style={{
            padding: '9px 12px', marginBottom: 12, borderRadius: 8, fontSize: 12,
            background: 'var(--bg-elev)', border: '1px solid var(--border-soft)', color: 'var(--text-2)', lineHeight: 1.55,
          }}>
            <b>Spot · longs only, no leverage.</b> Long setups are bought with the account's own cash and protected by a
            stop order on {venueLabel}; short setups are skipped. Only coins this account buys are ever sold.
          </div>
        )}
        <div style={{
          padding: '11px 13px', background: 'var(--bg-elev)', border: '1px solid var(--border-soft)',
          borderRadius: 8, fontSize: 12, color: 'var(--text-2)', lineHeight: 1.65, marginBottom: 16,
        }}>
          <div style={{ fontWeight: 600, color: 'var(--text)', marginBottom: 4 }}>
            Create {caps.trade ? 'a trade-only' : 'a read-only'} API key on {venueLabel}
          </div>
          <ol style={{ margin: 0, paddingLeft: 18 }}>
            {helpLines.map((line, i) => <li key={i}>{line}</li>)}
          </ol>
          <div style={{ marginTop: 6, color: 'var(--text-3)' }}>
            The key is encrypted as soon as it reaches the server and is never shown again — only its last 4 characters.
          </div>
        </div>

        <form onSubmit={submit}>
          {!existing && products && (
            <div style={{ marginBottom: 14 }}>
              <span style={acctLabelStyle}>Product</span>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
                {products.map(p => (
                  <button type="button" key={p.value} onClick={() => { setProduct(p.value); setSuggestSpot(null); setErr(null); }}
                    style={{
                      textAlign: 'left', padding: '9px 11px', borderRadius: 8, cursor: 'pointer',
                      background: product === p.value ? 'var(--bg-elev)' : 'transparent',
                      border: '1px solid ' + (product === p.value ? 'var(--gold)' : 'var(--border-soft)'),
                      color: 'var(--text)', fontFamily: 'var(--font-sans)',
                    }}>
                    <div style={{ fontWeight: 600, fontSize: 12.5 }}>{p.label}</div>
                    <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2 }}>{p.hint}</div>
                  </button>
                ))}
              </div>
            </div>
          )}
          {!existing && (
            <div style={{ display: 'grid', gridTemplateColumns: caps.envDemoReal ? '1fr 180px' : '1fr', gap: 12, marginBottom: 12 }}>
              <label>
                <span style={acctLabelStyle}>Name</span>
                <input style={acctInp} value={form.label} maxLength={60}
                       placeholder={`e.g. My ${venueLabel}`}
                       onChange={e => setForm({ ...form, label: e.target.value })} />
              </label>
              {caps.envDemoReal && (
                <label>
                  <span style={acctLabelStyle}>Account type</span>
                  <select style={acctInp} value={form.env} onChange={e => setForm({ ...form, env: e.target.value })}>
                    <option value="demo">Demo (practice funds)</option>
                    <option value="real">Real money</option>
                  </select>
                </label>
              )}
            </div>
          )}
          <label style={{ display: 'block', marginBottom: 12 }}>
            <span style={acctLabelStyle}>API key</span>
            <input style={acctInp} value={form.api_key} autoComplete="off" spellCheck={false} required
                   onChange={e => setForm({ ...form, api_key: e.target.value })} />
          </label>
          <label style={{ display: 'block', marginBottom: 14 }}>
            <span style={acctLabelStyle}>{venue === 'kraken' ? 'Private key / API secret' : 'API secret'}</span>
            <input style={acctInp} type="password" value={form.api_secret} autoComplete="new-password" spellCheck={false} required
                   onChange={e => setForm({ ...form, api_secret: e.target.value })} />
          </label>
          {!existing && isSpotTrading && (
            <label style={{ display: 'block', marginBottom: 14 }}>
              <span style={acctLabelStyle}>Buy with</span>
              <select style={acctInp} value={form.spot_quote || ''} onChange={e => setForm({ ...form, spot_quote: e.target.value })}>
                <option value="">Best available — USDT, then USDC, then USD</option>
                <option value="USDT">USDT only</option>
                <option value="USDC">USDC only</option>
                <option value="USD">USD only</option>
              </select>
              <div style={acctHint}>The stablecoin this account spends. A coin with no market in it is skipped.</div>
            </label>
          )}
          {!existing && needsAttest && (
            <label style={{ display: 'flex', gap: 9, alignItems: 'flex-start', marginBottom: 14, fontSize: 12.5, color: 'var(--text-2)', cursor: 'pointer' }}>
              <input type="checkbox" checked={!!form.no_withdraw_attested} style={{ marginTop: 2 }}
                     onChange={e => setForm({ ...form, no_withdraw_attested: e.target.checked })} />
              <span>This key does <b>not</b> have the "Withdraw funds" permission. {venueLabel} can't report it, so the
                key is also tested — one that can withdraw is refused.</span>
            </label>
          )}
          {err && <div style={{ color: 'var(--bear)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
          {suggestSpot && (
            <div style={{
              padding: '10px 12px', marginBottom: 12, borderRadius: 8, fontSize: 12, lineHeight: 1.55,
              background: 'var(--bg-elev)', border: '1px solid var(--gold)', color: 'var(--text-2)',
            }}>
              The same key works on Bybit <b>spot</b>. Connected as Spot, it buys long setups with its own cash
              (no leverage) and skips shorts. It starts in paper mode.
              <div style={{ marginTop: 8 }}>
                <button type="button" onClick={connectAsSpot} disabled={busy} style={acctBtn('primary', busy)}>
                  {busy ? 'Checking the key on Bybit spot…' : 'Connect as Spot'}
                </button>
              </div>
            </div>
          )}
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
            {!existing && !lockedVenue && (
              <button type="button" onClick={backToVenue} disabled={busy} style={acctBtn('ghost', busy)}>Back</button>
            )}
            {onCancel && <button type="button" onClick={onCancel} disabled={busy} style={acctBtn('ghost', busy)}>Cancel</button>}
            <button type="submit" disabled={busy} style={acctBtn('primary', busy)}>
              {busy ? `Checking the key with ${venueLabel}…` : existing ? 'Replace key' : 'Connect'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

// ── one account ─────────────────────────────────────────────────────────────
const PNL_PERIODS = [['1w', '1W'], ['1m', '1M'], ['3m', '3M'], ['6m', '6M'], ['1y', '1Y'], ['all', 'All']];

function pnlColor(v) { return v > 0 ? 'var(--bull)' : v < 0 ? 'var(--bear)' : 'var(--text)'; }

// Unrealized (now), realized (by period, with a small trend line) and entry
// slippage — the gap between what the trader called and the price we actually
// filled at, in % and $, so the pipeline's own latency has a visible cost.
function AcctPnlStrip({ account }) {
  const [period, setPeriod] = React.useState('1m');
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(null);

  // Unrealized is marked to the live price on every request, same as the ledger's
  // paper P&L — poll on the same 10s clock so it actually moves with the market.
  React.useEffect(() => {
    let alive = true;
    const fetchNow = () => {
      acctApi(`/accounts/${account.id}/pnl?period=${period}`).then(j => {
        if (!alive) return;
        if (j.ok) { setData(j.data); setErr(null); } else setErr(j.error || 'Could not load P&L');
      });
    };
    fetchNow(); // always — a freshly mounted widget loads regardless of tab visibility
    const id = setInterval(() => { if (!document.hidden) fetchNow(); }, 10000);
    return () => { alive = false; clearInterval(id); };
  }, [account.id, period]);

  const periodChips = (
    <span style={{ display: 'flex', gap: 3 }}>
      {PNL_PERIODS.map(([id, label]) => (
        <button key={id} onClick={() => setPeriod(id)} style={{
          padding: '2px 7px', borderRadius: 5, fontSize: 10, cursor: 'pointer', fontFamily: 'var(--font-sans)',
          background: period === id ? 'var(--bg-card)' : 'transparent',
          border: '1px solid ' + (period === id ? 'var(--border)' : 'transparent'),
          color: period === id ? 'var(--text)' : 'var(--text-4)', fontWeight: period === id ? 600 : 400,
        }}>{label}</button>
      ))}
    </span>
  );

  if (err) return <div className="card" style={{ gridColumn: 'span 12' }}><div className="card-b" style={{ color: 'var(--bear)', fontSize: 12 }}>{err}</div></div>;

  const u = data && data.unrealized, r = data && data.realized, s = data && data.slippage;

  return (
    <>
      <div className="card" style={{ gridColumn: 'span 3' }}>
        <div className="card-h">
          <span style={{ fontWeight: 600, fontSize: 13 }}>Unrealized P&amp;L</span>
          {u && u.marks_stale && <span className="chip gold" style={{ fontSize: 9.5, marginLeft: 8 }} title="Bybit didn't answer just now; showing the last prices we had.">stale price</span>}
        </div>
        <div className="card-b">
          <div className="mono" style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: u ? pnlColor(u.total) : undefined }}>
            {u ? fmtUsd(u.total) : '…'}
          </div>
          <div style={acctHint}>
            {u ? `${u.n} open position${u.n === 1 ? '' : 's'}` : ''}
            {u && u.missing_n > 0 && <span style={{ color: 'var(--gold)' }}> · {u.missing_n} without a live price</span>}
          </div>
        </div>
      </div>

      <div className="card" style={{ gridColumn: 'span 3' }}>
        <div className="card-h" style={{ justifyContent: 'space-between', flexWrap: 'wrap', gap: 4 }}>
          <span style={{ fontWeight: 600, fontSize: 13, whiteSpace: 'nowrap' }}>Realized P&amp;L</span>
          {periodChips}
        </div>
        <div className="card-b">
          <div className="mono" style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: r ? pnlColor(r.total) : undefined }}>
            {r ? fmtUsd(r.total) : '…'}
          </div>
          <div style={acctHint}>
            {r ? `${r.n} closed trade${r.n === 1 ? '' : 's'}` : ''}
            {r && r.missing_n > 0 && <span style={{ color: 'var(--gold)' }}> · {r.missing_n} missing a figure</span>}
          </div>
          {r && r.daily.length >= 2 && (
            <div style={{ marginTop: 8 }}>
              <Sparkline values={r.daily.map(d => d.cum)} height={28} baseline={0}
                        color={r.total >= 0 ? 'var(--bull)' : 'var(--bear)'} />
            </div>
          )}
        </div>
      </div>

      <div className="card" style={{ gridColumn: 'span 3' }}>
        <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Entry slippage</span></div>
        <div className="card-b">
          <div className="mono" style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em', color: s && s.avg_pct != null ? pnlColor(-s.avg_pct) : undefined }}>
            {s && s.avg_pct != null ? `${s.avg_pct >= 0 ? '+' : ''}${s.avg_pct.toFixed(2)}%` : '—'}
          </div>
          <div style={acctHint} title="How far our actual fill landed from the price the trader stated, on average — positive means we paid more (long) or received less (short). CMP/DCA setups have no stated price to compare against and aren't counted.">
            {s && s.avg_pct != null
              ? `${fmtUsd(s.total_cost_usd)} across ${s.n} trade${s.n === 1 ? '' : 's'} with a stated entry`
              : 'No trades with a stated entry in this period'}
            {s && s.excluded_n > 0 && <span> · {s.excluded_n} market-entry excluded</span>}
          </div>
        </div>
      </div>
    </>
  );
}

function AcctDetail({ account, role, onChanged, onRemoved }) {
  const [bal, setBal] = React.useState(null);
  const [hist, setHist] = React.useState([]);
  const [replacing, setReplacing] = React.useState(false);
  const [checking, setChecking] = React.useState(false);
  const [followsNone, setFollowsNone] = React.useState(false);   // can follow, but follows nothing yet

  React.useEffect(() => {
    let alive = true;
    const canFollow = !!acctCaps(account).follow;
    const load = async () => {
      const [b, h, f] = await Promise.all([
        acctApi(`/accounts/${account.id}/balance`),
        acctApi(`/accounts/${account.id}/history?days=30`),
        canFollow ? acctApi(`/accounts/${account.id}/follows`) : Promise.resolve(null),
      ]);
      if (!alive) return;
      if (b.ok) setBal(b.data);
      if (h.ok) setHist(h.data);
      if (f && f.ok) setFollowsNone(f.data.length > 0 && f.data.every(r => !r.enabled));
    };
    load();
    const id = setInterval(load, 60000);
    return () => { alive = false; clearInterval(id); };
  }, [account.id, account.status]);

  // After a follow is saved, drop the "next step" banner straight away instead of at the next refresh.
  async function refreshFollows() {
    const f = await acctApi(`/accounts/${account.id}/follows`);
    if (f.ok) setFollowsNone(f.data.length > 0 && f.data.every(r => !r.enabled));
  }

  async function recheck() {
    setChecking(true);
    await acctApi(`/accounts/${account.id}/validate`, 'POST');
    setChecking(false);
    onChanged();
  }

  if (replacing) {
    return <AcctConnectForm existing={account} onCancel={() => setReplacing(false)}
                            onDone={() => { setReplacing(false); onChanged(); }} />;
  }

  const [statusLabel, statusChip] = ACCT_STATUS[account.status] || [account.status, 'neutral'];
  const grid = { display: 'grid', gridTemplateColumns: 'repeat(12, 1fr)', gap: 14 };
  const balOk = bal && bal.ok !== false;

  return (
    <div className="rg-12" style={grid}>
      {account.status !== 'active' && (
        <div className="card" style={{ gridColumn: 'span 12', borderColor: account.status === 'error' ? 'var(--bear)' : 'var(--gold)' }}>
          <div className="card-b" style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' }}>
            <div style={{ flex: 1, minWidth: 240, fontSize: 12.5, lineHeight: 1.55 }}>
              <div style={{ fontWeight: 600, color: account.status === 'error' ? 'var(--bear)' : 'var(--gold)' }}>
                {account.status === 'error' ? 'This API key was not accepted' : 'The API key has not been checked yet'}
              </div>
              <div style={{ color: 'var(--text-3)' }}>
                {account.last_error || (acctIsPortfolio(account)
                  ? 'Nothing is shown for this account until the key is checked. Balances stay hidden until then.'
                  : 'Nothing is traded on this account until the key is checked.')}
              </div>
            </div>
            <button onClick={recheck} disabled={checking} style={acctBtn('ghost', checking)}>{checking ? 'Checking…' : 'Check again'}</button>
            <button onClick={() => setReplacing(true)} style={acctBtn('primary')}>Replace key</button>
          </div>
        </div>
      )}

      {followsNone && (
        <div className="card" style={{ gridColumn: 'span 12', borderColor: 'var(--gold)' }}>
          <div className="card-b" style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap', fontSize: 12.5 }}>
            <div style={{ flex: 1, minWidth: 240, lineHeight: 1.55 }}>
              <div style={{ fontWeight: 600, color: 'var(--gold)' }}>Next step: choose a strategy to follow</div>
              <div style={{ color: 'var(--text-3)' }}>
                This account is not following any strategy yet, so nothing is being copied. Tick one in the
                {' '}<b>Strategies</b> card below and press Save.
              </div>
            </div>
            <button onClick={() => { const el = document.getElementById('acct-strategies'); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' }); }}
                    style={acctBtn('primary')}>Go to strategies</button>
          </div>
        </div>
      )}

      {acctIsPortfolio(account) && account.status === 'active' && (
        <div className="card" style={{ gridColumn: 'span 12' }}>
          <div className="card-b" style={{ fontSize: 12.5, color: 'var(--text-2)' }}>
            <span className="chip neutral" style={{ marginRight: 8 }}>Portfolio · not for live copy</span>
            Balances and holdings only — Follow, Go live, kill switch and Close are not available on this venue in Phase A.
          </div>
        </div>
      )}

      {/* What needs attention right now comes first: open positions, then the numbers. */}
      {acctCaps(account).trade && <AcctTradingBar account={account} onChanged={onChanged} />}
      <AcctPositions account={account} onChanged={onChanged} />

      <div className="card" style={{ gridColumn: 'span 3' }}>
        <div className="card-h" style={{ justifyContent: 'space-between', gap: 6, flexWrap: 'wrap' }}>
          <span style={{ fontWeight: 600, fontSize: 13 }}>Equity</span>
          <span style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
            <span className="chip neutral">{VENUE_LABEL[account.venue] || account.venue || 'Bybit'}</span>
            {acctCaps(account).envDemoReal && (
              <span className={'chip ' + (account.env === 'real' ? 'bear' : 'gold')}>{account.env === 'real' ? 'Real money' : 'Demo'}</span>
            )}
            {acctIsPortfolio(account)
              ? <span className="chip neutral" title="Read-only portfolio — not for live copy">Portfolio</span>
              : (
                <span className={'chip ' + (account.mode === 'live' ? 'bear' : 'neutral')}
                      title={account.mode === 'live' ? 'Live orders are placed on this account' : 'Paper: nothing is sent to the exchange'}>
                  {account.mode === 'live' ? 'Live orders' : 'Paper'}
                </span>
              )}
            <span className={'chip ' + statusChip}>{statusLabel}</span>
            {/* The assistant (admin + members): is this account in sync with the traders it follows? */}
            {(role === 'admin' || role === 'arena') && acctCaps(account).trade && typeof openAssistant === 'function' && (
              <button className="as-ask-btn" style={{ padding: '2px 9px', fontSize: 11 }}
                      title="The assistant reads this account (read-only) and checks positions, stops and orders against what the traders you follow said"
                      onClick={() => openAssistant({ kind: 'review_account', account_id: account.id, account_label: account.label,
                        prompt: `Review my ${account.label} account: are its positions, stops, targets and orders in sync with what the traders I follow said?`, autoSend: true })}>
                ✦ Review account
              </button>
            )}
          </span>
        </div>
        <div className="card-b">
          <div className="mono" style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.02em' }}>
            {balOk ? fmtUsd(bal.equity_usd) : '—'}
          </div>
          {bal && bal.ok === false && <div style={{ ...acctHint, color: 'var(--text-3)' }}>{bal.error}</div>}
          <div style={acctHint}>
            USDT free <span className="mono" style={{ color: 'var(--text-3)' }}>{balOk ? fmtUsd(bal.usdt_free) : '—'}</span>
            {account.key_last4 && <span> · key ••••{account.key_last4}</span>}
          </div>
        </div>
      </div>

      {acctCaps(account).trade && <AcctPnlStrip account={account} />}

      <div className="card" style={{ gridColumn: 'span 8' }}>
        <div className="card-h" style={{ justifyContent: 'space-between' }}>
          <span style={{ fontWeight: 600, fontSize: 13 }}>Equity · 30d</span>
          <span className="chip neutral" style={{ fontSize: 10 }}>{hist.length} pts</span>
        </div>
        <div className="card-b"><EquityChart rows={hist} height={150} /></div>
      </div>

      <div className="card" style={{ gridColumn: 'span 4' }}>
        <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Allocation</span></div>
        <div className="card-b"><Allocation acct={balOk ? bal : null} /></div>
      </div>

      {/* Configuration last, top-aligned so the short card doesn't stretch to the tall one. */}
      {acctCaps(account).paperLive && acctCaps(account).trade
        ? <AcctSettings account={account} onChanged={onChanged} />
        : <AcctPortfolioSettings account={account} onChanged={onChanged} />}
      {acctCaps(account).follow && <AcctFollows account={account} onSaved={refreshFollows} />}
      <AcctRemove account={account} onRemoved={onRemoved} />
    </div>
  );
}

// ── settings ────────────────────────────────────────────────────────────────
// The one-click safety control, kept at the top of the page where it can be reached at once
// (it used to sit inside Account settings, near the bottom).
function AcctTradingBar({ account, onChanged }) {
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const killed = !!account.kill_switch;
  async function toggle() {
    setBusy(true); setErr(null);
    const j = await acctApi(`/accounts/${account.id}`, 'PATCH', { kill_switch: !killed });
    setBusy(false);
    if (!j.ok) { setErr(j.error || 'Could not change it'); return; }
    onChanged();
  }
  return (
    <div style={{
      gridColumn: 'span 12', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', padding: '9px 14px', borderRadius: 8,
      background: killed ? 'var(--bear-bg)' : 'var(--bg-card)',
      border: '1px solid ' + (killed ? 'var(--bear)' : 'var(--border-soft)'),
    }}>
      <div style={{ flex: 1, minWidth: 220, fontSize: 12.5, lineHeight: 1.45 }}>
        <span style={{ fontWeight: 600, color: killed ? 'var(--bear)' : 'var(--bull)' }}>
          {killed ? '⛔ Trading stopped on this account' : '● Trading active'}
        </span>
        <span style={{ color: 'var(--text-3)', marginLeft: 8 }}>
          {killed ? 'No new entries are placed. Open positions stay open.'
            : (account.mode === 'live' ? 'New setups from the strategies you follow are placed as live orders.' : 'Paper account — nothing is sent to the exchange.')}
        </span>
        {err && <span style={{ color: 'var(--bear)', marginLeft: 8 }}>{err}</span>}
      </div>
      <button disabled={busy} onClick={toggle}
              title="Stops new entries on this account only. Does not flip the pipeline stop (Stop all accounts). Open positions stay open."
              style={killed ? acctBtn('ghost', busy) : acctBtn('danger-outline', busy)}>
        {killed ? 'Resume trading' : 'Stop trading'}
      </button>
    </div>
  );
}

function AcctPortfolioSettings({ account, onChanged }) {
  const [form, setForm] = React.useState({ label: account.label });
  const [msg, setMsg] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => { setForm({ label: account.label }); }, [account.id, account.updated_at]);
  const dirty = form.label !== account.label;
  async function save() {
    setBusy(true); setMsg(null);
    const j = await acctApi(`/accounts/${account.id}`, 'PATCH', { label: form.label });
    setBusy(false);
    if (!j.ok) { setMsg({ err: j.error || 'Save failed' }); return; }
    setMsg({ ok: 'Settings saved' });
    onChanged();
  }
  return (
    <div className="card" style={{ gridColumn: 'span 12', alignSelf: 'start' }}>
      <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Account settings</span></div>
      <div className="card-b">
        <div style={{
          padding: '10px 12px', marginBottom: 14, borderRadius: 8, fontSize: 12, lineHeight: 1.5,
          background: 'var(--bg-elev)', border: '1px solid var(--border-soft)', color: 'var(--text-2)',
        }}>
          <b>Portfolio · not for live copy</b> — this venue is read-only in Phase A. Paper/Live, Follow and kill switch stay on Bybit.
        </div>
        <label style={{ display: 'block', maxWidth: 360 }}>
          <span style={acctLabelStyle}>Name</span>
          <input style={acctInp} value={form.label} maxLength={60} onChange={e => setForm({ label: e.target.value })} />
        </label>
        {msg && <div style={{ marginTop: 10, fontSize: 12, color: msg.err ? 'var(--bear)' : 'var(--bull)' }}>{msg.err || msg.ok}</div>}
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
          <button disabled={!dirty || busy} onClick={save}
                  style={{ ...acctBtn('primary', busy), opacity: !dirty ? 0.4 : 1 }}>Save settings</button>
        </div>
      </div>
    </div>
  );
}

function AcctSettings({ account, onChanged }) {
  const initial = () => ({
    label: account.label,
    max_concurrent: String(account.max_concurrent),
    daily_loss_cap_pct: account.daily_loss_cap_pct == null ? '' : String(account.daily_loss_cap_pct),
  });
  const [form, setForm] = React.useState(initial);
  const [msg, setMsg] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [liveOpen, setLiveOpen] = React.useState(false);

  React.useEffect(() => { setForm(initial()); }, [account.id, account.updated_at]);

  async function patch(body, okText) {
    setBusy(true); setMsg(null);
    const j = await acctApi(`/accounts/${account.id}`, 'PATCH', body);
    setBusy(false);
    if (!j.ok) { setMsg({ err: j.error || 'Save failed' }); return false; }
    setMsg({ ok: okText });
    onChanged();
    return true;
  }

  const dirty = form.label !== account.label
    || form.max_concurrent !== String(account.max_concurrent)
    || form.daily_loss_cap_pct !== (account.daily_loss_cap_pct == null ? '' : String(account.daily_loss_cap_pct));


  return (
    <div className="card" style={{ gridColumn: 'span 5', alignSelf: 'start' }}>
      <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Account settings</span></div>
      <div className="card-b">
        <div style={{
          display: 'flex', alignItems: 'center', gap: 12, padding: '10px 12px', marginBottom: 14, borderRadius: 8,
          background: account.mode === 'live' ? 'var(--bear-bg)' : 'var(--bg-elev)', border: '1px solid var(--border-soft)',
        }}>
          <div style={{ flex: 1, fontSize: 12, lineHeight: 1.5 }}>
            <div style={{ fontWeight: 600, color: account.mode === 'live' ? 'var(--bear)' : 'var(--text)' }}>
              {account.mode === 'live' ? 'Live orders — orders are placed on this account' : 'Paper — nothing is sent to Bybit'}
            </div>
            <div style={{ color: 'var(--text-3)' }}>
              {account.mode === 'live'
                ? 'Switching back to paper stops new orders; open positions stay as they are.'
                : 'Signals are still recorded, so you can see what would have happened.'}
            </div>
          </div>
          {account.mode === 'live'
            ? <button disabled={busy} onClick={() => patch({ mode: 'paper' }, 'Switched to paper')} style={acctBtn('ghost', busy)}>Switch to paper</button>
            : <button disabled={busy || account.status !== 'active'} onClick={() => setLiveOpen(true)}
                      title={account.status !== 'active' ? 'Connect and check the API key first' : undefined}
                      style={acctBtn('danger-outline', busy || account.status !== 'active')}>Go live…</button>}
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
          <label style={{ gridColumn: 'span 2' }}>
            <span style={acctLabelStyle}>Name</span>
            <input style={acctInp} value={form.label} maxLength={60} onChange={e => setForm({ ...form, label: e.target.value })} />
          </label>
          <label>
            <span style={acctLabelStyle}>Max open positions</span>
            <input style={acctInp} inputMode="numeric" value={form.max_concurrent}
                   onChange={e => setForm({ ...form, max_concurrent: e.target.value })} />
            <div style={acctHint}>New setups are skipped while this many are open.</div>
          </label>
          <label>
            <span style={acctLabelStyle}>Daily loss limit — % of account</span>
            <input style={acctInp} inputMode="decimal" placeholder="no limit" value={form.daily_loss_cap_pct}
                   onChange={e => setForm({ ...form, daily_loss_cap_pct: e.target.value })} />
            <div style={acctHint}>After losing this much in a day, no new trades until tomorrow (UTC).</div>
          </label>
        </div>
        {msg && <div style={{ marginTop: 10, fontSize: 12, color: msg.err ? 'var(--bear)' : 'var(--bull)' }}>{msg.err || msg.ok}</div>}
        <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 12 }}>
          <button disabled={!dirty || busy} onClick={() => patch({
            label: form.label,
            max_concurrent: form.max_concurrent,
            daily_loss_cap_pct: form.daily_loss_cap_pct === '' ? null : form.daily_loss_cap_pct,
          }, 'Settings saved')} style={{ ...acctBtn('primary', busy), opacity: !dirty ? 0.4 : 1 }}>Save settings</button>
        </div>
      </div>
      {liveOpen && (
        <AcctLiveModal account={account} onClose={() => setLiveOpen(false)}
          onConfirm={async () => { if (await patch({ mode: 'live', confirm_live: true }, 'This account is now live')) setLiveOpen(false); }}
          busy={busy} error={msg && msg.err} />
      )}
    </div>
  );
}

function AcctLiveModal({ account, onClose, onConfirm, busy, error }) {
  const [typed, setTyped] = React.useState('');
  const ok = typed.trim().toUpperCase() === 'LIVE';
  return (
    <AcctModal title="Go live on this account" tone="var(--bear)" onClose={onClose} busy={busy}>
      <div style={{ marginBottom: 10 }}>
        From now on, new setups from the strategies this account follows will place <b>real orders</b> on
        <b> {account.label}</b>{account.env === 'real' ? <> — this is a <b style={{ color: 'var(--bear)' }}>real-money</b> account.</> : <> (Bybit demo funds).</>}
      </div>
      <ul style={{ margin: '0 0 12px', paddingLeft: 18, color: 'var(--text-3)' }}>
        <li>Only setups posted after this moment are copied — nothing from before.</li>
        <li>Each trade risks the % you set per strategy; losses are real.</li>
        {account.env === 'real' && <li>Real-money orders also need real trading enabled on the platform.</li>}
      </ul>
      <label style={{ display: 'block', marginBottom: 12 }}>
        <span style={acctLabelStyle}>Type LIVE to confirm</span>
        <input style={acctInp} value={typed} autoFocus onChange={e => setTyped(e.target.value)} />
      </label>
      {error && <div style={{ color: 'var(--bear)', fontSize: 11.5, marginBottom: 10 }}>{error}</div>}
      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button onClick={onClose} disabled={busy} style={acctBtn('ghost', busy)}>Cancel</button>
        <button onClick={onConfirm} disabled={!ok || busy} style={{ ...acctBtn('danger', busy), opacity: ok ? 1 : 0.4 }}>
          {busy ? 'Switching…' : 'Go live'}
        </button>
      </div>
    </AcctModal>
  );
}

// ── strategies this account follows ─────────────────────────────────────────
function AcctFollows({ account, onSaved }) {
  const [rows, setRows] = React.useState(null);
  const [draft, setDraft] = React.useState({});
  const [msg, setMsg] = React.useState(null);
  const [busyId, setBusyId] = React.useState(null);

  // Reload when the account changes too: going live restarts each follow's live_since.
  const load = React.useCallback(async () => {
    const j = await acctApi(`/accounts/${account.id}/follows`);
    if (j.ok) { setRows(j.data); setDraft({}); } else setMsg({ err: j.error });
  }, [account.id, account.updated_at]);
  React.useEffect(() => { load(); }, [load]);

  const value = (r) => draft[r.strategy_id] || {
    enabled: !!r.enabled,
    risk_pct: r.risk_pct == null ? '1' : String(r.risk_pct),
    max_position_pct: r.max_position_pct == null ? '' : String(r.max_position_pct),
  };
  const edit = (r, patch) => setDraft(d => ({ ...d, [r.strategy_id]: { ...value(r), ...patch } }));

  async function save(r) {
    const v = value(r);
    setBusyId(r.strategy_id); setMsg(null);
    const j = await acctApi(`/accounts/${account.id}/follows`, 'PUT', {
      strategy_id: r.strategy_id, enabled: v.enabled, risk_pct: v.risk_pct,
      max_position_pct: v.max_position_pct === '' ? null : v.max_position_pct,
    });
    setBusyId(null);
    if (!j.ok) { setMsg({ err: j.error }); return; }
    setMsg({ ok: `${r.public_name} saved` });
    load();
    if (onSaved) onSaved();
  }

  return (
    <div id="acct-strategies" className="card" style={{ gridColumn: 'span 7', alignSelf: 'start' }}>
      <div className="card-h"><span style={{ fontWeight: 600, fontSize: 13 }}>Strategies</span></div>
      <div className="card-b">
        <div style={{ ...acctHint, marginTop: 0, marginBottom: 12 }}>
          Choose which strategies this account copies and how big each trade is. Only setups posted
          after you turn a strategy on are copied.
        </div>
        {!rows ? <div style={{ color: 'var(--text-4)', fontSize: 12 }}>Loading…</div>
          : !rows.length ? <div style={{ color: 'var(--text-4)', fontSize: 12 }}>No strategies available yet.</div>
          : rows.map(r => {
            const v = value(r);
            const dirty = !!draft[r.strategy_id];
            return (
              <div key={r.strategy_id} style={{
                padding: '11px 12px', marginBottom: 9, borderRadius: 8,
                background: 'var(--bg-elev)', border: '1px solid ' + (v.enabled ? 'var(--border)' : 'var(--border-soft)'),
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
                  <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', flex: 1 }}>
                    <input type="checkbox" checked={v.enabled} onChange={e => edit(r, { enabled: e.target.checked })} />
                    <span style={{ fontWeight: 600, fontSize: 13 }}>{r.public_name}</span>
                    <span style={{ fontSize: 11, color: r.enabled ? 'var(--bull)' : 'var(--text-4)' }}>
                      {r.enabled ? 'following' + (r.live_since ? ' since ' + fmtStamp(r.live_since) : '') : 'off'}
                    </span>
                  </label>
                  <button disabled={!dirty || busyId === r.strategy_id} onClick={() => save(r)}
                          style={{ ...acctBtn('primary', busyId === r.strategy_id), opacity: dirty ? 1 : 0.35, padding: '5px 12px' }}>Save</button>
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                  <label>
                    <span style={acctLabelStyle}>Risk per trade (1R) — % of account</span>
                    <input style={acctInp} inputMode="decimal" value={v.risk_pct} onChange={e => edit(r, { risk_pct: e.target.value })} />
                    <div style={acctHint}>What you lose if a trade hits its stop. 0.5–2% is typical.</div>
                  </label>
                  <label>
                    <span style={acctLabelStyle}>Max position size — % of account (optional)</span>
                    <input style={acctInp} inputMode="decimal" placeholder="no cap" value={v.max_position_pct}
                           onChange={e => edit(r, { max_position_pct: e.target.value })} />
                    <div style={acctHint}>Caps the position's value. Above 100% means leverage.</div>
                  </label>
                </div>
              </div>
            );
          })}
        {msg && <div style={{ fontSize: 12, color: msg.err ? 'var(--bear)' : 'var(--bull)' }}>{msg.err || msg.ok}</div>}
      </div>
    </div>
  );
}

// ── positions ───────────────────────────────────────────────────────────────
const ACCT_POS_LABEL = {
  ENTRY_WORKING: 'entering', OPEN: 'open', PARTIAL: 'partly closed', AWAITING_APPROVAL: 'awaiting approval',
  CLOSED: 'closed', CANCELLED: 'cancelled', EXPIRED: 'expired', SKIPPED: 'skipped',
};

function AcctPnl({ v }) {
  if (v == null) return <span style={{ color: 'var(--text-4)' }}>—</span>;
  const n = Number(v);
  return <span style={{ color: n > 0 ? 'var(--bull)' : n < 0 ? 'var(--bear)' : 'var(--text-3)' }}>{(n >= 0 ? '+' : '') + fmtUsd(n)}</span>;
}

function AcctPositions({ account, onChanged }) {
  const [tab, setTab] = React.useState('open');
  const [rows, setRows] = React.useState(null);
  const [closing, setClosing] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [expanded, setExpanded] = React.useState(() => new Set());
  const toggleExpand = (id) => setExpanded(prev => {
    const next = new Set(prev);
    next.has(id) ? next.delete(id) : next.add(id);
    return next;
  });

  const load = React.useCallback(async () => {
    if (tab === 'exchange') return;   // that tab loads its own data
    const j = await acctApi(`/accounts/${account.id}/positions?state=${tab === 'open' ? 'open' : 'closed'}`);
    if (j.ok) setRows(j.data);
  }, [account.id, tab]);

  React.useEffect(() => {
    setRows(null); load();
    const id = setInterval(load, 30000);
    return () => clearInterval(id);
  }, [load]);

  async function confirmClose() {
    setBusy(true); setErr(null);
    const j = await acctApi(`/accounts/${account.id}/positions/${closing.id}/close`, 'POST');
    setBusy(false);
    if (!j.ok) { setErr(j.error || j.reason || 'Close failed'); return; }
    setClosing(null); load(); onChanged();
  }

  const th = { textAlign: 'left', fontSize: 9.5, color: 'var(--text-4)', letterSpacing: '0.09em', textTransform: 'uppercase', padding: '0 8px 7px', fontWeight: 500 };
  const td = { padding: '7px 8px', borderTop: '1px solid var(--border-soft)', fontSize: 11.5 };
  const mono = { ...td, fontFamily: 'var(--font-mono)' };

  return (
    <div className="card" style={{ gridColumn: 'span 12' }}>
      <div className="card-h" style={{ justifyContent: 'space-between' }}>
        <span style={{ display: 'flex', gap: 4 }}>
          {[['open', 'Open positions'], ['history', 'History'], ['exchange', 'On the exchange']].map(([id, label]) => (
            <button key={id} onClick={() => setTab(id)} style={{
              padding: '4px 12px', borderRadius: 6, border: 'none', cursor: 'pointer',
              fontSize: 13, fontFamily: 'var(--font-sans)', fontWeight: tab === id ? 600 : 400,
              background: tab === id ? 'var(--bg-elev)' : 'transparent',
              color: tab === id ? 'var(--text)' : 'var(--text-3)',
            }}>{label}</button>
          ))}
        </span>
        {tab !== 'exchange' && <span className="chip neutral" style={{ fontSize: 10 }}>{rows ? rows.length : '…'}</span>}
      </div>
      <div className="card-b" style={{ overflowX: 'auto' }}>
        {tab === 'exchange' ? <AcctExchangeActivity account={account} />
          : rows == null ? <div style={{ padding: '18px 0', textAlign: 'center', color: 'var(--text-4)', fontSize: 12 }}>Loading…</div>
          : !rows.length ? <div style={{ padding: tab === 'open' ? '4px 0' : '18px 0', textAlign: 'center', color: 'var(--text-4)', fontSize: 12 }}>
              {tab === 'open' ? 'No open positions.' : 'No closed trades yet.'}
            </div>
          : (
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead><tr>
                <th style={{ ...th, width: 20 }}></th>
                <th style={th}>strategy</th><th style={th}>symbol</th><th style={th}>side</th><th style={th}>state</th>
                <th style={th}>size</th><th style={th}>entry</th>
                {tab !== 'open' && <th style={th} title="Exit price used when this trade was scored under the candidate live policy — not necessarily your own fill">exit</th>}
                <th style={th} title="Current Bybit mark price">now</th><th style={th}>stop</th><th style={th}>target</th>
                <th style={th}>{tab === 'open' ? 'unrealized' : 'result'}</th>
                <th style={th}>opened</th>{tab !== 'open' && <th style={th}>closed</th>}<th style={th}></th>
              </tr></thead>
              <tbody>
                {rows.map(p => {
                  const colSpan = tab === 'open' ? 13 : 15;
                  return (
                  <React.Fragment key={p.id}>
                  <tr>
                    <td style={td}>
                      <button onClick={() => toggleExpand(p.id)} title="Show the messages behind this position"
                        style={{
                          background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
                          color: 'var(--text-4)', fontSize: 10, width: 14,
                          transform: expanded.has(p.id) ? 'rotate(90deg)' : 'none', transition: 'transform .1s',
                        }}>▸</button>
                    </td>
                    <td style={{ ...td, color: 'var(--text-3)' }}>{p.strategy}</td>
                    <td style={{ ...td, fontWeight: 600 }}>{p.symbol}</td>
                    <td style={{ ...td, color: p.side === 'long' ? 'var(--bull)' : 'var(--bear)' }}>{p.side}</td>
                    <td style={td}>
                      <span className="chip neutral" style={{ fontSize: 9.5 }}>{ACCT_POS_LABEL[p.state] || p.state}</span>
                      {p.mode === 'paper' && <span style={{ marginLeft: 5, fontSize: 9.5, color: 'var(--text-4)' }}>paper</span>}
                    </td>
                    <td style={mono}>{fmtNum(p.filled_qty)}</td>
                    <td style={mono}>{fmtNum(p.avg_fill != null ? p.avg_fill : p.entry_price)}</td>
                    {tab !== 'open' && <td style={mono}>{fmtNum(p.exit_price_a)}</td>}
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtNum(p.current_price)}</td>
                    <td style={mono}>{fmtNum(p.stop_loss)}</td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{((p.our_tps || p.tps || []).slice(0, 2)).map(fmtNum).join(' · ') || '—'}</td>
                    <td style={mono}><AcctPnl v={tab === 'open' ? p.unrealized_pnl : p.realized_pnl} /></td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtStamp(p.opened_at)}</td>
                    {tab !== 'open' && <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtStamp(p.closed_at)}</td>}
                    <td style={td}>
                      {tab === 'open' && acctCaps(account).close && ['OPEN', 'PARTIAL'].includes(p.state) && (
                        <button onClick={() => { setClosing(p); setErr(null); }} style={{ ...acctBtn('danger-outline'), padding: '3px 10px', fontSize: 11 }}>Close</button>
                      )}
                    </td>
                  </tr>
                  {expanded.has(p.id) && (
                    <SignalHistorySubrow signalId={p.signal_id} colSpan={colSpan}
                      url={`/api/me/accounts/${account.id}/positions/${p.id}/timeline`} />
                  )}
                  </React.Fragment>
                  );
                })}
              </tbody>
            </table>
          )}
      </div>
      {closing && (
        <AcctModal title="Close this position now" tone="var(--bear)" busy={busy} onClose={() => !busy && setClosing(null)}>
          <div style={{ marginBottom: 12 }}>
            This sends a market order that closes <b>{closing.symbol}</b> ({closing.side}) on <b>{account.label}</b> right
            away, ahead of the planned stop or target. It can't be undone.
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, padding: '9px 11px', background: 'var(--bg-elev)', borderRadius: 7, marginBottom: 12 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-4)' }}>Size</span><span className="mono">{fmtNum(closing.filled_qty)}</span></div>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-4)' }}>Entry</span><span className="mono">{fmtNum(closing.avg_fill)}</span></div>
            <div style={{ display: 'flex', justifyContent: 'space-between' }}><span style={{ color: 'var(--text-4)' }}>Unrealized</span><span className="mono"><AcctPnl v={closing.unrealized_pnl} /></span></div>
          </div>
          {err && <div style={{ color: 'var(--bear)', fontSize: 11.5, marginBottom: 10 }}>{err}</div>}
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
            <button onClick={() => setClosing(null)} disabled={busy} style={acctBtn('ghost', busy)}>Cancel</button>
            <button onClick={confirmClose} disabled={busy} style={acctBtn('danger', busy)}>{busy ? 'Closing…' : 'Close position'}</button>
          </div>
        </AcctModal>
      )}
    </div>
  );
}

// ── the real account's activity, straight from the exchange ────────────────
// Not the ledger: every fill, funding charge, stop/target trigger and liquidation the exchange
// recorded on this account, including anything done outside the bot. "Trades" groups the fills
// into round trips (flat -> position -> flat); "Fills" is the raw list.
const EXCH_REASON = {
  stop_loss: ['stop loss', 'var(--bear)'], take_profit: ['take profit', 'var(--bull)'],
  trailing_stop: ['trailing stop', 'var(--text-2)'], liquidation: ['liquidated', 'var(--bear)'],
  adl: ['auto-deleveraged', 'var(--bear)'],
};
const EXCH_FILLS_PAGE = 100;

function AcctExchangeActivity({ account }) {
  const [view, setView] = React.useState('trips');
  const [data, setData] = React.useState(null);
  const [page, setPage] = React.useState(0);
  const [syncing, setSyncing] = React.useState(false);
  const [err, setErr] = React.useState(null);
  const [expanded, setExpanded] = React.useState(() => new Set());
  const toggleExpand = (key) => setExpanded(prev => {
    const next = new Set(prev);
    next.has(key) ? next.delete(key) : next.add(key);
    return next;
  });

  const load = React.useCallback(async () => {
    const path = view === 'trips'
      ? `/accounts/${account.id}/trips`
      : `/accounts/${account.id}/fills?limit=${EXCH_FILLS_PAGE}&offset=${page * EXCH_FILLS_PAGE}`;
    const j = await acctApi(path);
    if (j.ok) setData(j.data); else setErr(j.error || 'Could not load the exchange history');
  }, [account.id, view, page]);

  React.useEffect(() => {
    setData(null); setErr(null); load();
    const id = setInterval(load, 60000);
    return () => clearInterval(id);
  }, [load]);

  async function syncNow() {
    setSyncing(true); setErr(null);
    const j = await acctApi(`/accounts/${account.id}/fills/sync`, 'POST');
    setSyncing(false);
    if (!j.ok) { setErr(j.error || 'Sync failed'); return; }
    load();
  }

  const th = { textAlign: 'left', fontSize: 9.5, color: 'var(--text-4)', letterSpacing: '0.09em', textTransform: 'uppercase', padding: '0 8px 7px', fontWeight: 500 };
  const td = { padding: '7px 8px', borderTop: '1px solid var(--border-soft)', fontSize: 11.5 };
  const mono = { ...td, fontFamily: 'var(--font-mono)' };
  const sync = data && data.sync;
  const origin = (r) => r.signal_id != null
    ? <span style={{ color: 'var(--text-3)' }} title="Placed by one of our orders">{r.strategy || 'ledger'}</span>
    : <span className="chip neutral" style={{ fontSize: 9, color: 'var(--gold)' }}
            title="No order of ours matches this: a manual trade, a stop or target the exchange triggered, funding, or an order from before the ledger">not from the ledger</span>;
  // The messages behind a row, when one of our orders placed it (same history the ledger rows show).
  const chevron = (key, positionId) => (
    <button onClick={() => toggleExpand(key)}
      title={positionId != null ? 'Show the messages behind this order' : 'Show where this came from'}
      style={{
        background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
        color: 'var(--text-4)', fontSize: 10, width: 14,
        transform: expanded.has(key) ? 'rotate(90deg)' : 'none', transition: 'transform .1s',
      }}>▸</button>
  );
  const subrow = (key, positionId, colSpan) => !expanded.has(key) ? null : positionId != null
    ? <SignalHistorySubrow signalId={positionId} colSpan={colSpan}
        url={`/api/me/accounts/${account.id}/positions/${positionId}/timeline`} />
    : (
      <tr><td colSpan={colSpan} style={{ padding: '10px 14px 12px 34px', background: 'var(--bg-elev)', borderTop: '1px solid var(--border-soft)', fontSize: 11, color: 'var(--text-4)' }}>
        No message triggered this. It was not placed by one of our orders: a manual trade, a stop or target the
        exchange triggered, a funding charge, or an order from before the ledger.
      </td></tr>
    );
  const viewBtn = (id, label) => (
    <button key={id} onClick={() => { setView(id); setPage(0); }} style={{
      padding: '3px 10px', borderRadius: 6, border: 'none', cursor: 'pointer', fontSize: 12,
      background: view === id ? 'var(--bg-elev)' : 'transparent',
      color: view === id ? 'var(--text)' : 'var(--text-3)', fontWeight: view === id ? 600 : 400,
    }}>{label}</button>
  );

  const summary = view === 'trips' && data ? data.summary : null;
  const trips = view === 'trips' && data ? data.trips : null;
  const fills = view === 'fills' && data ? data.fills : null;
  const list = view === 'trips' ? trips : fills;

  return (
    <div>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 10 }}>
        <span style={{ display: 'flex', gap: 2 }}>{viewBtn('trips', 'Trades')}{viewBtn('fills', 'Fills')}</span>
        {summary && summary.closed > 0 && (
          <span style={{ fontSize: 11.5, color: 'var(--text-3)', display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <span>{summary.closed} closed · {Math.round(summary.wins / summary.closed * 100)}% won</span>
            <span>net <AcctPnl v={summary.net_pnl} /></span>
            <span title="Already included in net">fees {fmtUsd(summary.fees)}</span>
            {summary.funding !== 0 && <span title="Already included in net">funding {fmtUsd(summary.funding)}</span>}
          </span>
        )}
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 10.5, color: sync && sync.error ? 'var(--bear)' : 'var(--text-4)' }}
              title={sync && sync.error ? sync.error : 'The worker pulls new executions from the exchange every couple of minutes'}>
          {sync && sync.error ? 'Last sync failed' : sync && sync.synced_at ? `Synced ${agoFrom(sync.synced_at)} ago` : 'Not synced yet'}
        </span>
        <button onClick={syncNow} disabled={syncing} style={{ ...acctBtn('ghost', syncing), padding: '4px 10px', fontSize: 11 }}>
          {syncing ? 'Syncing…' : 'Sync now'}
        </button>
      </div>
      {err && <div style={{ color: 'var(--bear)', fontSize: 11.5, marginBottom: 8 }}>{err}</div>}
      {sync && sync.error && <div style={{ color: 'var(--text-3)', fontSize: 11, marginBottom: 8 }}>{sync.error}</div>}

      {data == null ? <div style={{ padding: '18px 0', textAlign: 'center', color: 'var(--text-4)', fontSize: 12 }}>Loading…</div>
        : list && !list.length ? <div style={{ padding: '18px 0', textAlign: 'center', color: 'var(--text-4)', fontSize: 12 }}>
            {sync && sync.synced_at ? 'The exchange has recorded no activity on this account yet.' : 'Nothing synced yet. Press Sync now.'}
          </div>
        : view === 'trips' ? (
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead><tr>
              <th style={{ ...th, width: 20 }}></th>
              <th style={th}>symbol</th><th style={th}>side</th><th style={th}>status</th><th style={th}>size</th>
              <th style={th} title="Average fill price in">entry</th><th style={th} title="Average fill price out">exit</th>
              <th style={th} title="What the exchange paid out, after fees and funding">net result</th><th style={th}>%</th>
              <th style={th}>fees</th><th style={th}>funding</th><th style={th}>opened</th><th style={th}>closed</th>
              <th style={th}>ended by</th><th style={th}>origin</th>
            </tr></thead>
            <tbody>
              {trips.map((t) => {
                const [rl, rc] = EXCH_REASON[t.exit_reason] || (t.status === 'closed'
                  ? [t.exit_by_us ? 'our order' : 'a market order', 'var(--text-3)'] : ['—', 'var(--text-4)']);
                const key = `t:${t.coin}:${t.opened_at}`;
                return (
                  <React.Fragment key={key}>
                  <tr>
                    <td style={td}>{chevron(key, t.position_id)}</td>
                    <td style={{ ...td, fontWeight: 600 }}>{t.coin}</td>
                    <td style={{ ...td, color: t.direction === 'long' ? 'var(--bull)' : 'var(--bear)' }}>{t.direction}</td>
                    <td style={td}>
                      <span className="chip neutral" style={{ fontSize: 9.5 }}>{t.status === 'open' ? 'still open' : t.incomplete ? 'bought before history' : 'closed'}</span>
                    </td>
                    <td style={mono}>{fmtNum(t.qty)}</td>
                    <td style={mono}>{fmtNum(t.avg_entry)}</td>
                    <td style={mono}>{fmtNum(t.avg_exit)}</td>
                    <td style={mono} title={t.fee_unknown ? 'A fee was paid in a currency we cannot price, so no net figure is shown' : undefined}>
                      <AcctPnl v={t.net_pnl} />
                    </td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{t.net_pct == null ? '—' : (t.net_pct >= 0 ? '+' : '') + t.net_pct.toFixed(2) + '%'}</td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtNum(t.fees)}</td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{t.funding ? fmtNum(t.funding) : '—'}</td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtStamp(t.opened_at)}</td>
                    <td style={{ ...mono, color: 'var(--text-3)' }}>{t.closed_at ? fmtStamp(t.closed_at) : '—'}</td>
                    <td style={{ ...td, color: rc }}>{rl}</td>
                    <td style={td}>{origin(t)}</td>
                  </tr>
                  {subrow(key, t.position_id, 15)}
                  </React.Fragment>
                );
              })}
            </tbody>
          </table>
        ) : (
          <div>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead><tr>
                <th style={{ ...th, width: 20 }}></th>
                <th style={th}>time</th><th style={th}>symbol</th><th style={th}>side</th><th style={th}>type</th>
                <th style={th}>qty</th><th style={th}>price</th><th style={th}>value</th><th style={th}>fee</th>
                <th style={th}>origin</th>
              </tr></thead>
              <tbody>
                {fills.map(f => {
                  const funding = f.kind === 'funding';
                  const [rl, rc] = EXCH_REASON[f.reason] || [funding ? 'funding' : f.is_maker ? 'maker' : 'trade', 'var(--text-3)'];
                  const key = `f:${f.id}`;
                  return (
                    <React.Fragment key={key}>
                    <tr>
                      <td style={td}>{chevron(key, f.position_id)}</td>
                      <td style={{ ...mono, color: 'var(--text-3)' }}>{fmtStamp(f.filled_at)}</td>
                      <td style={{ ...td, fontWeight: 600 }}>{f.coin}</td>
                      <td style={{ ...td, color: funding ? 'var(--text-3)' : f.side === 'buy' ? 'var(--bull)' : 'var(--bear)' }}>{funding ? '—' : f.side}</td>
                      <td style={{ ...td, color: rc }}>{rl}</td>
                      <td style={mono}>{fmtNum(f.qty)}</td>
                      <td style={mono}>{fmtNum(f.price)}</td>
                      <td style={mono}>{fmtNum(f.cost)}</td>
                      <td style={{ ...mono, color: funding && Number(f.fee) < 0 ? 'var(--bull)' : 'var(--text-3)' }}
                          title={funding ? 'Funding: positive = paid, negative = received' : undefined}>
                        {fmtNum(f.fee)}{f.fee_ccy && f.fee_ccy !== f.quote ? ' ' + f.fee_ccy : ''}
                      </td>
                      <td style={td}>{origin(f)}</td>
                    </tr>
                    {subrow(key, f.position_id, 10)}
                    </React.Fragment>
                  );
                })}
              </tbody>
            </table>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'flex-end', marginTop: 8, fontSize: 11, color: 'var(--text-4)' }}>
              <span>{page * EXCH_FILLS_PAGE + 1}–{page * EXCH_FILLS_PAGE + fills.length} of {data.total}</span>
              <button disabled={page === 0} onClick={() => setPage(p => p - 1)}
                      style={{ ...acctBtn('ghost', false), padding: '3px 9px', fontSize: 11, opacity: page === 0 ? 0.4 : 1 }}>Newer</button>
              <button disabled={(page + 1) * EXCH_FILLS_PAGE >= data.total} onClick={() => setPage(p => p + 1)}
                      style={{ ...acctBtn('ghost', false), padding: '3px 9px', fontSize: 11, opacity: (page + 1) * EXCH_FILLS_PAGE >= data.total ? 0.4 : 1 }}>Older</button>
            </div>
          </div>
        )}
    </div>
  );
}

// ── remove ──────────────────────────────────────────────────────────────────
function AcctRemove({ account, onRemoved }) {
  const [confirm, setConfirm] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  async function remove() {
    setBusy(true); setErr(null);
    const j = await acctApi(`/accounts/${account.id}`, 'DELETE');
    setBusy(false);
    if (!j.ok) { setErr(j.error || 'Could not remove the account'); return; }
    onRemoved();
  }

  return (
    <div style={{ gridColumn: 'span 12', display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 10 }}>
      {err && <span style={{ color: 'var(--bear)', fontSize: 12 }}>{err}</span>}
      {!confirm
        ? <button onClick={() => setConfirm(true)} style={{ ...acctBtn('ghost'), color: 'var(--text-3)' }}>Remove this account…</button>
        : <>
            <span style={{ fontSize: 12, color: 'var(--text-2)' }}>Stop copying and delete the stored API key?</span>
            <button onClick={() => setConfirm(false)} disabled={busy} style={acctBtn('ghost', busy)}>No</button>
            <button onClick={remove} disabled={busy} style={acctBtn('danger', busy)}>{busy ? 'Removing…' : 'Yes, remove'}</button>
          </>}
    </div>
  );
}
