import { useMemo, useState, type ReactNode } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { AnimatePresence } from 'framer-motion'
import {
  Activity,
  Crown,
  Layers3,
  RefreshCw,
  Repeat,
  Search,
  Settings2,
  TrendingDown,
  TrendingUp,
} from 'lucide-react'
import { PageHeader } from '@/components/PageHeader'
import { EmptyState } from '@/components/EmptyState'
import { AnalysisConfigDialog, PresetFetchState, type AnalysisFieldConfig } from '@/components/analysis-shared'
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
import { RpsRotationDialog } from '@/components/RpsRotationDialog'
import { api, type MarketSnapshotRow } from '@/lib/api'
import { QK } from '@/lib/queryKeys'
import { storage } from '@/lib/storage'
import { fmtBigNum, fmtPct, priceColorClass } from '@/lib/format'
import { cn } from '@/lib/cn'
import { resolveDimension, type DimensionGroup, type StockRow } from '@/lib/analysis-adapter'

const KEYWORDS = ['concept', '概念', 'theme', '题材', '板块']
const CANDIDATE_FIELDS = ['concept', '概念', 'theme', '题材', '板块', 'concept_name', '概念名称']
const PAGE_LIMIT = 12000
const MAX_RENDERED_CONCEPTS = 120
const MAX_RENDERED_STOCKS = 160

type SortMode = 'heat' | 'avgPct' | 'leader' | 'amount' | 'down'

interface EnrichedStock extends MarketSnapshotRow {
  leaderScore: number
  leaderParts: {
    momentum: number
    turnover: number
    amount: number
    cap: number
    volume: number
    boards: number
  }
}

interface ConceptStat {
  key: string
  stocks: EnrichedStock[]
  count: number
  avgPct: number | null
  medianPct: number | null
  upCount: number
  downCount: number
  flatCount: number
  upRate: number
  strongCount: number
  weakCount: number
  totalAmount: number
  avgTurnover: number | null
  avgVolRatio: number | null
  leader: EnrichedStock | null
  heatScore: number
  riskScore: number
}

function loadConfig(): AnalysisFieldConfig {
  return storage.conceptAnalysisConfig.get({}) as AnalysisFieldConfig
}

function saveConfig(c: AnalysisFieldConfig) {
  storage.conceptAnalysisConfig.set(c)
}

function pickBestConfig(
  configs: { id: string; label: string; description?: string; fields: { name: string; label: string }[] }[],
): string {
  let best = ''
  let bestScore = 0
  for (const c of configs) {
    const haystack = [c.id, c.label, c.description ?? '', ...c.fields.flatMap(f => [f.name, f.label])].join(' ').toLowerCase()
    const score = KEYWORDS.reduce((n, k) => n + (haystack.includes(k) ? 1 : 0), 0)
    if (score > bestScore) {
      bestScore = score
      best = c.id
    }
  }
  return best
}

function symbolKeys(symbol: unknown): string[] {
  const raw = String(symbol ?? '').trim()
  if (!raw) return []
  const plain = raw.replace(/\.\w+$/, '')
  return Array.from(new Set([raw, plain]))
}

function buildMarketMap(rows: MarketSnapshotRow[]) {
  const map = new Map<string, MarketSnapshotRow>()
  for (const r of rows) {
    for (const key of symbolKeys(r.symbol)) map.set(key, r)
  }
  return map
}

function clamp01(v: number) {
  if (!Number.isFinite(v)) return 0
  return Math.max(0, Math.min(1, v))
}

function num(v: unknown): number | null {
  return typeof v === 'number' && Number.isFinite(v) ? v : null
}

function avg(values: number[]) {
  return values.length ? values.reduce((a, b) => a + b, 0) / values.length : null
}

function median(values: number[]) {
  if (!values.length) return null
  const sorted = [...values].sort((a, b) => a - b)
  const mid = Math.floor(sorted.length / 2)
  return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
}

function leaderScore(stock: MarketSnapshotRow) {
  const pct = num(stock.change_pct) ?? 0
  const turnover = num(stock.turnover_rate) ?? 0
  const amount = num(stock.amount) ?? 0
  const cap = num(stock.float_market_cap) ?? num(stock.market_cap) ?? 0
  const volRatio = num(stock.vol_ratio_5d) ?? 1
  const boards = num(stock.consecutive_limit_ups) ?? 0

  const parts = {
    momentum: clamp01((pct + 0.02) / 0.12),
    turnover: clamp01(Math.log1p(Math.max(turnover, 0)) / Math.log1p(30)),
    amount: clamp01(Math.log1p(Math.max(amount, 0)) / Math.log1p(20_000_000_000)),
    cap: clamp01(Math.log1p(Math.max(cap, 0)) / Math.log1p(300_000_000_000)),
    volume: clamp01((volRatio - 1) / 4),
    boards: clamp01(boards / 5),
  }

  const score = (
    parts.momentum * 0.35 +
    parts.turnover * 0.22 +
    parts.amount * 0.18 +
    parts.cap * 0.15 +
    parts.volume * 0.07 +
    parts.boards * 0.03
  ) * 100

  return { score, parts }
}

