// Shared primitives used by both Variants A and B.
// Tiny SVGs for icons (no emoji).

const Icon = {
  back:    (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>,
  plus:    (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round"><path d="M12 5v14M5 12h14"/></svg>,
  close:   (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg>,
  copy:    (c='currentColor', s=14) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>,
  scan:    (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2M7 12h10"/></svg>,
  search:  (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>,
  chev:    (c='currentColor', s=14) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18l6-6-6-6"/></svg>,
  trash:   (c='currentColor', s=14) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>,
  bolt:    (c='currentColor', s=12) => <svg width={s} height={s} viewBox="0 0 24 24" fill={c}><path d="M13 2L3 14h7l-1 8 11-12h-7l1-8z"/></svg>,
  cog:     (c='currentColor', s=18) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3h0a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8v0a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>,
  sun:     (c='currentColor', s=16) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>,
  moon:    (c='currentColor', s=16) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>,
  auto:    (c='currentColor', s=16) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 3v18" fill={c}/><path d="M12 3a9 9 0 0 1 0 18z" fill={c}/></svg>,
  // Tab bar glyphs
  home:    (c='currentColor', s=22) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12l9-8 9 8M5 10v10h14V10"/></svg>,
  bars:    (c='currentColor', s=22) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round"><path d="M5 19V11M12 19V5M19 19v-6"/></svg>,
  list:    (c='currentColor', s=22) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round"><path d="M4 6h16M4 12h16M4 18h16"/></svg>,
  wallet:  (c='currentColor', s=22) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="6" width="18" height="14" rx="2"/><path d="M3 10h18M16 15h2"/></svg>,
};

// Mini SVG sparkline driven by a numeric series. Falls back to a deterministic
// scribble (window.spark) when fewer than 2 points are available, so the row
// still reads as a sparkline before the first WS push lands.
function LiveSpark({ points, w=50, h=20, color='#50d2c1', strokeW=1.4, fill=true }) {
  if (!points || points.length < 2) {
    return <Spark sym="LIVE" w={w} h={h} color={color} strokeW={strokeW} fill={fill}/>;
  }
  let mn = Infinity, mx = -Infinity;
  for (let i = 0; i < points.length; i++) {
    const v = points[i]; if (v < mn) mn = v; if (v > mx) mx = v;
  }
  // Avoid a flat line when min === max (no recent change).
  const range = (mx - mn) || Math.max(1e-9, Math.abs(mx) * 0.001);
  const N = points.length;
  const stepX = w / Math.max(1, N - 1);
  let path = '';
  for (let i = 0; i < N; i++) {
    const x = (i * stepX).toFixed(2);
    const y = (h - ((points[i] - mn) / range) * (h - 2) - 1).toFixed(2);
    path += (i === 0 ? 'M' : 'L') + x + ',' + y + ' ';
  }
  const id = 'lsp-' + Math.abs(((mx + mn) * 1000) | 0);
  return (
    <svg width={w} height={h} style={{ display:'block' }}>
      {fill && (
        <>
          <defs>
            <linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor={color} stopOpacity="0.32"/>
              <stop offset="100%" stopColor={color} stopOpacity="0"/>
            </linearGradient>
          </defs>
          <path d={path + ` L${w},${h} L0,${h} Z`} fill={`url(#${id})`}/>
        </>
      )}
      <path d={path} stroke={color} strokeWidth={strokeW} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

// Mini SVG sparkline using deterministic data.
function Spark({ sym, up=true, w=72, h=22, color='#50d2c1', strokeW=1.5, fill=true }) {
  const d = window.spark(sym, w, h, up);
  const id = 'sp-' + sym + (up?'u':'d');
  return (
    <svg width={w} height={h} style={{ display: 'block' }}>
      {fill && (
        <>
          <defs>
            <linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor={color} stopOpacity="0.32"/>
              <stop offset="100%" stopColor={color} stopOpacity="0"/>
            </linearGradient>
          </defs>
          <path d={d + ` L${w},${h} L0,${h} Z`} fill={`url(#${id})`} />
        </>
      )}
      <path d={d} stroke={color} strokeWidth={strokeW} fill="none" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

// Top-50 token color palette (used for monogram fallback + ring tint)
const TOKEN_PALETTE = {
  BTC:{bg:'#f7931a22',fg:'#f7931a'}, ETH:{bg:'#8b5cf622',fg:'#a78bfa'},
  SOL:{bg:'#9945ff22',fg:'#a974ff'}, HYPE:{bg:'#50d2c122',fg:'#50d2c1'},
  ARB:{bg:'#28a0f022',fg:'#60a5fa'}, AVAX:{bg:'#e84a4222',fg:'#e84a42'},
  DOGE:{bg:'#c2a63322',fg:'#e0c34d'}, BNB:{bg:'#f3ba2f22',fg:'#f3ba2f'},
  XRP:{bg:'#23292f22',fg:'#94a3b8'}, MATIC:{bg:'#8247e522',fg:'#a78bfa'},
  POL:{bg:'#8247e522',fg:'#a78bfa'}, LINK:{bg:'#2a5ada22',fg:'#60a5fa'},
  OP:{bg:'#ff042022',fg:'#ff4d63'}, SUI:{bg:'#4ca2ff22',fg:'#4ca2ff'},
  INJ:{bg:'#00f1c022',fg:'#00f1c0'}, TIA:{bg:'#7b2bf922',fg:'#a974ff'},
  SEI:{bg:'#9c1f4422',fg:'#e879a8'}, ATOM:{bg:'#2e3148bb',fg:'#a3aed4'},
  NEAR:{bg:'#00ec9722',fg:'#00ec97'}, APT:{bg:'#13b5ec22',fg:'#56cdf2'},
  DYDX:{bg:'#6966ff22',fg:'#a3a1ff'}, LDO:{bg:'#00a3ff22',fg:'#56b8ff'},
  AAVE:{bg:'#b6509e22',fg:'#d77ec1'}, UNI:{bg:'#ff007a22',fg:'#ff5fae'},
  ENA:{bg:'#1f1f1fbb',fg:'#cbd5e1'}, WIF:{bg:'#dc94f422',fg:'#dc94f4'},
  PEPE:{bg:'#1aa04022',fg:'#33c860'}, BONK:{bg:'#fbbf2422',fg:'#fbbf24'},
  SHIB:{bg:'#ffa40922',fg:'#ffb84d'}, FET:{bg:'#1f2937bb',fg:'#94a3b8'},
  TAO:{bg:'#ffffff14',fg:'#e5e7eb'}, RUNE:{bg:'#33ff9922',fg:'#33ff99'},
  KAS:{bg:'#70c7ba22',fg:'#70c7ba'}, ORDI:{bg:'#0a1929bb',fg:'#fbbf24'},
  JTO:{bg:'#7eafe522',fg:'#7eafe5'}, JUP:{bg:'#c7f284ff22',fg:'#c7f284'},
  PYTH:{bg:'#a72fff22',fg:'#c674ff'}, ARK:{bg:'#fb212922',fg:'#fb6470'},
  MKR:{bg:'#1aab9b22',fg:'#3edcc8'}, FTM:{bg:'#13b5ec22',fg:'#13b5ec'},
  TRX:{bg:'#ff060a22',fg:'#ff5054'}, LTC:{bg:'#345d9d22',fg:'#7da2d8'},
  BCH:{bg:'#0ac18e22',fg:'#0ac18e'}, ETC:{bg:'#34844422',fg:'#5dbb6e'},
  FIL:{bg:'#0090ff22',fg:'#54b6ff'}, ADA:{bg:'#0033ad22',fg:'#5b8def'},
  DOT:{bg:'#e6007a22',fg:'#ff5fae'}, SAND:{bg:'#00aceaff22',fg:'#36c8ff'},
  AXS:{bg:'#0055d422',fg:'#5b8def'}, MANA:{bg:'#ff2d5522',fg:'#ff5e79'},
  GMX:{bg:'#2d42fcff22',fg:'#7080ff'}, RENDER:{bg:'#cf133eff22',fg:'#ef5071'},
  RNDR:{bg:'#cf133eff22',fg:'#ef5071'}, IMX:{bg:'#0d6cdfff22',fg:'#52a3ff'},
  STX:{bg:'#5546ffff22',fg:'#7d72ff'}, ICP:{bg:'#3b00b922',fg:'#7d56ff'},
  W:{bg:'#3b82f622',fg:'#60a5fa'}, EIGEN:{bg:'#1a0c6dbb',fg:'#a3a1ff'},
  ONDO:{bg:'#0066ff22',fg:'#3a8cff'}, STRK:{bg:'#ec796b22',fg:'#ec796b'},
  TON:{bg:'#0098ea22',fg:'#0098ea'},
};

// Token logo — Hyperliquid's official icon CDN first, then coincap, then a
// colored monogram tile. Cross-origin caching is handled by the service
// worker (sw.js / ICON_CACHE), so subsequent visits paint from local cache
// without re-fetching the network.
const COINCAP_ALIAS = { POL:'matic', RNDR:'render', RENDER:'render' };

// Ticker → TradingView company slug for HIP-3 stock dexes (`xyz:NVDA` → NVDA
// → `nvidia`). Slugs verified live against s3-symbol-logo.tradingview.com.
// Missing entries fall through to the monogram tile.
const STOCK_LOGO_SLUG = {
  NVDA:'nvidia', TSLA:'tesla', AAPL:'apple', MSFT:'microsoft',
  GOOGL:'alphabet', META:'meta-platforms', AMZN:'amazon', NFLX:'netflix',
  AMD:'advanced-micro-devices', INTC:'intel', COIN:'coinbase',
  MSTR:'microstrategy', HOOD:'robinhood', PLTR:'palantir',
  COST:'costco-wholesale', LLY:'eli-lilly', CRCL:'circle', ORCL:'oracle',
  TSM:'taiwan-semiconductor', BABA:'alibaba', RIVN:'rivian',
  MU:'micron-technology', GME:'gamestop', DKNG:'draftkings',
  MRVL:'marvell-tech', RKLB:'rocket-lab', ZM:'zoom-video-communications',
  EBAY:'ebay', ARM:'arm', SNDK:'sandisk', SMSN:'samsung',
  SOFTBANK:'softbank', KIOXIA:'kioxia', HYUNDAI:'hyundai',
};

function TokenGlyph({ sym, size=34, radius=10, bg, fg, mono=false }) {
  // Namespaced HIP-3 syms (`xyz:NVDA`) split into kind+ticker so we can
  // (a) route to TradingView's stock logo CDN and
  // (b) draw monograms from the bare ticker, not the dex prefix.
  const _raw = String(sym || '');
  const _ci = _raw.indexOf(':');
  const isStock = _ci >= 0;
  const ticker = isStock ? _raw.slice(_ci + 1).toUpperCase() : _raw.toUpperCase();
  const key = ticker;
  const sources = React.useMemo(() => {
    if (isStock) {
      const slug = STOCK_LOGO_SLUG[ticker];
      return slug ? [`https://s3-symbol-logo.tradingview.com/${slug}--big.svg`] : [];
    }
    const cdnSym = (COINCAP_ALIAS[key] || key || '').toLowerCase();
    return [
      `https://app.hyperliquid.xyz/coins/${key}.svg`,
      `https://assets.coincap.io/assets/icons/${cdnSym}@2x.png`,
    ];
  }, [isStock, ticker, key]);
  const [stage, setStage] = React.useState(0);
  React.useEffect(() => { setStage(0); }, [key]);

  // Skeleton sits behind the image. Cached SW responses paint the image on
  // the first frame so the skeleton is never seen; uncached first loads see
  // it briefly until network resolves. No fade — instant swap.
  const skeletonBg = 'rgba(127,127,127,0.10)';

  if (stage < sources.length) {
    const src = sources[stage];
    return (
      <span style={{
        display:'inline-block', position:'relative', flexShrink: 0,
        width: size, height: size, borderRadius: radius,
        background: skeletonBg,
        overflow:'hidden',
      }}>
        <img
          src={src}
          alt={key}
          width={size} height={size}
          decoding="sync"
          loading="eager"
          onError={() => setStage(s => s + 1)}
          style={{
            width: size, height: size, borderRadius: radius,
            objectFit:'contain', display:'block',
          }}
        />
      </span>
    );
  }

  // Monogram fallback — colored tile for legibility when no CDN succeeds.
  const palette = TOKEN_PALETTE[key] || { bg:'#64748b22', fg:'#64748b' };
  const _bg = bg ?? palette.bg;
  const _fg = fg ?? palette.fg;
  return (
    <div style={{
      width: size, height: size, borderRadius: radius,
      background: _bg, color: _fg,
      display:'flex', alignItems:'center', justifyContent:'center',
      fontFamily: mono ? 'JetBrains Mono, ui-monospace, monospace' : 'inherit',
      fontWeight: 700, fontSize: size*0.36, letterSpacing: '-0.02em',
      flexShrink: 0, overflow:'hidden',
    }}>
      <span>{key === 'HYPE' ? 'HY' : key.slice(0,3)}</span>
    </div>
  );
}

// Wallet avatar — single Hyperliquid logo for every tracked address. Past
// versions used a per-address colored gradient + glyph; the user prefers a
// uniform brand icon since every entry in the list is an HL account.
const HL_LOGO_URL = 'https://app.hyperliquid.xyz/apple-touch-icon.png';
function HLWalletAvatar({ size=32, radius=10 }) {
  return (
    <img
      src={HL_LOGO_URL}
      alt="Hyperliquid"
      width={size} height={size}
      style={{
        width: size, height: size, borderRadius: radius,
        objectFit:'cover', display:'block', flexShrink: 0, background:'transparent',
      }}
      loading="lazy"
    />
  );
}

// PnL coloring resolver — driven by a tweak.
window.pnlColor = function(pnl, mode='rg', opts={}) {
  // mode: rg (red/green), gr (swap), neon (cyan/pink)
  const isPos = pnl >= 0;
  if (mode === 'gr') return isPos ? '#ef4444' : '#22c55e';
  if (mode === 'neon') return isPos ? '#22d3ee' : '#f472b6';
  return isPos ? '#22c55e' : '#ef4444'; // default rg: green up / red down
};

// useTick was a mock-time flicker for the design phase; live updates now come
// from hl-store via WebSocket pushes, so it's been removed.

function pendingPxFmt(px) {
  if (px == null) return 1;
  return px < 10 ? 3 : (px < 100 ? 2 : 1);
}

function pendingNotional(o) {
  if (!o) return null;
  if (o.value != null && isFinite(o.value)) return o.value;
  if (o.price != null && o.sizeNum != null && isFinite(o.sizeNum)) return o.price * o.sizeNum;
  return null;
}

function pendingPricePct(basePx, orderPx, posSide) {
  if (basePx == null || !isFinite(basePx) || basePx === 0 || orderPx == null) return null;
  const delta = posSide === 'LONG' ? (orderPx - basePx) : (basePx - orderPx);
  return (delta / basePx) * 100;
}

function pendingOrderSize(o) {
  if (!o) return null;
  if (o.sizeNum != null && isFinite(o.sizeNum) && o.sizeNum > 0) return o.sizeNum;
  if (o.origSzNum != null && isFinite(o.origSzNum) && o.origSzNum > 0) return o.origSzNum;
  if (o.value != null && o.price != null && isFinite(o.price) && o.price > 0) return o.value / o.price;
  const parsed = parseFloat(String(o.size || '').replace(/,/g, ''));
  return isFinite(parsed) && parsed > 0 ? parsed : null;
}

function pendingRefPx(pos, g) {
  if (pos) {
    const entry = pos.entry != null ? parseFloat(pos.entry) : NaN;
    if (isFinite(entry)) return entry;
    const mark = pos.mark != null ? parseFloat(pos.mark) : NaN;
    if (isFinite(mark)) return mark;
  }
  if (g.entry && g.entry.price != null && isFinite(g.entry.price)) return g.entry.price;
  const extras = g.extraEntries || [];
  for (let i = 0; i < extras.length; i++) {
    if (extras[i].price != null && isFinite(extras[i].price)) return extras[i].price;
  }
  return null;
}

function pendingClosePnl(basePx, o, posSide) {
  if (!o || basePx == null || !isFinite(basePx) || o.price == null) return null;
  const sz = pendingOrderSize(o);
  if (sz == null) return null;
  const delta = posSide === 'LONG' ? (o.price - basePx) : (basePx - o.price);
  return delta * sz;
}

function pendingPctColor(pct, isDark) {
  if (pct == null || !isFinite(pct) || pct === 0) return null;
  return pct > 0
    ? (isDark ? '#7ee2a8' : '#15803d')
    : (isDark ? '#fca5a5' : '#b91c1c');
}

function pendingLabelColor(lineKind, posSide, isDark) {
  if (lineKind === 'tp') return isDark ? '#7ee2a8' : '#15803d';
  if (lineKind === 'sl') return isDark ? '#fca5a5' : '#b91c1c';
  if (lineKind === 'entry') {
    if (posSide === 'LONG') return isDark ? '#7ee2a8' : '#15803d';
    if (posSide === 'SHORT') return isDark ? '#fca5a5' : '#b91c1c';
  }
  return isDark ? '#94a3b8' : '#64748b';
}

function pendingPanelBg(th) {
  if (th.tk) return th.isDark ? th.tk.solidBg : '#ffffff';
  return th.isDark ? '#1f2129' : '#ffffff';
}

function fmtUsdtNum(v, d) {
  if (v == null || !isFinite(v)) return '—';
  return Math.abs(v).toLocaleString('en-US', {
    minimumFractionDigits: d,
    maximumFractionDigits: d,
  });
}

const PENDING_ORDER_GRID = '30px 50px minmax(0,1fr) 44px 72px';

function PendingOrderCell({ children, align, color, weight, size, th }) {
  return (
    <span style={{
      textAlign: align || 'center',
      whiteSpace: 'nowrap',
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      fontVariantNumeric: 'tabular-nums',
      fontWeight: weight != null ? weight : 500,
      fontSize: size || 11.5,
      color: color || th.ink,
      lineHeight: 1.2,
    }}>
      {children}
    </span>
  );
}

function pendingRowBg(lineKind, isDark) {
  if (lineKind === 'tp') return isDark ? 'rgba(34,197,94,0.05)' : 'rgba(34,197,94,0.06)';
  if (lineKind === 'sl') return isDark ? 'rgba(239,68,68,0.05)' : 'rgba(239,68,68,0.06)';
  return 'transparent';
}

function PendingOrderLine({ label, o, th, posSide, lineKind, refPx, divider }) {
  if (!o) return null;
  const isDark = th.isDark;
  const pxFmt = pendingPxFmt(o.price);
  const pxPct = refPx != null ? pendingPricePct(refPx, o.price, posSide) : null;
  const showPxPct = pxPct != null && pxPct !== 0;
  const pctFmt = window.fmt && window.fmt.pct;
  const showPnl = lineKind === 'tp' || lineKind === 'sl';
  let pnl = null;
  let pnlColor = th.faint;
  if (showPnl) {
    pnl = pendingClosePnl(refPx, o, posSide);
    if (pnl != null) {
      pnlColor = pnl >= 0
        ? (isDark ? '#7ee2a8' : '#15803d')
        : (isDark ? '#fca5a5' : '#b91c1c');
    }
  }
  const priceStr = o.price != null ? window.fmt.num(o.price, pxFmt) : '—';
  const notional = pendingNotional(o);
  const qty = notional != null ? fmtUsdtNum(notional, 0) : o.size;
  const pxPctColor = pendingPctColor(pxPct, isDark) || th.dim;
  return (
    <div style={{
      display:'grid', gridTemplateColumns:PENDING_ORDER_GRID, gap:'0 6px',
      alignItems:'center', padding:'10px 10px',
      fontFamily:th.numFont,
      borderBottom: divider ? `1px solid ${th.hair}` : 'none',
      background: pendingRowBg(lineKind, isDark),
    }}>
      <PendingOrderCell color={pendingLabelColor(lineKind, posSide, isDark)} weight={700} size={11} th={th}>
        {label}
      </PendingOrderCell>
      <PendingOrderCell color={th.dim} th={th}>{priceStr}</PendingOrderCell>
      <PendingOrderCell weight={600} th={th}>{qty}</PendingOrderCell>
      <PendingOrderCell color={showPxPct ? pxPctColor : th.faint} weight={showPxPct ? 600 : 500} th={th}>
        {showPxPct && pctFmt ? pctFmt(pxPct) : '—'}
      </PendingOrderCell>
      <PendingOrderCell
        color={showPnl && pnl != null ? pnlColor : th.faint}
        weight={showPnl && pnl != null ? 700 : 500}
        th={th}
      >
        {showPnl && pnl != null ? ((pnl >= 0 ? '+' : '−') + fmtUsdtNum(pnl, 0)) : '—'}
      </PendingOrderCell>
    </div>
  );
}

function PendingOrderTable({ lines, th }) {
  const rows = lines.filter(l => l && l.o);
  if (!rows.length) return null;
  const panelBg = pendingPanelBg(th);
  return (
    <div style={{
      marginTop:11, borderRadius:12, overflow:'hidden',
      background:panelBg,
      border:`1px solid ${th.hair}`,
    }}>
      <div style={{
        display:'grid', gridTemplateColumns:PENDING_ORDER_GRID, gap:'0 6px',
        padding:'8px 10px', fontFamily:th.numFont,
        fontSize:10, color:th.faint, letterSpacing:'0.06em',
        borderBottom:`1px solid ${th.hair}`,
        background:panelBg,
      }}>
        <span/>
        <span style={{ textAlign:'center' }}>价格</span>
        <span style={{ textAlign:'center' }}>成交额</span>
        <span style={{ textAlign:'center' }}>涨跌</span>
        <span style={{ textAlign:'center' }}>盈亏</span>
      </div>
      {rows.map((line, i) => (
        <PendingOrderLine
          key={line.key}
          label={line.label}
          o={line.o}
          th={th}
          posSide={line.posSide}
          lineKind={line.lineKind}
          refPx={line.refPx}
          divider={i < rows.length - 1}
        />
      ))}
    </div>
  );
}

function PendingOrderGroupCard({ g, t, th, onSymClick, getPosition }) {
  const isDark = th.isDark;
  const pos = getPosition ? getPosition(g.sym, g.side) : null;
  const refPx = pendingRefPx(pos, g);
  const posEntryPx = pos && pos.entry != null ? parseFloat(pos.entry) : null;
  const symLabel = (() => {
    const split = window.HL.splitCoin && window.HL.splitCoin(g.sym);
    return split ? (split.ticker + '·' + split.dex) : g.sym;
  })();
  const hasEntry = !!(g.entry || (g.extraEntries && g.extraEntries.length));
  const hasTpsl = !!(g.tp || g.sl || (g.extraTps && g.extraTps.length));
  const statusLabel = hasEntry
    ? (hasTpsl ? '开仓 + 风控' : '待开仓')
    : '持仓风控';
  const sideAccent = g.side === 'LONG'
    ? (isDark ? '#22c55e' : '#16a34a')
    : (isDark ? '#ef4444' : '#dc2626');
  const entryLabel = g.side === 'LONG' ? '开多' : '开空';
  const lines = [];
  if (g.entry) {
    lines.push({
      key: g.entry.key || 'entry',
      label: entryLabel, o: g.entry, lineKind: 'entry',
    });
  }
  (g.extraEntries || []).forEach((o, i) => {
    lines.push({
      key: o.key || ('extra-' + i),
      label: entryLabel, o: o, lineKind: 'entry',
    });
  });
  const tpOrders = [g.tp].concat(g.extraTps || []).filter(Boolean);
  if (tpOrders.length > 1 && refPx != null) {
    tpOrders.sort((a, b) => {
      const pa = pendingPricePct(refPx, a.price, g.side) || 0;
      const pb = pendingPricePct(refPx, b.price, g.side) || 0;
      return pa - pb;
    });
  }
  tpOrders.forEach((o, i) => {
    lines.push({
      key: o.key || ('tp-' + i),
      label: tpOrders.length > 1 ? ('止盈' + (i + 1)) : '止盈',
      o: o,
      lineKind: 'tp',
    });
  });
  if (g.sl) lines.push({ key: 'sl', label: '止损', o: g.sl, lineKind: 'sl' });
  const tableLines = lines.map(line => Object.assign({}, line, {
    posSide: g.side,
    refPx: refPx,
  }));
  const refPxLabel = posEntryPx != null && isFinite(posEntryPx)
    ? ('均价 ' + window.fmt.num(posEntryPx, posEntryPx < 10 ? 3 : 1))
    : (refPx != null
      ? ('委托价 ' + window.fmt.num(refPx, refPx < 10 ? 3 : 1))
      : null);
  const metaParts = [statusLabel];
  if (refPxLabel) metaParts.push(refPxLabel);
  const body = (
    <div style={{ ...th.card, borderRadius:14, padding:'13px 14px 14px' }}>
      <div style={{ display:'flex', alignItems:'center', gap:10 }}>
        <div style={{
          width:3, alignSelf:'stretch', borderRadius:999, flexShrink:0,
          background:sideAccent, opacity:0.75,
        }}/>
        <TokenGlyph sym={g.sym} size={34} radius={9}/>
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ display:'flex', alignItems:'center', gap:6, minWidth:0 }}>
            <span style={{
              fontSize:15, fontWeight:600, letterSpacing:'-0.01em',
              overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
            }}>
              {symLabel}
            </span>
            <ATag tone={g.side === 'LONG' ? 'long' : 'short'} t={t}>
              {g.side === 'LONG' ? 'Long' : 'Short'}
            </ATag>
            {pos && pos.lev > 1 && (
              <span style={{ fontSize:11, color:th.faint, fontFamily:th.numFont }}>{pos.lev}×</span>
            )}
          </div>
          <div style={{
            marginTop:4, fontSize:11, color:th.faint, fontFamily:th.numFont,
            overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap',
          }}>
            {metaParts.join(' · ')}
          </div>
        </div>
        {onSymClick && (
          <span style={{ color:th.faint, flexShrink:0, opacity:0.55, marginTop:1 }}>
            {Icon.chev(th.faint, 15)}
          </span>
        )}
      </div>
      <PendingOrderTable lines={tableLines} th={th}/>
    </div>
  );
  if (onSymClick) {
    return (
      <button type="button" onClick={() => onSymClick(g.sym)} style={{
        display:'block', width:'100%', background:'transparent', border:0, padding:0,
        color:'inherit', textAlign:'left', cursor:'pointer',
      }}>{body}</button>
    );
  }
  return body;
}

function PendingOrdersSection({ groups, t, th, title, getPosition, onSymClick, style }) {
  if (!groups || !groups.length) return null;
  let orderCount = 0;
  groups.forEach(g => {
    if (g.entry) orderCount += 1;
    if (g.tp) orderCount += 1;
    orderCount += (g.extraTps || []).length;
    if (g.sl) orderCount += 1;
    orderCount += (g.extraEntries || []).length;
  });
  const isDark = th.isDark;
  return (
    <div style={Object.assign({ padding:'0 0 4px' }, style || {})}>
      <div style={{
        display:'flex', justifyContent:'space-between', alignItems:'center',
        padding:'0 8px 10px',
      }}>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <span style={{ fontSize:12.5, letterSpacing:'0.18em', color:th.dim }}>
            {title || '待成交挂单'}
          </span>
          <span style={{
            fontSize:10, fontWeight:700, letterSpacing:'0.06em',
            padding:'3px 8px', borderRadius:999, fontFamily:th.numFont,
            background:isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)',
            color:th.dim,
          }}>
            {orderCount}
          </span>
        </div>
        <span style={{ fontSize:12, color:th.faint, fontFamily:th.numFont }}>
          {groups.length} 组
        </span>
      </div>
      <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
        {groups.map((g, i) => (
          <PendingOrderGroupCard
            key={g.key || i}
            g={g}
            t={t}
            th={th}
            onSymClick={onSymClick}
            getPosition={getPosition}
          />
        ))}
      </div>
    </div>
  );
}

Object.assign(window, { Icon, Spark, LiveSpark, TokenGlyph, HLWalletAvatar, PendingOrdersSection });
