// =============================================================================
// a-markets.jsx — Screen 00 行情 (Markets)
// =============================================================================
// Default landing screen. Two list modes:
//   • 自选 (Watchlist) — user-pinned coins, persisted in localStorage.
//   • Top20            — market-cap top 20 (HL.markets).
// List rows are reorderable by long-press + drag (HTML5 DnD on desktop,
// pointer events on touch). Tap a row → ADetail. Detail will show position
// info ONLY if the symbol is also in HL.positions; otherwise market-only view.
// =============================================================================

const WATCHLIST_KEY = 'hl.watchlist.v1';
const ORDER_KEY = (mode) => `hl.order.${mode}.v1`;

function readJSON(k, fb) {
  try { const v = localStorage.getItem(k); return v ? JSON.parse(v) : fb; }
  catch { return fb; }
}
function writeJSON(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} }

function useWatchlist() {
  const [list, _setList] = React.useState(() => readJSON(WATCHLIST_KEY, ['BTC','ETH','SOL','HYPE']));
  // Wrap setList so any update (functional or direct) writes through to
  // localStorage and broadcasts to other screens (detail-page star, etc.).
  const setList = React.useCallback((next) => {
    _setList(prev => {
      const resolved = typeof next === 'function' ? next(prev) : next;
      writeJSON(WATCHLIST_KEY, resolved);
      try { window.dispatchEvent(new CustomEvent('hl-watchlist-change')); } catch (e) {}
      return resolved;
    });
  }, []);
  // Sync from external toggles (detail page).
  React.useEffect(() => {
    const onChange = () => _setList(readJSON(WATCHLIST_KEY, []));
    window.addEventListener('hl-watchlist-change', onChange);
    return () => window.removeEventListener('hl-watchlist-change', onChange);
  }, []);
  return [list, setList];
}