function enrichStock(stock: StockRow, marketMap: Map<string, MarketSnapshotRow>): EnrichedStock {
  const market = symbolKeys(stock.symbol ?? stock.code).map(k => marketMap.get(k)).find(Boolean) ?? {}
  const merged = { ...stock, ...market } as MarketSnapshotRow & StockRow
  const ls = leaderScore(merged)
  return {
    ...merged,
    symbol: String(merged.symbol ?? stock.symbol ?? stock.code ?? ''),
    name: merged.name ?? String(stock.name ?? stock['股票简称'] ?? ''),
    leaderScore: ls.score,
    leaderParts: ls.parts,
  }
}

function calcConceptStat(group: DimensionGroup, marketMap: Map<string, MarketSnapshotRow>): ConceptStat {
  const seen = new Set<string>()
  const stocks = group.stocks
    .map(s => enrichStock(s, marketMap))
    .filter(s => {
      const key = String(s.symbol ?? '')
      if (!key) return false
      if (seen.has(key)) return false
      seen.add(key)
      return true
    })

  const pctValues = stocks.map(s => num(s.change_pct)).filter((v): v is number => v != null)
  const turnoverValues = stocks.map(s => num(s.turnover_rate)).filter((v): v is number => v != null)
  const volValues = stocks.map(s => num(s.vol_ratio_5d)).filter((v): v is number => v != null)
  const totalAmount = stocks.reduce((sum, s) => sum + (num(s.amount) ?? 0), 0)
  const upCount = pctValues.filter(v => v > 0).length
  const downCount = pctValues.filter(v => v < 0).length
  const flatCount = Math.max(0, stocks.length - upCount - downCount)
  const strongCount = pctValues.filter(v => v >= 0.05).length
  const weakCount = pctValues.filter(v => v <= -0.05).length
  const leader = stocks.length ? [...stocks].sort((a, b) => b.leaderScore - a.leaderScore)[0] : null
  const avgPct = avg(pctValues)
  const medianPct = median(pctValues)
  const upRate = pctValues.length ? upCount / pctValues.length : 0
  const amountScore = clamp01(Math.log1p(totalAmount) / Math.log1p(80_000_000_000))
  const strongScore = stocks.length ? clamp01(strongCount / Math.max(1, stocks.length * 0.18)) : 0
  const leaderPart = clamp01((leader?.leaderScore ?? 0) / 100)
  const avgPart = clamp01(((avgPct ?? 0) + 0.02) / 0.09)
  const upPart = clamp01((upRate - 0.35) / 0.55)

  const heatScore = (avgPart * 0.38 + upPart * 0.2 + strongScore * 0.16 + amountScore * 0.12 + leaderPart * 0.14) * 100
  const riskScore = (clamp01((-(avgPct ?? 0) + 0.01) / 0.08) * 0.55 + clamp01(weakCount / Math.max(1, stocks.length * 0.18)) * 0.45) * 100

  return {
    key: group.key,
    stocks,
    count: stocks.length,
    avgPct,
    medianPct,
    upCount,
    downCount,
    flatCount,
    upRate,
    strongCount,
    weakCount,
    totalAmount,
    avgTurnover: avg(turnoverValues),
    avgVolRatio: avg(volValues),
    leader,
    heatScore,
    riskScore,
  }
}

function statSort(mode: SortMode) {
  return (a: ConceptStat, b: ConceptStat) => {
    switch (mode) {
      case 'avgPct': return (b.avgPct ?? -Infinity) - (a.avgPct ?? -Infinity)
      case 'leader': return (b.leader?.leaderScore ?? -Infinity) - (a.leader?.leaderScore ?? -Infinity)
      case 'amount': return b.totalAmount - a.totalAmount
      case 'down': return (a.avgPct ?? Infinity) - (b.avgPct ?? Infinity)
      case 'heat':
      default: return b.heatScore - a.heatScore
    }
  }
}

