// Trade Ticket — order dialog (design D, live)
// Colored circle · Market/Limit/Stop · entry/stop price hero · attach TP/SL ·
// footer VWAP·Amount·Margin · joined Sell/Buy bar.

const TT_CAT_COLOR = { FX: '#3B82F6', Metals: '#E0A21A', Energy: '#E2563B', Indices: '#8B5CF6', Crypto: '#F7931A' };

// 3-part price (same format as Market Watch): base 75% · pips 100% · frac 50%
function TPx({ value, inst, size = 30, color = '#fff', weight = 700 }) {
  const { base, pips, frac } = splitPip(value, inst.digits, inst.pipDigits);
  return (
      // A 3-part price is three ADJACENT spans, which is exactly the shape
      // RTL reorders: 1.08|57|4 renders as 4|57|1.08 — an unreadable price,
      // not a cosmetic difference. Pinning the wrapper to LTR keeps the
      // parts in order; the surrounding row still mirrors normally (ID5).
    <span style={{ display: 'inline-flex', alignItems: 'flex-end', gap: 0, direction: 'ltr', unicodeBidi: 'isolate', color, fontVariantNumeric: 'tabular-nums', lineHeight: 1 }}>
      <span style={{ fontSize: Math.round(size * 0.75), fontWeight: weight, lineHeight: 1, opacity: 0.72 }}>{base}</span>
      <span style={{ fontSize: size, fontWeight: weight, lineHeight: 1 }}>{pips}</span>
      {frac && <span style={{ fontSize: Math.round(size * 0.5), fontWeight: weight, lineHeight: 1, alignSelf: 'flex-start', opacity: 0.74 }}>{frac}</span>}
    </span>
  );
}

// Tap-to-type price: shows the 3-part TPx when idle, a numeric input when focused
function EditablePrice({ value, setValue, inst, size = 30, color, theme, width = 160 }) {
  const [editing, setEditing] = React.useState(false);
  const [text, setText] = React.useState('');
  if (editing) {
    return (
      <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 = value; setValue(+n.toFixed(inst.digits)); setEditing(false); }}
        onKeyDown={e => { if (e.key === 'Enter') e.currentTarget.blur(); }}
        style={{
          width, textAlign: 'center', background: 'transparent',
          border: 'none', borderBottom: `2px solid ${color || theme.text}`, outline: 'none',
          color: color || theme.text, fontSize: size, fontWeight: 700,
          fontVariantNumeric: 'tabular-nums', fontFamily: 'inherit', padding: '0 0 2px',
        }}
      />
    );
  }
  return (
    <div onClick={() => { setText(value.toFixed(inst.digits)); setEditing(true); }} style={{ cursor: 'text', display: 'flex', justifyContent: 'center' }}>
      <TPx value={value} inst={inst} size={size} color={color || theme.text}/>
    </div>
  );
}

// Tap-to-type volume (plain decimal, 2dp)
function EditableVol({ vol, setVol, theme, fontSize, width = 150 }) {
  const [editing, setEditing] = React.useState(false);
  const [text, setText] = React.useState('');
  if (editing) {
    return (
      <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 = vol; n = Math.max(0.01, Math.min(50, Math.round(n * 100) / 100)); setVol(n); setEditing(false); }}
        onKeyDown={e => { if (e.key === 'Enter') e.currentTarget.blur(); }}
        style={{
          width, textAlign: 'center', background: 'transparent',
          border: 'none', borderBottom: `2px solid ${theme.text}`, outline: 'none',
          color: theme.text, fontSize, fontWeight: 700, letterSpacing: -1,
          fontVariantNumeric: 'tabular-nums', fontFamily: 'inherit', padding: '0 0 2px',
        }}
      />
    );
  }
  return (
    <div onClick={() => { setText(vol.toFixed(2)); setEditing(true); }} style={{ fontSize, fontWeight: 700, color: theme.text, fontVariantNumeric: 'tabular-nums', letterSpacing: -1, minWidth: width, cursor: 'text' }}>
      {vol.toFixed(2)}
    </div>
  );
}

