// =============================================================================
// 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']));
  React.useEffect(() => writeJSON(WATCHLIST_KEY, list), [list]);
  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]);
  // If the underlying base symbols change (mode switch), resync.
  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]);
  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',
      }}>
      <TokenGlyph sym={m.sym} size={30} radius={9}/>
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ fontSize:14, fontWeight:600, letterSpacing:'-0.01em' }}>{m.sym}<span style={{ color:th.faint, fontWeight:500, fontSize:11.5, marginLeft:5 }}>USDT</span></div>
        <div style={{ fontSize:11.5, color:th.dim, marginTop:1, fontFamily: th.numFont }}>
          24h量 ${(m.vol24/1e9).toFixed(2)}B
        </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 */}
      <div
        {...dragHandlers}
        {...touchDragHandlers}
        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';

  // Use pointerdown/up so the delete fires *immediately* on touch — no waiting
  // for the iOS synthesized click (which can be eaten by the swipe gesture
  // logic on adjacent rows).
  const fire = (e) => {
    e.preventDefault();
    e.stopPropagation();
    onDelete();
    close();
  };

  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 }) {
  const th = aTheme(t);
  const state = HL.useStore();
  const [mode, setMode] = React.useState('watchlist'); // 'watchlist' | 'top'
  const [watch, setWatch] = useWatchlist();
  const [searchOpen, setSearchOpen] = React.useState(false);
  const baseTop = React.useMemo(() => state.markets.map(m => m.sym), [state.markets]);
  const baseSyms = mode === 'watchlist' ? watch : baseTop;
  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 */}
      <div style={{ padding:`0 ${th.L.pagePadX}px 6px`, display:'flex', gap:2, alignItems:'center' }}>
        {[
          { id:'watchlist', label:'自选', star:true },
          { id:'top',       label:'Top 20' },
        ].map(it => {
          const on = mode === it.id;
          return (
            <button key={it.id} onClick={()=>setMode(it.id)} style={{
              background:'transparent', border:0, padding:'8px 12px',
              fontSize:14, fontWeight: on?700:500,
              color: on ? th.ink : th.dim,
              borderBottom: `2px solid ${on ? th.accent : 'transparent'}`,
              cursor:'pointer', display:'inline-flex', alignItems:'center', gap:4,
              whiteSpace:'nowrap', flexShrink:0,
            }}>
              {it.star && <span style={{ color: on ? '#f0b90b' : th.faint }}>★</span>}
              {it.label}
            </button>
          );
        })}
        <div style={{ flex:1 }}/>
      </div>

      {/* List */}
      <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 }}>
            自选列表为空<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={{
                opacity: dragIdx === i ? 0.4 : 1,
                borderTop: overIdx === i && dragIdx !== i ? `2px solid ${th.accent}` : '2px solid transparent',
                marginTop: -2,
              }}
            >
              {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;

  const all = (state.universe || []).map((u, i) => {
    const ctx = state.metaCtx[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) };
  }).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}`,
            }}>
              <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' }}>
                    {r.sym}<span style={{ color:th.faint, fontWeight:500, fontSize:11.5, marginLeft:5 }}>USDT</span>
                  </div>
                  <div style={{ fontSize:11.5, color:th.dim, marginTop:1, fontFamily: th.numFont }}>
                    24h量 {r.vol >= 1e9 ? '$'+(r.vol/1e9).toFixed(2)+'B' : '$'+(r.vol/1e6).toFixed(1)+'M'}
                  </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;