function useOrderedList(mode, baseSyms) {
  // Persist the user's drag order per mode. Filters out unknown / re-adds new ones at the end.
  const [order, setOrder] = React.useState(() => {
    const stored = readJSON(ORDER_KEY(mode), null);
    if (!stored) return baseSyms;
    const known = new Set(baseSyms);
    const kept = stored.filter(s => known.has(s));
    const fresh = baseSyms.filter(s => !stored.includes(s));
    return [...kept, ...fresh];
  });
  React.useEffect(() => writeJSON(ORDER_KEY(mode), order), [mode, order]);
  // Resync when mode switches OR when the underlying base symbols list
  // changes (★ add / swipe delete in watchlist mode etc.). Use a stable
  // string key so we don't fire on every render due to array identity.
  const baseKey = baseSyms.join(',');
  React.useEffect(() => {
    setOrder(prev => {
      const known = new Set(baseSyms);
      const kept = prev.filter(s => known.has(s));
      const fresh = baseSyms.filter(s => !prev.includes(s));
      return [...kept, ...fresh];
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mode, baseKey]);
  return [order, setOrder];
}

// Star toggle button — fills if in watchlist.
function WatchStar({ on, onClick, color, size=16 }) {
  return (
    <button onClick={(e)=>{ e.stopPropagation(); onClick?.(); }} aria-label="watchlist" style={{
      background:'transparent', border:0, padding:4, cursor:'pointer', color, lineHeight:0,
    }}>
      <svg width={size} height={size} viewBox="0 0 24 24" fill={on?color:'none'} stroke={color} strokeWidth="1.6" strokeLinejoin="round">
        <path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z"/>
      </svg>
    </button>
  );
}

// Row — draggable.
function MarketRow({ m, t, watch, onToggleWatch, onTap, dragHandlers, touchDragHandlers, dragging, livePoints }) {
  const th = aTheme(t);
  const up = m.ch24 >= 0;
  const upC = window.pnlColor(up?1:-1, th.pnlMode);
  return (
    <div
      onClick={()=>onTap?.(m.sym)}
      style={{
        display:'flex', alignItems:'center', gap:10, padding:'11px 14px',
        background: dragging ? (th.isDark?'rgba(92,220,201,0.06)':'rgba(13,138,122,0.05)') : 'transparent',
        borderBottom: `1px solid ${th.hair}`,
        cursor:'pointer', userSelect:'none', touchAction:'pan-y',
        transition: 'background 0.15s',
      }}>
      {(() => {
        const split = window.HL.splitCoin && window.HL.splitCoin(m.sym);
        const ticker = split ? split.ticker : m.sym;
        const tag    = split ? split.dex   : 'USDT';
        return (<>
          <TokenGlyph sym={m.sym} size={30} radius={9}/>
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ fontSize:14, fontWeight:600, letterSpacing:'-0.01em' }}>{ticker}<span style={{ color:th.faint, fontWeight:500, fontSize:11.5, marginLeft:5 }}>{tag}</span></div>
            <div style={{ fontSize:11.5, color:th.dim, marginTop:1, fontFamily: th.numFont }}>
              24h量 {window.fmt.vol(m.vol24)}
            </div>
          </div>
        </>);
      })()}
      <LiveSpark points={livePoints} w={50} h={20} color={upC} strokeW={1.4}/>
      <div style={{ textAlign:'right', minWidth:88 }}>
        <div style={{ fontFamily:th.numFont, fontSize:14, fontWeight:600, fontVariantNumeric:'tabular-nums' }}>
          <NumFlow value={m.price} format={(v)=>window.fmt.num(v, m.price<10?3:1)}/>
        </div>
        <div style={{
          marginTop:3, display:'inline-block', minWidth:64, textAlign:'center',
          fontFamily:th.numFont, fontSize:11.5, fontWeight:600,
          color:'#fff', background: upC, borderRadius:4, padding:'2px 6px',
          fontVariantNumeric:'tabular-nums',
        }}>{window.fmt.pct(m.ch24)}</div>
      </div>
      {/* drag handle — desktop HTML5 DnD via dragHandlers, touch via
          touchDragHandlers. stopPropagation on click so the synthesized tap
          after touch-end doesn't bubble to the row's onClick (= jump to
          detail). */}
      <div
        {...dragHandlers}
        {...touchDragHandlers}
        onClick={(e) => e.stopPropagation()}
        style={{ padding:'8px 4px 8px 8px', color: th.faint, cursor:'grab', touchAction:'none' }}
        aria-label="拖拽排序"
      >
        <svg width="12" height="14" viewBox="0 0 12 14" fill="none">
          <circle cx="3" cy="3"  r="1.3" fill="currentColor"/><circle cx="9" cy="3"  r="1.3" fill="currentColor"/>
          <circle cx="3" cy="7"  r="1.3" fill="currentColor"/><circle cx="9" cy="7"  r="1.3" fill="currentColor"/>
          <circle cx="3" cy="11" r="1.3" fill="currentColor"/><circle cx="9" cy="11" r="1.3" fill="currentColor"/>
        </svg>
      </div>
    </div>
  );
}