// StepBtn / PriceHero live at MODULE scope on purpose: the ticket re-renders
// on every price tick, and a component defined inside the render function is
// a NEW component type each time — React unmounts and remounts its DOM every
// tick, eating any click whose mousedown/mouseup straddles a render. That
// made the +/− steppers unresponsive while tap-to-type (stable top-level
// EditableVol/EditablePrice) kept working.
function TTStepBtn({ theme, big, onClick, children }) {
  return (
    <div onClick={onClick} style={{
      width: big ? 44 : 38, height: big ? 44 : 38, borderRadius: big ? 22 : 19,
      background: theme.bgCard, border: `0.5px solid ${theme.border}`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      color: theme.text, fontSize: big ? 24 : 20, cursor: 'pointer', flexShrink: 0, userSelect: 'none',
    }}>{children}</div>
  );
}

function PriceHero({ theme, inst, label, value, setValue, onMinus, onPlus, sub, valueSize = 30, big }) {
  return (
    <div style={{ textAlign: 'center', padding: big ? '6px 0 12px' : '4px 0 10px' }}>
      <div style={{ fontSize: 11, color: theme.textSec, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>{label}</div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: big ? 18 : 14, marginTop: 4 }}>
        <TTStepBtn theme={theme} big={big} onClick={onMinus}>−</TTStepBtn>
        <div style={{ minWidth: 150, display: 'flex', justifyContent: 'center' }}>
          <EditablePrice value={value} setValue={setValue} inst={inst} size={valueSize} theme={theme}/>
        </div>
        <TTStepBtn theme={theme} big={big} onClick={onPlus}>+</TTStepBtn>
      </div>
      {sub && <div style={{ fontSize: 12, color: theme.textSec, marginTop: 4 }}>{sub}</div>}
    </div>
  );
}