export function ConceptAnalysis() {
  const [fieldConfig, setFieldConfig] = useState<AnalysisFieldConfig>(loadConfig)
  const [showConfig, setShowConfig] = useState(false)
  const [search, setSearch] = useState('')
  const [selectedKey, setSelectedKey] = useState<string | null>(null)
  const [sortMode, setSortMode] = useState<SortMode>('heat')
  const [previewSymbol, setPreviewSymbol] = useState<string | null>(null)
  const [previewName, setPreviewName] = useState<string>('')
  const [showRps, setShowRps] = useState(false)

  const configsQuery = useQuery({ queryKey: QK.extData, queryFn: api.extDataList })
  const availableConfigs = configsQuery.data?.items ?? []
  // 用户配置的 configId 可能已失效 (扩展数据被删除), 此时回退到自动选择,
  // 避免用失效 ID 请求接口报错; 用户仍可点配置按钮重新选择。
  const preferredConfigId = fieldConfig.configId || pickBestConfig(availableConfigs)
  const preferredConfig = availableConfigs.find(c => c.id === preferredConfigId)
  const activeConfigId = preferredConfig ? preferredConfigId : pickBestConfig(availableConfigs)
  const activeConfig = availableConfigs.find(c => c.id === activeConfigId)

  const rowsQuery = useQuery({
    queryKey: QK.extDataRows(activeConfigId, undefined, PAGE_LIMIT),
    queryFn: () => api.extDataRows(activeConfigId, { limit: PAGE_LIMIT }),
    enabled: !!activeConfigId,
  })

  // 内置概念预设 (ext_gn_ths) 手动获取数据
  const PRESET_CONCEPT_ID = 'ext_gn_ths'
  const queryClient = useQueryClient()
  const fetchMutation = useMutation({
    mutationFn: () => api.extDataPresetFetch(PRESET_CONCEPT_ID),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: QK.extData })
      queryClient.invalidateQueries({ queryKey: QK.extDataRows(PRESET_CONCEPT_ID, undefined, PAGE_LIMIT) })
    },
  })
  // 是否处于「内置概念预设存在但无数据」状态 → 显示获取按钮
  const needsConceptFetch =
    !!activeConfig && activeConfig.id === PRESET_CONCEPT_ID &&
    !rowsQuery.isLoading && (rowsQuery.data?.total ?? 0) === 0

  const marketQuery = useQuery({
    queryKey: QK.marketSnapshot,
    queryFn: api.marketSnapshot,
    staleTime: 60_000,
  })

  const marketMap = useMemo(() => buildMarketMap(marketQuery.data?.rows ?? []), [marketQuery.data?.rows])
  const resolved = useMemo(
    () => resolveDimension(rowsQuery.data, activeConfig, fieldConfig.dimensionField ? [fieldConfig.dimensionField, ...CANDIDATE_FIELDS] : CANDIDATE_FIELDS),
    [rowsQuery.data, activeConfig, fieldConfig.dimensionField],
  )

  const stats = useMemo(() => {
    return resolved.groups
      .map(g => calcConceptStat(g, marketMap))
      .filter(s => s.count > 0)
  }, [resolved.groups, marketMap])

  const filteredStats = useMemo(() => {
    const q = search.trim().toLowerCase()
    const base = q ? stats.filter(s => s.key.toLowerCase().includes(q)) : stats
    return [...base].sort(statSort(sortMode))
  }, [stats, search, sortMode])

  const selected = filteredStats.find(s => s.key === selectedKey) ?? filteredStats[0] ?? null
  const leading = useMemo(() => [...stats].sort(statSort('heat')).slice(0, 10), [stats])
  const falling = useMemo(() => [...stats].sort(statSort('down')).slice(0, 10), [stats])
  const activeConcept = useMemo(() => [...stats].sort(statSort('amount'))[0] ?? null, [stats])
  const conceptBreadth = useMemo(() => {
    const priced = stats.filter(s => s.avgPct != null)
    return {
      up: priced.filter(s => (s.avgPct ?? 0) > 0).length,
      down: priced.filter(s => (s.avgPct ?? 0) < 0).length,
      flat: priced.filter(s => s.avgPct === 0).length,
    }
  }, [stats])

  const totalSymbols = useMemo(() => {
    const set = new Set<string>()
    stats.forEach(s => s.stocks.forEach(st => { if (st.symbol) set.add(st.symbol) }))
    return set.size
  }, [stats])

  const handleSaveConfig = (c: AnalysisFieldConfig) => {
    setFieldConfig(c)
    saveConfig(c)
    setSelectedKey(null)
  }

  if (configsQuery.isLoading) {
    return <div className="flex h-full items-center justify-center"><RefreshCw className="h-5 w-5 animate-spin text-muted" /></div>
  }

  if (!activeConfig) {
    // 极端情况: 无任何概念配置。仍提供一键获取内置概念数据入口
    return (
      <>
        <div className="flex h-full flex-col">
          <PageHeader
            title="概念分析"
            right={
              <button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
                <Settings2 className="h-4 w-4" />
              </button>
            }
          />
          <PresetFetchState
            title="暂无概念数据"
            hint="从同花顺获取概念分类数据后即可使用概念分析"
            isLoading={fetchMutation.isPending}
            error={fetchMutation.error}
            onFetch={() => fetchMutation.mutate()}
          />
        </div>
        <AnimatePresence>
          {showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} />}
        </AnimatePresence>
      </>
    )
  }

  return (
    <>
      <PageHeader
        title="概念分析"
        subtitle={`${marketQuery.data?.as_of ?? rowsQuery.data?.date ?? '最新'} · ${stats.length} 个概念 · ${totalSymbols} 只标的`}
        right={
          <div className="flex items-center gap-1">
            {/* RPS 轮动: 打开涨幅轮动矩阵对话框 */}
            <button
              onClick={() => setShowRps(true)}
              className="inline-flex items-center gap-1 rounded-btn border border-amber-400/40 bg-amber-400/15 px-2.5 py-1.5 text-[11px] text-amber-400 font-medium transition-colors hover:bg-amber-400/25 hover:border-amber-400/60"
              title="概念涨幅轮动矩阵"
            >
              <Repeat className="h-3.5 w-3.5" />涨幅RPS轮动分析
            </button>
            <button
              onClick={() => { rowsQuery.refetch(); marketQuery.refetch() }}
              disabled={rowsQuery.isFetching || marketQuery.isFetching}
              className="p-1.5 text-muted hover:bg-surface disabled:opacity-50"
              title="刷新"
            >
              <RefreshCw className={cn('h-4 w-4', (rowsQuery.isFetching || marketQuery.isFetching) && 'animate-spin')} />
            </button>
            <button onClick={() => setShowConfig(true)} className="p-1.5 text-muted hover:bg-surface hover:text-accent" title="配置数据源">
              <Settings2 className="h-4 w-4" />
            </button>
          </div>
        }
      />

      <div className="min-h-full bg-[radial-gradient(circle_at_12%_0%,rgba(59,130,246,0.12),transparent_28%),radial-gradient(circle_at_85%_8%,rgba(244,63,94,0.08),transparent_28%)] px-6 py-5">
        <div className="mx-auto max-w-[1440px] space-y-5">
          <HeroPanel leading={leading[0]} falling={falling[0]} activeConcept={activeConcept} conceptBreadth={conceptBreadth} />

          <MarketPulse
            leading={leading}
            falling={falling}
            selectedKey={selected?.key ?? null}
            onSelect={setSelectedKey}
            onStockClick={(sym, name) => { setPreviewSymbol(sym); setPreviewName(name ?? '') }}
          />

          {stats.length > 0 ? (
            <div className="grid grid-cols-1 gap-4 xl:grid-cols-[18rem_1fr]">
              <ConceptRail
                stats={filteredStats.slice(0, MAX_RENDERED_CONCEPTS)}
                selectedKey={selected?.key ?? null}
                search={search}
                sortMode={sortMode}
                onSearch={v => { setSearch(v); setSelectedKey(null) }}
                onSort={setSortMode}
                onSelect={setSelectedKey}
              />
              <ConceptFocus stat={selected} onStockClick={(sym, name) => { setPreviewSymbol(sym); setPreviewName(name ?? '') }} />
            </div>
          ) : rowsQuery.isLoading ? (
            <div className="rounded-2xl border border-border bg-surface px-6 py-16 text-center text-sm text-muted">正在计算概念强度...</div>
          ) : needsConceptFetch ? (
            <PresetFetchState
              title="未获取概念数据"
              hint="内置概念数据源已就绪,点击下方按钮从同花顺获取概念分类数据"
              isLoading={fetchMutation.isPending}
              error={fetchMutation.error}
              onFetch={() => fetchMutation.mutate()}
            />
          ) : (
            <EmptyState icon={Layers3} title="未匹配到概念数据" hint={resolved.hint || '请检查扩展数据是否包含概念/题材相关字段'} />
          )}
        </div>
      </div>

      <AnimatePresence>
        {showConfig && <AnalysisConfigDialog currentConfig={fieldConfig} onSave={handleSaveConfig} onClose={() => setShowConfig(false)} />}
      </AnimatePresence>

      {previewSymbol && (
        <StockPreviewDialog
          symbol={previewSymbol}
          name={previewName}
          onClose={() => { setPreviewSymbol(null); setPreviewName('') }}
        />
      )}

      <AnimatePresence>
        {showRps && <RpsRotationDialog onClose={() => setShowRps(false)} />}
      </AnimatePresence>
    </>
  )
}