// iOS-style swipe to delete. The wrapped row translates left as the user
// drags; releasing past the threshold snaps to the open state revealing a
// red 「删除」 action behind it. Tap outside or the action button closes.
function SwipeToDelete({ t, onDelete, children }) {
  const ACTION_W = 88;
  const TRIGGER  = 36;          // |dx| past which we lock open on release
  const th = aTheme(t);
  const [x, setX] = React.useState(0);
  const [animate, setAnimate] = React.useState(true);
  const startX = React.useRef(0);
  const startY = React.useRef(0);
  const dragX  = React.useRef(0);
  const decided = React.useRef(false);    // gesture direction confirmed
  const isHoriz = React.useRef(false);

  const onTouchStart = (e) => {
    setAnimate(false);
    startX.current = e.touches[0].clientX;
    startY.current = e.touches[0].clientY;
    dragX.current = x;
    decided.current = false;
    isHoriz.current = false;
  };
  const onTouchMove = (e) => {
    const dx = e.touches[0].clientX - startX.current;
    const dy = e.touches[0].clientY - startY.current;
    if (!decided.current && (Math.abs(dx) > 6 || Math.abs(dy) > 6)) {
      decided.current = true;
      isHoriz.current = Math.abs(dx) > Math.abs(dy);
    }
    if (!isHoriz.current) return;
    e.preventDefault?.();
    const next = Math.max(-ACTION_W - 8, Math.min(0, dragX.current + dx));
    setX(next);
  };
  const onTouchEnd = () => {
    setAnimate(true);
    if (x < -TRIGGER) setX(-ACTION_W);
    else setX(0);
  };
  const close = () => { setAnimate(true); setX(0); };

  const isOpen = x !== 0;
  const isDark = th.isDark;
  const actionBg = isDark ? '#dc2626' : '#ef4444';

  // Idempotent fire — both onPointerUp and onClick fire on iOS, so without
  // a guard toggleWatch runs twice (remove → re-add). Lock for 600ms then
  // reset so the same SwipeToDelete instance can be reused on next swipe.
  const firedRef = React.useRef(false);
  const fire = (e) => {
    e.preventDefault();
    e.stopPropagation();
    if (firedRef.current) return;
    firedRef.current = true;
    onDelete();
    close();
    setTimeout(() => { firedRef.current = false; }, 600);
  };

  return (
    <div style={{ position:'relative', overflow:'hidden', background: actionBg }}>
      <button
        onPointerUp={fire}
        onClick={fire}
        style={{
          position:'absolute', right:0, top:0, bottom:0, width: ACTION_W,
          background: actionBg, color:'#fff', border:0, cursor:'pointer',
          fontSize:14, fontWeight:700, letterSpacing:'0.04em',
          display:'flex', alignItems:'center', justifyContent:'center',
          zIndex: 1,
        }}>删除</button>
      <div
        onTouchStart={onTouchStart}
        onTouchMove={onTouchMove}
        onTouchEnd={onTouchEnd}
        onTouchCancel={onTouchEnd}
        // While open, capture-phase intercept any tap on the inner row,
        // close instead of triggering its own onClick (which would jump to detail).
        onClickCapture={isOpen ? (e) => { e.preventDefault(); e.stopPropagation(); close(); } : undefined}
        style={{
          position:'relative', zIndex: 2,
          transform: `translateX(${x}px)`,
          transition: animate ? 'transform 220ms ease-out' : 'none',
          background: th.bg,
          touchAction: 'pan-y',
        }}
      >
        {children}
      </div>
    </div>
  );
}

