import type { MarketInfo, TickerData } from '../hooks/useMarketTicker'; export type MarketSortKey = 'name' | 'price' | 'change' | 'volume'; export type MarketSortDir = 'asc' | 'desc'; export interface MarketSearchRow { market: string; korean_name: string; english_name: string; symbol: string; } function compareNullableNum( a: number | null | undefined, b: number | null | undefined, mul: number, ): number { if (a == null && b == null) return 0; if (a == null) return 1; if (b == null) return -1; return mul * (a - b); } export function marketInfosToRows(infos: MarketInfo[]): MarketSearchRow[] { return infos.map(m => ({ market: m.market, korean_name: m.koreanName, english_name: m.englishName, symbol: m.market.replace('KRW-', ''), })); } export function filterMarketRows(rows: MarketSearchRow[], query: string): MarketSearchRow[] { const q = query.trim(); if (!q) return rows; const upper = q.toUpperCase(); const lower = q.toLowerCase(); return rows.filter(m => m.korean_name.includes(q) || m.symbol.toUpperCase().includes(upper) || m.english_name.toLowerCase().includes(lower) || m.market.toUpperCase().includes(upper), ); } /** MarketSearchPanel 과 동일 — 관심종목 우선·거래대금 또는 사용자 정렬 */ export function buildSortedMarketRows( rows: MarketSearchRow[], tickers: Map, favs: Set, sortKey: MarketSortKey | null, sortDir: MarketSortDir, ): MarketSearchRow[] { if (!sortKey) { return [...rows].sort((a, b) => { const fa = favs.has(a.market) ? 1 : 0; const fb = favs.has(b.market) ? 1 : 0; if (fb !== fa) return fb - fa; const va = tickers.get(a.market)?.accTradePrice24 ?? 0; const vb = tickers.get(b.market)?.accTradePrice24 ?? 0; return vb - va; }); } const mul = sortDir === 'asc' ? 1 : -1; return [...rows].sort((a, b) => { switch (sortKey) { case 'name': return mul * a.korean_name.localeCompare(b.korean_name, 'ko'); case 'price': return compareNullableNum( tickers.get(a.market)?.tradePrice, tickers.get(b.market)?.tradePrice, mul, ); case 'change': return compareNullableNum( tickers.get(a.market)?.changeRate, tickers.get(b.market)?.changeRate, mul, ); case 'volume': return mul * ( (tickers.get(a.market)?.accTradePrice24 ?? 0) - (tickers.get(b.market)?.accTradePrice24 ?? 0) ); default: return 0; } }); } export function buildSortedMarketCodes( marketInfos: MarketInfo[], tickers: Map, favs: Set, query: string, sortKey: MarketSortKey | null, sortDir: MarketSortDir, ): string[] { const rows = filterMarketRows(marketInfosToRows(marketInfos), query); return buildSortedMarketRows(rows, tickers, favs, sortKey, sortDir).map(r => r.market); } export function adjacentMarketInList( orderedMarkets: string[], currentMarket: string, direction: 'prev' | 'next', ): string | null { if (orderedMarkets.length === 0) return null; let idx = orderedMarkets.indexOf(currentMarket); if (idx < 0) { idx = direction === 'next' ? -1 : orderedMarkets.length; } const nextIdx = direction === 'prev' ? idx - 1 : idx + 1; if (nextIdx < 0 || nextIdx >= orderedMarkets.length) return null; return orderedMarkets[nextIdx] ?? null; }