function HeroPanel({
  leading,
  falling,
  activeConcept,
  conceptBreadth,
}: {
  leading?: ConceptStat
  falling?: ConceptStat
  activeConcept?: ConceptStat | null
  conceptBreadth: { up: number; down: number; flat: number }
}) {
  return (
    <div className="grid grid-cols-2 gap-2 md:grid-cols-5">
      <HeroMetric icon={TrendingUp} label="最强主线" value={leading?.key ?? '—'} hint={leading?.avgPct != null ? <span className={priceColorClass(leading.avgPct)}>{fmtPct(leading.avgPct)}</span> : '等待行情'} tone="up" />
      <HeroMetric icon={TrendingDown} label="最大风险" value={falling?.key ?? '—'} hint={falling?.avgPct != null ? <span className={priceColorClass(falling.avgPct)}>{fmtPct(falling.avgPct)}</span> : '等待行情'} tone="down" />
      <HeroMetric
        icon={Activity}
        label="涨跌板块"
        value={<><span className="text-bull">{conceptBreadth.up}</span><span className="mx-1 text-muted">/</span><span className="text-bear">{conceptBreadth.down}</span></>}
        hint={<><span className="text-bull">上涨</span><span className="mx-1 text-muted">/</span><span className="text-bear">下跌</span>{conceptBreadth.flat ? <span className="text-muted"> · 平 {conceptBreadth.flat}</span> : null}</>}
        tone="blue"
      />
      <HeroMetric icon={Activity} label="资金活跃" value={activeConcept?.key ?? '—'} hint={activeConcept ? fmtBigNum(activeConcept.totalAmount) : '等待行情'} tone="blue" />
      <HeroMetric icon={Crown} label="龙头算法" value="6 因子" hint="强势 + 承接 + 容量" tone="gold" />
    </div>
  )
}