function AMarkets({ t, nav, onPos, mode: extMode, onModeChange }) {
  const th = aTheme(t);
  const state = HL.useStore();
  // 'watchlist' | 'crypto' | 'stock'. `state.markets` is two Top-20s
  // concatenated; we split here by the namespaced-coin prefix. Mode can be
  // lifted to the parent (Art) so the user's tab choice survives a detour
  // through the detail page; if no parent owns it, fall back to local state.
  const [localMode, setLocalMode] = React.useState('watchlist');
  const mode    = extMode !== undefined ? extMode : localMode;
  const setMode = onModeChange || setLocalMode;
  const [watch, setWatch] = useWatchlist();
  const [searchOpen, setSearchOpen] = React.useState(false);
  const baseCrypto = React.useMemo(
    () => state.markets.filter(m => String(m.sym).indexOf(':') < 0).map(m => m.sym),
    [state.markets]
  );
  const baseStock = React.useMemo(
    () => state.markets.filter(m => String(m.sym).indexOf(':') >= 0).map(m => m.sym),
    [state.markets]
  );
  const baseSyms = mode === 'watchlist' ? watch : mode === 'stock' ? baseStock : baseCrypto;
  const [order, setOrder] = useOrderedList(mode, baseSyms);
  const [dragIdx, setDragIdx] = React.useState(null);
  const [overIdx, setOverIdx] = React.useState(null);

  const rows = order.map(s => state.markets.find(m => m.sym === s)).filter(Boolean);

  const toggleWatch = (sym) => {
    setWatch(w => w.includes(sym) ? w.filter(x => x!==sym) : [...w, sym]);
  };

  // Drag handlers — works on desktop and touch (HTML5 DnD).
  const onDragStart = (i) => (e) => {
    setDragIdx(i);
    try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(i)); } catch {}
  };
  const onDragOver = (i) => (e) => { e.preventDefault(); setOverIdx(i); };
  const onDrop = (i) => (e) => {
    e.preventDefault();
    if (dragIdx == null || dragIdx === i) { setDragIdx(null); setOverIdx(null); return; }
    setOrder(prev => {
      const next = prev.slice();
      const [moved] = next.splice(dragIdx, 1);
      next.splice(i, 0, moved);
      return next;
    });
    setDragIdx(null); setOverIdx(null);
  };
  const onDragEnd = () => { setDragIdx(null); setOverIdx(null); };

  // ── Touch reorder (iOS) ──────────────────────────────────────────────
  // HTML5 DnD doesn't fire on iOS Safari, so we implement a parallel touch
  // pipeline. The user touches the dots-handle on a row and drags vertically;
  // we resolve the hovered row via document.elementFromPoint and commit the
  // reorder on touchend.
  const touchDragRef = React.useRef({ active: false });
  const onTouchDragStart = (i) => (e) => {
    e.stopPropagation();
    touchDragRef.current.active = true;
    setDragIdx(i);
    setOverIdx(i);
  };
  const onTouchDragMove = (e) => {
    if (!touchDragRef.current.active) return;
    e.preventDefault();
    const t = e.touches[0]; if (!t) return;
    const el = document.elementFromPoint(t.clientX, t.clientY);
    const rowEl = el && el.closest && el.closest('[data-row-idx]');
    if (!rowEl) return;
    const idx = parseInt(rowEl.getAttribute('data-row-idx'), 10);
    if (!isNaN(idx)) setOverIdx(idx);
  };
  const onTouchDragEnd = () => {
    if (!touchDragRef.current.active) return;
    touchDragRef.current.active = false;
    if (dragIdx != null && overIdx != null && dragIdx !== overIdx) {
      setOrder(prev => {
        const next = prev.slice();
        const [moved] = next.splice(dragIdx, 1);
        next.splice(overIdx, 0, moved);
        return next;
      });
    }
    setDragIdx(null);
    setOverIdx(null);
  };
  const touchDragHandlers = React.useCallback((i) => ({
    onTouchStart: onTouchDragStart(i),
    onTouchMove:  onTouchDragMove,
    onTouchEnd:   onTouchDragEnd,
    onTouchCancel: onTouchDragEnd,
  }), [dragIdx, overIdx]);

  return (
    <>
      {/* Header */}
      <div style={{ padding:`4px ${th.L.pagePadX + 2}px 8px`, display:'flex', justifyContent:'space-between', alignItems:'flex-end' }}>
        <div>
          <div style={{ fontSize:12, color:th.dim, letterSpacing:'0.18em' }}>市场</div>
          <div style={{ fontSize:22, fontWeight:600, marginTop:2 }}>行情</div>
        </div>
        <button onClick={()=>setSearchOpen(true)} aria-label="搜索币种" style={{
          background:'transparent', border:0, color:th.dim, cursor:'pointer', padding:6,
        }}>
          {Icon.search('currentColor', 18)}
        </button>
      </div>

      {/* Mode tabs — three equal columns, large touch targets, centered
          accent underline indicator that animates between selections. The
          row's own bottom hairline doubles as the list's top divider so we
          drop the list's borderTop below. */}
      <div style={{
        display:'flex', alignItems:'stretch',
        borderBottom: `1px solid ${th.hair}`,
        margin:`0 ${th.L.pagePadX}px`,
      }}>
        {[
          { id:'watchlist', label:'自选' },
          { id:'crypto',    label:'加密货币' },
          { id:'stock',     label:'股市' },
        ].map(it => {
          const on = mode === it.id;
          return (
            <button
              key={it.id}
              onClick={()=>setMode(it.id)}
              style={{
                flex:1,
                position:'relative',
                background:'transparent', border:0,
                padding:'13px 0',
                fontSize:15, fontWeight: on ? 600 : 500,
                color: on ? th.ink : th.dim,
                cursor:'pointer',
                WebkitTapHighlightColor:'transparent',
                transition:'color 0.18s ease',
              }}
            >
              {it.label}
              <span style={{
                position:'absolute',
                left:'50%', bottom:-1,
                transform:'translateX(-50%)',
                width: on ? 28 : 0,
                height: 2.5,
                borderRadius: 2,
                background: th.accent,
                transition:'width 0.22s ease',
              }}/>
            </button>
          );
        })}
      </div>

      {/* List */}
      <div style={{ flex:1, overflow:'auto' }}>
        {rows.length === 0 ? (
          <div style={{ padding:'40px 20px', textAlign:'center', color:th.faint, fontSize:13.5, lineHeight:1.7 }}>
            {mode === 'watchlist' ? (<>
              自选列表为空<br/>
              <span style={{ fontSize:12 }}>点击币种左侧的 ★ 加入自选</span>
            </>) : (<>加载中…</>)}
          </div>
        ) : rows.map((m, i) => {
          const row = (
            <MarketRow
              m={m} t={t}
              watch={watch.includes(m.sym)}
              onToggleWatch={() => toggleWatch(m.sym)}
              onTap={(sym) => onPos?.(sym)}
              dragging={dragIdx === i}
              livePoints={state.midHistory[m.sym]}
            />
          );
          return (
            <div
              key={m.sym}
              data-row-idx={i}
              draggable
              onDragStart={onDragStart(i)}
              onDragOver={onDragOver(i)}
              onDrop={onDrop(i)}
              onDragEnd={onDragEnd}
              style={{
                // The row currently picked up — half-faded, slightly shrunk,
                // accent-tinted background so the user knows what's traveling.
                opacity:   dragIdx === i ? 0.5 : 1,
                transform: dragIdx === i ? 'scale(0.98)' : 'none',
                background: dragIdx === i
                  ? (th.isDark ? 'rgba(92,220,201,0.06)' : 'rgba(13,138,122,0.05)')
                  : 'transparent',
                // Drop-target indicator: a chunky teal bar above the hovered
                // row + soft glow so the insertion point is obvious from a
                // glance. We also pad the top so the row visibly nudges down
                // to "open the gap".
                borderTop:  overIdx === i && dragIdx !== i ? `3px solid ${th.accent}` : '3px solid transparent',
                boxShadow:  overIdx === i && dragIdx !== i ? `0 -1px 10px ${th.accent}55` : 'none',
                paddingTop: overIdx === i && dragIdx !== i ? 4 : 0,
                marginTop: -3,
                transition: 'opacity 140ms ease-out, transform 140ms ease-out, background 140ms, border-color 100ms, box-shadow 140ms, padding-top 140ms',
              }}
            >
              {mode === 'watchlist'
                ? <SwipeToDelete t={t} onDelete={() => toggleWatch(m.sym)}>
                    <MarketRow
                      m={m} t={t}
                      watch={watch.includes(m.sym)}
                      onToggleWatch={() => toggleWatch(m.sym)}
                      onTap={(sym) => onPos?.(sym)}
                      dragging={dragIdx === i}
                      livePoints={state.midHistory[m.sym]}
                      touchDragHandlers={touchDragHandlers(i)}
                    />
                  </SwipeToDelete>
                : <MarketRow
                    m={m} t={t}
                    watch={watch.includes(m.sym)}
                    onToggleWatch={() => toggleWatch(m.sym)}
                    onTap={(sym) => onPos?.(sym)}
                    dragging={dragIdx === i}
                    livePoints={state.midHistory[m.sym]}
                    touchDragHandlers={touchDragHandlers(i)}
                  />}
            </div>
          );
        })}
        <div style={{ height: 8 }}/>
      </div>

      <ATabBar active="markets" onNav={nav} t={t}/>

      <SearchSheet
        t={t}
        open={searchOpen}
        onClose={()=>setSearchOpen(false)}
        watch={watch}
        toggleWatch={(sym)=>setWatch(w => w.includes(sym) ? w.filter(x => x !== sym) : [...w, sym])}
        onPick={(sym)=>{ setSearchOpen(false); onPos?.(sym); }}
        onAddedToWatchlist={()=>setMode('watchlist')}
      />
    </>
  );
}