// ——— P6: agent prefill (PD11) ———
//
// `prefill` fills this sheet in. It cannot submit it. The sheet is mounted per
// open, so unlike the desktop ticket there is no re-seed to fight — the values
// below are simply the initial state when an agent proposed them.
//
// The banner sits directly above the confirm button, which on this shell is the
// bottom of a sheet the human's thumb is already near. Nothing autofocuses and
// nothing is auto-pressed: opening a filled ticket is showing, not submitting.
function TradeTicket({ inst, priceState, theme, onClose, onConfirm, initialSide = 'buy', prefill = null }) {
  const c = TT_CAT_COLOR[inst.cat] || theme.accent;
  const pip = Math.pow(10, -inst.pipDigits);   // one pip
  const tick = Math.pow(10, -inst.digits);

  const round = (v) => +v.toFixed(inst.digits);
  const side = initialSide;                       // fixed — chosen on the previous screen
  const isBuy = side === 'buy';
  const marketPrice = isBuy ? priceState.ask : priceState.bid;

  const [type, setType] = React.useState(() => (prefill && prefill.orderType) || 'Market'); // Market | Limit | Stop
  const [vol, setVol] = React.useState(() => (prefill && prefill.vol > 0 ? prefill.vol : 0.10));
  // Who filled this in, when it was not the person holding the phone. Dismissed
  // explicitly; it does NOT clear the numbers, because "I have read this" must
  // not destroy the thing that was read.
  const [agentFill, setAgentFill] = React.useState(() => (prefill ? { by: prefill.by } : null));
  const [phase, setPhase] = React.useState('form'); // form | confirming | done | error
  const [err, setErr] = React.useState(null);

  // entry/stop price — seeded a sensible distance & direction from market
  const seedPrice = () => {
    // Limit = better price than market (buy below / sell above); Stop = worse (buy above / sell below)
    if (type === 'Limit') return round(isBuy ? priceState.ask - 50 * pip : priceState.bid + 50 * pip);
    if (type === 'Stop')  return round(isBuy ? priceState.ask + 50 * pip : priceState.bid - 50 * pip);
    return marketPrice;
  };
  // An agent's own level wins over the seeded default, and it is read from the
  // field the order type actually uses — a STOP's trigger in the limit box is a
  // ticket that places the wrong order when a human presses the button.
  const prefillLevel = prefill
    ? (prefill.orderType === 'Stop' ? prefill.stopPrice : prefill.orderType === 'Limit' ? prefill.price : 0)
    : 0;
  const [entry, setEntry] = React.useState(() => (prefillLevel > 0 ? round(prefillLevel) : seedPrice()));
  // Skip the first run when an agent supplied the level, or the type it set
  // would immediately re-seed over it.
  const seededFromAgent = React.useRef(prefillLevel > 0);
  React.useEffect(() => {
    if (seededFromAgent.current) { seededFromAgent.current = false; return; }
    setEntry(seedPrice()); /* reseed on type change */
  }, [type]);

  // TP / SL — directional defaults (buy: TP above, SL below; sell: reversed)
  // A zero from an agent means "no level", exactly as it does on the placement
  // API — so an unattached stop stays unattached rather than becoming zero.
  const [tpOn, setTpOn] = React.useState(() => !!(prefill && prefill.tp > 0));
  const [slOn, setSlOn] = React.useState(() => !!(prefill && prefill.sl > 0));
  const refPrice = type === 'Market' ? marketPrice : entry;
  const [tp, setTp] = React.useState(() => (prefill && prefill.tp > 0 ? round(prefill.tp)
    : round(isBuy ? priceState.ask + 80 * pip : priceState.bid - 80 * pip)));
  const [sl, setSl] = React.useState(() => (prefill && prefill.sl > 0 ? round(prefill.sl)
    : round(isBuy ? priceState.bid - 80 * pip : priceState.ask + 80 * pip)));

  const adjVol = (d) => setVol(v => Math.max(0.01, Math.min(50, Math.round((v + d) * 100) / 100)));

  // derived numbers — real contract size, real leverage, quote-ccy-aware.
  // The notional of GBP/JPY is JPY; it is only a $ figure after conversion
  // through the live crosses (quoteToUSD). No conversion streaming → show
  // the honest quote-currency amount instead of a fake $.
  const execPrice = type === 'Market' ? marketPrice : entry;
  const q2u = quoteToUSD(inst); // USD per quote unit; 0 = unknown
  const notionalQuote = vol * inst.contract * execPrice;
  const valueUSD = notionalQuote * q2u;
  const margin = marginEstimate(inst, vol, execPrice); // USD; 0 when no conversion
  const dir = isBuy ? 1 : -1;
  const estUSD = (level) => (level - refPrice) * dir * vol * inst.contract * q2u;
  const tpEst = estUSD(tp);
  const slEst = estUSD(sl);

  const pipsAway = Math.round((entry - marketPrice) / pip * 10) / 10;

  // Isolated like every other money run (ID5) — see theme.jsx fmtMoney.
  const fmtUSD = (n, dp = 2) => bidiIsolate('$' + Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp }));
  const fmtPlain = (n, dp = 0) => Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: dp, maximumFractionDigits: dp });

  // Live submit: onConfirm(order) → Promise<{ok, status, ticket, error}>.
  // The resolved status is the PLACEMENT ACK (submitted / held / pending) —
  // execution is asynchronous and arrives later via the event stream (and
  // surfaces as fill/partial-fill toasts). The dialog must never claim
  // "filled" here; it only knows the order was accepted.
  const [ackStatus, setAckStatus] = React.useState(null);
  // FR4: `err` is what the user reads (localized from the reject CODE);
  // `errDetail` carries the server's own English text as a tooltip only.
  const [errDetail, setErrDetail] = React.useState(null);
  const submit = async () => {
    setPhase('confirming');
    try {
      const res = await onConfirm({
        inst, side, vol, type,
        entry: type === 'Market' ? marketPrice : entry,
        tp: tpOn ? tp : 0,
        sl: slOn ? sl : 0,
      });
      if (res && res.ok) {
        setAckStatus(res.status || 'submitted');
        setPhase('done');
        // The human acted on it, so it is theirs now.
        setAgentFill(null);
        uiClearPrefill();
        setTimeout(onClose, 1200);
      } else {
        const code = res && res.rejectCode;
        setErr(code ? tCode('reject', code) : ((res && res.error) || t('ticket.rejectedTitle')));
        setErrDetail(code ? ((res && res.error) || null) : null);
        setPhase('error');
      }
    } catch (e) {
      setErr(String(e.message || e));
      setErrDetail(null);
      setPhase('error');
    }
  };

  const ackTitle = () => {
    if (ackStatus === 'pending') return t('ticket.ackPendingTitle', { kind: t('order.kind.' + type.toLowerCase()) });
    if (ackStatus === 'held') return t('ticket.ackAccepted');
    return t('ticket.ackSentToMarket'); // submitted — fills arrive asynchronously
  };
  const ackSub = () => {
    const sideWord = t(isBuy ? 'common.side.buy' : 'common.side.sell');
    if (ackStatus === 'pending') return t('ticket.ackPendingSub', { side: sideWord, vol: vol.toFixed(2), price: fmtPrice(entry, inst.digits) });
    return t('ticket.ackSentSub', { side: sideWord, vol: vol.toFixed(2) });
  };

  const sym = inst.sym.length === 6 && inst.sym.match(/^[A-Z]{6}$/) ? inst.sym.slice(0,3)+'/'+inst.sym.slice(3) : inst.sym;

  return (
    <div 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',
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%', background: theme.bgElevated,
        borderTopLeftRadius: 24, borderTopRightRadius: 24,
        padding: '12px 16px 28px',
        animation: 'slideUp 280ms cubic-bezier(0.2, 0.9, 0.3, 1.05)',
        borderTop: `0.5px solid ${theme.border}`,
        maxHeight: '94%', overflowY: 'auto',
      }}>
        {/* grab handle */}
        <div style={{ width: 36, height: 5, borderRadius: 3, background: theme.borderStrong, margin: '0 auto 14px' }} />

        {/* header — colored circle, no icon */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, marginBottom: 16 }}>
          <div style={{ width: 36, height: 36, borderRadius: 18, background: `${c}22`, border: `1.5px solid ${c}`, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
            <div style={{ width: 11, height: 11, borderRadius: 6, background: c }}/>
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 17, fontWeight: 700, color: theme.text, letterSpacing: -0.3 }}>{sym}</div>
            <div style={{ fontSize: 12, color: theme.textSec, marginTop: 1 }}>{inst.name}</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>

        {phase === 'done' ? (
          <div style={{ padding: '32px 0 20px', 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 }}>{ackTitle()}</div>
            <div style={{ fontSize: 13, color: theme.textSec, marginTop: 4 }}>{ackSub()}</div>
          </div>
        ) : phase === 'error' ? (
          <div style={{ padding: '28px 0 18px', textAlign: 'center' }}>
            <div style={{ width: 56, height: 56, borderRadius: 28, background: theme.sellBg, display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' }}>
              <svg width="28" height="28" viewBox="0 0 24 24" fill="none"><path d="M18 6L6 18M6 6l12 12" stroke={theme.sell} strokeWidth="3" strokeLinecap="round"/></svg>
            </div>
            <div style={{ fontSize: 18, fontWeight: 700, color: theme.text }}>{t('ticket.rejectedTitle')}</div>
            <div title={errDetail || undefined} style={{ fontSize: 13, color: theme.sell, marginTop: 4, padding: '0 16px' }}>{err}</div>
            <div onClick={() => { setErr(null); setErrDetail(null); setPhase('form'); }} style={{ marginTop: 16, padding: '11px 26px', borderRadius: 11, display: 'inline-block', background: theme.bgSubtle, color: theme.text, fontWeight: 600, fontSize: 14, cursor: 'pointer' }}>{t('common.action.retry')}</div>
          </div>
        ) : (
          <>
            {/* order type */}
            <div style={{ display: 'flex', gap: 3, background: theme.bgSubtle, borderRadius: 10, padding: 3, marginBottom: 14 }}>
              {/* the array stays canonical — it is compared against `type`
                  throughout; only the rendered label is localized. */}
              {['Market', 'Limit', 'Stop'].map(o => (
                <div key={o} onClick={() => setType(o)} style={{
                  flex: 1, textAlign: 'center', padding: '8px 0', borderRadius: 7, fontSize: 13, fontWeight: 600, cursor: 'pointer',
                  background: type === o ? theme.bgCard : 'transparent', color: type === o ? theme.text : theme.textSec,
                  border: type === o ? `0.5px solid ${theme.borderStrong}` : '0.5px solid transparent',
                }}>{t('order.kind.' + o.toLowerCase())}</div>
              ))}
            </div>

            {/* entry / stop price hero (Limit & Stop only) */}
            {type !== 'Market' && (
              <>
                <PriceHero theme={theme} inst={inst}
                  label={type === 'Limit' ? t('ticket.entryPrice') : t('ticket.stopPrice')}
                  value={entry}
                  setValue={setEntry}
                  onMinus={() => setEntry(v => round(v - pip))}
                  onPlus={() => setEntry(v => round(v + pip))}
                  sub={t(pipsAway >= 0 ? 'ticket.pipsAboveMarket' : 'ticket.pipsBelowMarket', { pips: Math.abs(pipsAway).toFixed(1) })}
                  valueSize={34} big
                />
                <div style={{ height: 1, background: theme.border, margin: '2px 0 12px' }}/>
              </>
            )}

            {/* volume hero */}
            <PriceHeroVol
              theme={theme} vol={vol} setVol={setVol} adjVol={adjVol} inst={inst}
              big={type === 'Market'} units={inst.contract}
            />

            {/* TP / SL — dashed add-pills, expand to fields */}
            <div style={{ display: 'flex', gap: 10, marginBottom: 14 }}>
              <TpSlField
                kind="tp" on={tpOn} setOn={setTpOn} price={tp} setPrice={setTp}
                onMinus={() => setTp(v => round(v - pip))} onPlus={() => setTp(v => round(v + pip))}
                est={tpEst} theme={theme} inst={inst}
              />
              <TpSlField
                kind="sl" on={slOn} setOn={setSlOn} price={sl} setPrice={setSl}
                onMinus={() => setSl(v => round(v - pip))} onPlus={() => setSl(v => round(v + pip))}
                est={slEst} theme={theme} inst={inst}
              />
            </div>

            {/* footer — VWAP · Amount · Margin */}
            <div style={{ display: 'flex', padding: '12px 0', borderRadius: 12, background: theme.bgSubtle, marginBottom: 14 }}>
              {[
                [t('ticket.estVwap'), fmtPrice(execPrice, inst.digits)],
                q2u > 0
                  ? [t('ticket.usdValue'), fmtUSD(valueUSD, 0)]
                  // the quote currency is a canonical token — interpolated, never translated
                  : [t('ticket.quoteAmount', { quote: inst.quote || '' }), fmtPlain(notionalQuote)],
                [t('ticket.marginReq'), margin > 0 ? fmtUSD(margin) : '—'],
              ].map(([label, val], i) => (
                <React.Fragment key={label}>
                  {i > 0 && <div style={{ width: 1, background: theme.border }}/>}
                  <div style={{ flex: 1, textAlign: 'center' }}>
                    <div style={{ fontSize: 9.5, color: theme.textTer, textTransform: 'uppercase', letterSpacing: 0.5, fontWeight: 600 }}>{label}</div>
                    <div style={{ fontSize: 14, color: theme.text, fontWeight: 700, fontVariantNumeric: 'tabular-nums', marginTop: 3 }}>{val}</div>
                  </div>
                </React.Fragment>
              ))}
            </div>

            {/* P6: the ticket says who filled it in, immediately above the
                button — the last thing read before the thumb lands. */}
            {agentFill && (
              <div style={{
                marginBottom: 12, padding: '10px 12px', borderRadius: 12,
                background: theme.bgSubtle, border: `1px solid ${theme.accent}66`,
                fontSize: 12.5, color: theme.text, lineHeight: 1.4,
              }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
                  <b style={{ color: theme.accent }}>{t('agent.prefill.banner.title', { name: agentFill.by })}</b>
                  <span
                    onClick={(e) => { e.stopPropagation(); setAgentFill(null); uiClearPrefill(); }}
                    style={{ marginInlineStart: 'auto', color: theme.textTer, fontSize: 11, textDecoration: 'underline', cursor: 'pointer' }}
                  >{t('agent.prefill.banner.dismiss')}</span>
                </div>
                <div style={{ marginTop: 3, color: theme.textSec }}>{t('agent.prefill.banner.body')}</div>
              </div>
            )}

            {/* single confirm — side already chosen on previous screen */}
            <div
              onClick={() => phase === 'form' && submit()}
              style={{
                padding: '16px', borderRadius: 14,
                background: isBuy ? theme.buy : theme.sell, color: '#fff',
                textAlign: 'center', fontWeight: 700, fontSize: 16, letterSpacing: -0.2,
                // Outlined while agent-filled, so it does not look like the
                // button on a ticket the human composed a moment ago.
                outline: agentFill ? `2px solid ${theme.accent}` : undefined,
                outlineOffset: agentFill ? 2 : undefined,
                cursor: phase === 'form' ? 'pointer' : 'default',
                opacity: phase === 'confirming' ? 0.7 : 1,
                boxShadow: `0 8px 20px ${(isBuy ? theme.buy : theme.sell)}55`,
              }}
            >
              {phase === 'confirming'
                ? t('ticket.placing')
                : type === 'Market'
                  ? t('ticket.confirmMarket', { side: t(isBuy ? 'common.side.buy' : 'common.side.sell'), vol: vol.toFixed(2), symbol: sym })
                  : t('ticket.placePending', { side: t(isBuy ? 'common.side.buy' : 'common.side.sell'), kind: t('order.kind.' + type.toLowerCase()) })}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

function PriceHeroVol({ theme, vol, setVol, adjVol, inst, big, units }) {
  const step = inst.volStep || 0.01;
  // `base` is the asset the notional is denominated in — a canonical ticker
  // (EUR, BTC), so it is interpolated into the sub-line, never translated.
  // Only the generic fallback is a real word, and that one is a bundle lookup.
  const base = inst.cat === 'FX' ? inst.sym.slice(0,3) : (inst.cat === 'Crypto' ? inst.sym.replace('USD','') : t('common.units'));
  const sub = t('ticket.volSub', { units: (vol * units).toLocaleString(), asset: base });
  return (
    <div style={{ textAlign: 'center', padding: big ? '6px 0 14px' : '4px 0 10px' }}>
      <div style={{ fontSize: 11, color: theme.textSec, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>{t('common.volume')}</div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: big ? 18 : 14, marginTop: 4 }}>
        <TTStepBtn theme={theme} big={big} onClick={() => adjVol(-step)}>−</TTStepBtn>
        <EditableVol vol={vol} setVol={setVol} theme={theme} fontSize={big ? 44 : 30}/>
        <TTStepBtn theme={theme} big={big} onClick={() => adjVol(step)}>+</TTStepBtn>
      </div>
      <div style={{ fontSize: 12, color: theme.textSec, marginTop: 4 }}>{sub}</div>
    </div>
  );
}

function TpSlField({ kind, on, setOn, price, setPrice, onMinus, onPlus, est, theme, inst }) {
  const isTp = kind === 'tp';
  const col = isTp ? theme.buy : theme.sell;
  const label = isTp ? t('common.takeProfit') : t('common.stopLoss');
  if (!on) {
    // whole-string key: the leading "+" is part of the call to action, not a
    // fragment glued onto a separately translated label.
    return (
      <div onClick={() => setOn(true)} style={{
        flex: 1, padding: '11px', borderRadius: 12, background: theme.bgCard,
        border: `1px dashed ${col}66`, textAlign: 'center', color: col, fontSize: 13, fontWeight: 600, cursor: 'pointer',
      }}>{isTp ? t('ticket.addTakeProfit') : t('ticket.addStopLoss')}</div>
    );
  }
  return (
    <div style={{ flex: 1, padding: '9px 12px', borderRadius: 12, background: theme.bgCard, border: `0.5px solid ${theme.border}` }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span style={{ fontSize: 10, color: theme.textSec, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.4 }}>{label}</span>
        <div onClick={() => setOn(false)} style={{ width: 34, height: 20, borderRadius: 10, background: col, position: 'relative', cursor: 'pointer' }}>
          {/* Knob parked at the "on" end of the track. Logical, so the switch
              mirrors with everything else — a knob at the LEADING edge reads
              as off, which is the opposite of what this control means. */}
          <div style={{ position: 'absolute', top: 2, insetInlineStart: 16, width: 16, height: 16, borderRadius: 8, background: '#fff' }}/>
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 6, gap: 4 }}>
        <div onClick={onMinus} style={{ width: 24, height: 24, borderRadius: 7, background: theme.bgSubtle, display: 'flex', alignItems: 'center', justifyContent: 'center', color: theme.text, fontSize: 16, cursor: 'pointer', flexShrink: 0, userSelect: 'none' }}>−</div>
        <EditablePrice value={price} setValue={setPrice} inst={inst} size={16} color={col} theme={theme} width={86}/>
        <div onClick={onPlus} style={{ width: 24, height: 24, borderRadius: 7, background: theme.bgSubtle, display: 'flex', alignItems: 'center', justifyContent: 'center', color: theme.text, fontSize: 16, cursor: 'pointer', flexShrink: 0, userSelect: 'none' }}>+</div>
      </div>
      <div style={{ fontSize: 10.5, color: theme.textSec, textAlign: 'center', marginTop: 5, fontVariantNumeric: 'tabular-nums' }}>
        {bidiIsolate((est >= 0 ? '+' : '−') + '$' + Math.abs(est).toLocaleString('en-US', { maximumFractionDigits: 0 }))}
      </div>
    </div>
  );
}

Object.assign(window, { TradeTicket, TpSlField, EditablePrice });