function HeroMetric({ icon: Icon, label, value, hint, tone }: {
  icon: typeof TrendingUp
  label: string
  value: ReactNode
  hint: ReactNode
  tone: 'up' | 'down' | 'gold' | 'blue'
}) {
  const toneClass = {
    up: 'text-bull bg-bull/10',
    down: 'text-bear bg-bear/10',
    gold: 'text-amber-300 bg-amber-400/10',
    blue: 'text-blue-300 bg-blue-400/10',
  }[tone]
  const valueClass = {
    up: 'text-bull',
    down: 'text-bear',
    gold: 'text-amber-300',
    blue: 'text-foreground',
  }[tone]
  return (
    <div className="rounded-xl border border-border bg-surface px-3 py-2">
      <div className="flex items-center justify-between text-[11px] text-muted">
        <span>{label}</span>
        <span className={cn('rounded-md p-1', toneClass)}><Icon className="h-3.5 w-3.5" /></span>
      </div>
      <div className={cn('mt-1 truncate text-sm font-semibold', valueClass)}>{value}</div>
      <div className="mt-0.5 truncate text-[11px] text-muted">{hint}</div>
    </div>
  )
}

function MarketPulse({
  leading,
  falling,
  selectedKey,
  onSelect,
  onStockClick,
}: {
  leading: ConceptStat[]
  falling: ConceptStat[]
  selectedKey: string | null
  onSelect: (key: string) => void
  onStockClick: (symbol: string, name?: string) => void
}) {
  return (
    <div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
      <PulseList title="领涨主线" items={leading} mode="up" selectedKey={selectedKey} onSelect={onSelect} onStockClick={onStockClick} />
      <PulseList title="领跌方向" items={falling} mode="down" selectedKey={selectedKey} onSelect={onSelect} onStockClick={onStockClick} />
    </div>
  )
}