// Fullscreen search overlay — searches the full HL universe (not just Top20),
// lets the user toggle ★ to add/remove from watchlist, or tap the row to jump
// straight to that coin's detail page.
function SearchSheet({ t, open, onClose, watch, toggleWatch, onPick, onAddedToWatchlist }) {
  const th = aTheme(t);
  const isDark = th.isDark;
  const state = HL.useStore();
  const [q, setQ] = React.useState('');
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    if (!open) { setQ(''); return; }
    const tm = setTimeout(() => { try { inputRef.current?.focus(); } catch (e) {} }, 60);
    return () => clearTimeout(tm);
  }, [open]);

  if (!open) return null;

  // Search across native perps + every HIP-3 stock dex. The search-sheet rows
  // store the namespaced coin (`xyz:NVDA`) — display in the row strips the
  // prefix back to a clean ticker.
  function _rowsFor(uni, ctxs) {
    return (uni || []).map((u, i) => {
      const ctx = (ctxs && ctxs[i]) || {};
      const live = state.mids[u.name];
      const price = live != null ? live : parseFloat(ctx.markPx || 0);
      const prev  = parseFloat(ctx.prevDayPx || 0);
      const ch24  = prev ? ((price - prev) / prev * 100) : 0;
      return { sym: u.name, price, ch24, vol: parseFloat(ctx.dayNtlVlm || 0) };
    });
  }
  const native = _rowsFor(state.universe, state.metaCtx);
  const stocks = [];
  for (const dex in (state.dexUniverses || {})) {
    stocks.push.apply(stocks, _rowsFor(state.dexUniverses[dex], state.dexMetaCtxs[dex]));
  }
  const all = native.concat(stocks).filter(r => r.price > 0);

  const ql = q.trim().toUpperCase();
  let rows;
  if (ql) {
    rows = all
      .filter(r => r.sym.toUpperCase().includes(ql))
      .sort((a, b) => {
        const aStarts = a.sym.toUpperCase().startsWith(ql) ? 0 : 1;
        const bStarts = b.sym.toUpperCase().startsWith(ql) ? 0 : 1;
        if (aStarts !== bStarts) return aStarts - bStarts;
        return b.vol - a.vol;
      })
      .slice(0, 200);
  } else {
    rows = all.slice().sort((a, b) => b.vol - a.vol).slice(0, 100);
  }

  const handleStar = (sym) => {
    const wasIn = watch.includes(sym);
    toggleWatch(sym);
    if (!wasIn) onAddedToWatchlist?.();
  };

  return (
    <div style={{
      position:'absolute', inset:0, background: th.bg, zIndex:95,
      display:'flex', flexDirection:'column',
      // SearchSheet sits inside AFrame's already-safe-area-padded inner div
      // ONLY in standalone mode. In desktop bezel mode, AFrame puts a fixed
      // statusBar pad. Cover both: also reserve env(safe-area-inset-top) here.
      paddingTop: 'env(safe-area-inset-top)',
    }}>
      <div style={{
        padding:`4px ${th.L.pagePadX - 2}px 10px`,
        display:'flex', alignItems:'center', gap:10, flexShrink:0,
      }}>
        <div style={{
          flex:1, ...th.card, borderRadius:12, padding:'9px 12px',
          display:'flex', alignItems:'center', gap:8,
        }}>
          {Icon.search(th.faint, 16)}
          <input
            ref={inputRef}
            type="text"
            inputMode="search"
            autoCapitalize="characters"
            autoCorrect="off"
            spellCheck={false}
            placeholder="搜索币种 · 如 BTC、HYPE、TON"
            value={q}
            onChange={(e)=>setQ(e.target.value)}
            style={{
              flex:1, background:'transparent', border:0, outline:'none', minWidth:0,
              fontSize:16, color: th.ink, fontFamily: th.numFont, padding:0,
            }}
          />
          {q && (
            <button onClick={()=>setQ('')} aria-label="清空" style={{
              background:'transparent', border:0, color:th.faint, cursor:'pointer',
              padding:0, fontSize:18, lineHeight:1, width:18, height:18,
            }}>×</button>
          )}
        </div>
        <button onClick={onClose} style={{
          background:'transparent', border:0, color: th.dim, cursor:'pointer',
          padding:'6px 4px', fontSize:14.5,
        }}>取消</button>
      </div>

      <div style={{ padding:`0 ${th.L.pagePadX}px 6px`, fontSize:12, color:th.faint, letterSpacing:'0.04em' }}>
        {ql ? `匹配 · ${rows.length}` : `热门 · 按 24h 成交量排序`}
      </div>

      <div style={{ flex:1, overflow:'auto', borderTop:`1px solid ${th.hair}` }}>
        {rows.length === 0 ? (
          <div style={{ padding:'40px 20px', textAlign:'center', color:th.faint, fontSize:13.5, lineHeight:1.7 }}>
            没有匹配的币种
          </div>
        ) : rows.map(r => {
          const on  = watch.includes(r.sym);
          const upC = window.pnlColor(r.ch24>=0?1:-1, th.pnlMode);
          return (
            <div key={r.sym} style={{
              display:'flex', alignItems:'center', gap:10,
              padding:'12px 14px', borderBottom:`1px solid ${th.hair}`,
            }}>
              {(() => {
                const split = window.HL.splitCoin && window.HL.splitCoin(r.sym);
                const ticker = split ? split.ticker : r.sym;
                const tag    = split ? split.dex   : 'USDT';
                return (
              <button onClick={()=>onPick?.(r.sym)} style={{
                flex:1, minWidth:0, background:'transparent', border:0, padding:0, textAlign:'left', cursor:'pointer',
                display:'flex', alignItems:'center', gap:10, color: th.ink,
              }}>
                <TokenGlyph sym={r.sym} size={30} radius={9}/>
                <div style={{ flex:1, minWidth:0 }}>
                  <div style={{ fontSize:14, fontWeight:600, letterSpacing:'-0.01em' }}>
                    {ticker}<span style={{ color:th.faint, fontWeight:500, fontSize:11.5, marginLeft:5 }}>{tag}</span>
                  </div>
                  <div style={{ fontSize:11.5, color:th.dim, marginTop:1, fontFamily: th.numFont }}>
                    24h量 {window.fmt.vol(r.vol)}
                  </div>
                </div>
                <div style={{ textAlign:'right', minWidth:80 }}>
                  <div style={{ fontFamily: th.numFont, fontSize:13.5, fontWeight:600, fontVariantNumeric:'tabular-nums' }}>
                    {window.fmt.num(r.price, r.price<10?3:1)}
                  </div>
                  <div style={{ fontFamily: th.numFont, fontSize:11.5, color: upC, marginTop:2, fontWeight:600 }}>
                    {window.fmt.pct(r.ch24)}
                  </div>
                </div>
              </button>
                );
              })()}
              <button onClick={()=>handleStar(r.sym)} aria-label={on ? '从自选移除' : '加入自选'} style={{
                background:'transparent', border:0, padding:8, cursor:'pointer',
                color: on ? '#f0b90b' : th.faint, lineHeight:0,
              }}>
                <svg width="20" height="20" viewBox="0 0 24 24"
                  fill={on ? '#f0b90b' : 'none'} stroke="currentColor" strokeWidth="1.6" strokeLinejoin="round">
                  <path d="M12 3l2.7 5.6 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.9 1-6.1L3.2 9.5l6.1-.9z"/>
                </svg>
              </button>
            </div>
          );
        })}
        <div style={{ height: 16 }}/>
      </div>
    </div>
  );
}

window.AMarkets = AMarkets;