function PulseList({
  title,
  items,
  mode,
  selectedKey,
  onSelect,
  onStockClick,
}: {
  title: string
  items: ConceptStat[]
  mode: 'up' | 'down'
  selectedKey: string | null
  onSelect: (key: string) => void
  onStockClick: (symbol: string, name?: string) => void
}) {
  const toneText = mode === 'up' ? 'text-bull' : 'text-bear'
  const toneBorder = mode === 'up' ? 'border-bull/20' : 'border-bear/20'
  const toneBg = mode === 'up' ? 'bg-bull/10' : 'bg-bear/10'
  const toneHover = mode === 'up' ? 'hover:border-bull/35' : 'hover:border-bear/35'

  return (
    <div className={cn('rounded-xl border bg-base/35 p-2', toneBorder)}>
      <div className="mb-1.5 flex items-center justify-between px-1">
        <div className={cn('flex items-center gap-1.5 text-xs font-medium', toneText)}>
          {mode === 'up' ? <TrendingUp className="h-3.5 w-3.5" /> : <TrendingDown className="h-3.5 w-3.5" />}
          {title}
        </div>
        <span className="rounded-full bg-elevated/60 px-2 py-0.5 text-[10px] text-muted">Top 10</span>
      </div>
      <div className="space-y-1">
        {items.map((item, idx) => {
          const active = selectedKey === item.key
          const leaders = [...item.stocks].sort((a, b) => b.leaderScore - a.leaderScore).slice(0, 3)
          const upPct = item.count > 0 ? (item.upCount / item.count) * 100 : 0
          const downPct = item.count > 0 ? (item.downCount / item.count) * 100 : 0
          const flatPct = Math.max(0, 100 - upPct - downPct)
          return (
            <button
              key={item.key}
              onClick={() => onSelect(item.key)}
              className={cn(
                'block w-full rounded-lg border border-transparent bg-surface/45 px-2 py-1.5 text-left transition-colors',
                active ? cn('bg-blue-400/[0.08]', toneBorder) : cn('hover:bg-elevated/35', toneHover),
              )}
            >
              <div className="grid gap-2 md:grid-cols-[minmax(0,0.9fr)_minmax(16rem,1.1fr)] md:items-center">
                <div className="min-w-0">
                  <div className="flex min-w-0 items-center gap-2">
                    <span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded-md font-mono text-[10px]', idx < 3 ? cn(toneBg, toneText) : 'bg-elevated/70 text-muted')}>{idx + 1}</span>
                    <div className="min-w-0 flex-1">
                      <div className="flex min-w-0 items-center gap-2">
                        <span className="truncate text-xs font-medium text-foreground">{item.key}</span>
                        <span className="shrink-0 text-[10px] text-muted">
                          <span className="text-bull">{item.upCount}</span>涨
                          <span className="mx-0.5 text-muted/40">/</span>
                          <span className="text-bear">{item.downCount}</span>跌
                        </span>
                        <span className={cn('ml-auto shrink-0 font-mono text-[10px] tabular-nums', priceColorClass(item.avgPct))}>{item.avgPct != null ? fmtPct(item.avgPct) : '—'}</span>
                      </div>
                    </div>
                  </div>
                  <div className="ml-7 mt-1">
                    <div className="flex h-1 overflow-hidden rounded-full bg-elevated">
                      <div className="h-full bg-bull/70" style={{ width: `${upPct}%` }} />
                      {flatPct > 0 && <div className="h-full bg-muted/25" style={{ width: `${flatPct}%` }} />}
                      <div className="h-full bg-bear/70" style={{ width: `${downPct}%` }} />
                    </div>
                  </div>
                </div>
                <div className="grid min-w-0 grid-cols-3 gap-1">
                  {Array.from({ length: 3 }).map((_, i) => {
                    const stock = leaders[i]
                    return stock ? (
                      <span key={stock.symbol} title={stock.name || stock.symbol} onClick={e => { e.stopPropagation(); onStockClick(stock.symbol, stock.name || undefined) }} className={cn('flex min-w-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-[10px] cursor-pointer hover:brightness-125', i === 0 ? 'bg-amber-300/10 text-foreground' : 'bg-elevated/60 text-secondary')}>
                        <span className="flex min-w-0 items-center gap-1">
                          <span className="min-w-0 truncate font-medium">{stock.name || stock.symbol}</span>
                        </span>
                        <span className={cn('shrink-0 font-mono', priceColorClass(stock.change_pct))}>{stock.change_pct != null ? fmtPct(stock.change_pct) : '—'}</span>
                      </span>
                    ) : <span key={i} className="rounded-md bg-elevated/30 px-1.5 py-0.5 text-[10px] text-muted/40">—</span>
                  })}
                </div>
              </div>
            </button>
          )
        })}
      </div>
    </div>
  )
}

function ConceptRail({
  stats,
  selectedKey,
  search,
  sortMode,
  onSearch,
  onSort,
  onSelect,
}: {
  stats: ConceptStat[]
  selectedKey: string | null
  search: string
  sortMode: SortMode
  onSearch: (v: string) => void
  onSort: (v: SortMode) => void
  onSelect: (v: string) => void
}) {
  return (
    <section className="rounded-2xl border border-border bg-surface p-2.5">
      <div className="px-1 pb-2.5">
        <div className="flex items-center justify-between">
          <h3 className="text-sm font-semibold text-foreground">概念矩阵</h3>
          <span className="text-[10px] text-muted">Top {stats.length}</span>
        </div>
        <div className="mt-2 relative">
          <Search className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted" />
          <input value={search} onChange={e => onSearch(e.target.value)} placeholder="搜索概念" className="h-8 w-full rounded-lg border border-border bg-base pl-8 pr-3 text-xs text-foreground outline-none focus:border-accent/50" />
        </div>
        <div className="mt-2 grid grid-cols-5 overflow-hidden rounded-lg border border-border text-[10px]">
          {([
            ['heat', '强度'], ['avgPct', '涨幅'], ['leader', '龙头'], ['amount', '成交'], ['down', '跌幅'],
          ] as [SortMode, string][]).map(([key, label]) => (
            <button key={key} onClick={() => onSort(key)} className={cn('py-1.5 transition-colors', sortMode === key ? 'bg-accent/15 text-accent' : 'bg-base text-muted hover:text-foreground')}>{label}</button>
          ))}
        </div>
      </div>
      <div className="max-h-[620px] overflow-auto rounded-lg border border-border/50">
        {stats.map(item => {
          const active = selectedKey === item.key
          return (
            <button key={item.key} onClick={() => onSelect(item.key)} className={cn('w-full border-b border-border/50 px-2.5 py-2 text-left transition-colors last:border-b-0', active ? 'bg-blue-400/[0.08]' : 'hover:bg-elevated/40')}>
              <div className="flex items-center gap-2">
                <span className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">{item.key}</span>
                <span className={cn('font-mono text-xs', priceColorClass(item.avgPct))}>{item.avgPct != null ? fmtPct(item.avgPct) : '—'}</span>
              </div>
              <div className="mt-1 flex items-center gap-2 text-[10px] text-muted">
                <span>{item.count}只</span>
                <span className="text-bull">{item.upCount}涨</span>
                <span className="text-bear">{item.downCount}跌</span>
                <span className="ml-auto">强度 {item.heatScore.toFixed(0)}</span>
              </div>
            </button>
          )
        })}
      </div>
    </section>
  )
}

function ConceptFocus({ stat, onStockClick }: { stat: ConceptStat | null; onStockClick: (symbol: string, name?: string) => void }) {
  if (!stat) return null
  const stocks = [...stat.stocks].sort((a, b) => b.leaderScore - a.leaderScore).slice(0, MAX_RENDERED_STOCKS)
  const topLeaders = stocks.slice(0, 3)
  return (
    <section className="flex max-h-[720px] flex-col overflow-hidden rounded-2xl border border-border bg-surface">
      <div className="shrink-0 border-b border-border px-5 py-4">
        <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
          <div className="min-w-0">
            <div className="flex items-center gap-3">
              <h3 className="truncate text-xl font-semibold text-foreground">{stat.key}</h3>
              <span className="rounded-full bg-blue-400/10 px-2 py-0.5 text-[10px] text-blue-300">强度 {stat.heatScore.toFixed(0)}</span>
            </div>
            <div className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted">
              <span>{stat.count} 只成分</span>
              <span className={priceColorClass(stat.avgPct)}>平均 {stat.avgPct != null ? fmtPct(stat.avgPct) : '—'}</span>
              <span>上涨占比 {(stat.upRate * 100).toFixed(0)}%</span>
              <span>成交额 {fmtBigNum(stat.totalAmount)}</span>
              {stat.avgTurnover != null && <span>均换手 {stat.avgTurnover.toFixed(2)}%</span>}
            </div>
          </div>
          <div className="grid grid-cols-5 gap-2 lg:w-[520px]">
            <MiniStat label="均涨" value={stat.avgPct != null ? fmtPct(stat.avgPct) : '—'} cls={priceColorClass(stat.avgPct)} />
            <MiniStat label="中位" value={stat.medianPct != null ? fmtPct(stat.medianPct) : '—'} cls={priceColorClass(stat.medianPct)} />
            <MiniStat label="强势" value={`${stat.strongCount}`} cls="text-bull" />
            <MiniStat label="弱势" value={`${stat.weakCount}`} cls="text-bear" />
            <MiniStat label="量比" value={stat.avgVolRatio != null ? stat.avgVolRatio.toFixed(2) : '—'} cls="text-foreground" />
          </div>
        </div>
      </div>

      <div className="grid shrink-0 gap-3 border-b border-border bg-base/25 p-4 lg:grid-cols-[1fr_1.15fr]">
        <LeaderStage stocks={topLeaders} onStockClick={onStockClick} />
        <ScoreExplain stock={topLeaders[0]} />
      </div>

      <div className="min-h-0 flex-1 overflow-auto">
        <table className="min-w-full text-left text-xs">
          <thead className="bg-elevated/60 text-[11px] text-muted">
            <tr>
              <th className="px-4 py-2 font-medium">排名</th>
              <th className="px-4 py-2 font-medium">股票</th>
              <th className="px-4 py-2 font-medium">涨跌幅</th>
              <th className="px-4 py-2 font-medium">换手率</th>
              <th className="px-4 py-2 font-medium">成交额</th>
              <th className="px-4 py-2 font-medium">流通市值</th>
              <th className="px-4 py-2 font-medium">量比</th>
              <th className="px-4 py-2 font-medium">龙头分</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-border/70">
            {stocks.map((s, idx) => (
              <tr key={`${s.symbol}-${idx}`} className="hover:bg-elevated/30 cursor-pointer" onClick={() => onStockClick(s.symbol, s.name || undefined)}>
                <td className="px-4 py-2 font-mono text-muted">{idx + 1}</td>
                <td className="px-4 py-2">
                  <div className="font-medium text-foreground">{s.name || '—'}</div>
                  <div className="font-mono text-[10px] text-muted">{s.symbol}</div>
                </td>
                <td className={cn('px-4 py-2 font-mono tabular-nums', priceColorClass(s.change_pct))}>{s.change_pct != null ? fmtPct(s.change_pct) : '—'}</td>
                <td className="px-4 py-2 font-mono text-foreground">{s.turnover_rate != null ? `${s.turnover_rate.toFixed(2)}%` : '—'}</td>
                <td className="px-4 py-2 font-mono text-foreground">{fmtBigNum(s.amount)}</td>
                <td className="px-4 py-2 font-mono text-foreground">{fmtBigNum(s.float_market_cap ?? s.market_cap)}</td>
                <td className="px-4 py-2 font-mono text-foreground">{s.vol_ratio_5d != null ? s.vol_ratio_5d.toFixed(2) : '—'}</td>
                <td className="px-4 py-2">
                  <div className="flex items-center gap-2">
                    <span className="w-9 font-mono text-amber-300">{s.leaderScore.toFixed(0)}</span>
                    <div className="h-1.5 w-16 rounded-full bg-elevated"><div className="h-full rounded-full bg-amber-300" style={{ width: `${Math.max(4, s.leaderScore)}%` }} /></div>
                  </div>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      {stat.stocks.length > MAX_RENDERED_STOCKS && <div className="shrink-0 border-t border-border px-4 py-2 text-center text-[11px] text-muted">仅展示龙头分前 {MAX_RENDERED_STOCKS} 只，共 {stat.stocks.length} 只</div>}
    </section>
  )
}

function MiniStat({ label, value, cls }: { label: string; value: string; cls: string }) {
  return <div className="rounded-lg border border-border/60 bg-base/35 px-2 py-1.5"><div className="text-[10px] text-muted">{label}</div><div className={cn('mt-0.5 truncate text-sm font-semibold', cls)}>{value}</div></div>
}

function LeaderStage({ stocks, onStockClick }: { stocks: EnrichedStock[]; onStockClick: (symbol: string, name?: string) => void }) {
  if (!stocks.length) return <div className="rounded-xl border border-border/60 bg-surface p-4 text-sm text-muted">暂无龙头候选</div>
  return (
    <div className="rounded-xl border border-border/60 bg-surface p-3">
      <div className="mb-2 flex items-center gap-2 text-xs font-medium text-amber-300">
        <Crown className="h-3.5 w-3.5" />
        本概念三龙头
      </div>
      <div className="grid gap-2 md:grid-cols-3">
        {stocks.map((stock, idx) => (
          <div key={stock.symbol} onClick={() => onStockClick(stock.symbol, stock.name || undefined)} className={cn('rounded-lg border p-3 cursor-pointer hover:brightness-110 transition-all', idx === 0 ? 'border-amber-400/25 bg-amber-400/[0.06]' : 'border-border/60 bg-base/35')}>
            <div className="flex items-center justify-between gap-2">
              <span className={cn('text-[10px] font-medium', idx === 0 ? 'text-amber-300' : 'text-muted')}>{idx === 0 ? '主龙头' : `辅龙 ${idx}`}</span>
              <span className="font-mono text-[11px] text-amber-300">{stock.leaderScore.toFixed(0)}</span>
            </div>
            <div className="mt-2 truncate text-sm font-medium text-foreground">{stock.name || stock.symbol}</div>
            <div className="mt-0.5 flex items-center justify-between text-[11px]">
              <span className="font-mono text-muted">{stock.symbol}</span>
              <span className={cn('font-mono', priceColorClass(stock.change_pct))}>{stock.change_pct != null ? fmtPct(stock.change_pct) : '—'}</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  )
}

function ScoreExplain({ stock }: { stock?: EnrichedStock }) {
  if (!stock) return <div className="rounded-xl border border-border/60 bg-surface p-4 text-sm text-muted">暂无评分拆解</div>
  const parts = stock.leaderParts
  return (
    <div className="rounded-xl border border-border/60 bg-surface p-3">
      <div className="mb-2 flex items-center justify-between">
        <span className="text-xs font-medium text-foreground">主龙头评分拆解</span>
        <span className="text-[11px] text-muted">涨幅 / 换手 / 成交 / 市值 / 量比 / 连板</span>
      </div>
      <div className="grid grid-cols-2 gap-2 md:grid-cols-3">
        <Part label="动能" value={parts.momentum} cls="bg-rose-400" />
        <Part label="换手" value={parts.turnover} cls="bg-orange-400" />
        <Part label="成交" value={parts.amount} cls="bg-blue-400" />
        <Part label="市值" value={parts.cap} cls="bg-cyan-400" />
        <Part label="量比" value={parts.volume} cls="bg-purple-400" />
        <Part label="连板" value={parts.boards} cls="bg-amber-300" />
      </div>
    </div>
  )
}

function Part({ label, value, cls }: { label: string; value: number; cls: string }) {
  return <div className="rounded-lg bg-base/35 px-2 py-1.5"><div className="mb-1 flex justify-between text-[10px] text-muted"><span>{label}</span><span>{Math.round(value * 100)}</span></div><div className="h-1 rounded-full bg-elevated"><div className={cn('h-full rounded-full', cls)} style={{ width: `${Math.max(3, value * 100)}%` }} /></div></div>
}
