// 多因子篩選 + 智慧選股雷達 — Live Data
const { useState, useMemo, useEffect, useRef, useCallback } = React;

// ── helpers ──────────────────────────────────────────────
function avg(arr) {
  return arr.length ? arr.reduce((s, v) => s + v, 0) / arr.length : 0;
}

function computeRSI(closes, period) {
  if (closes.length < period + 1) return 50;
  let gains = 0, losses = 0;
  for (let i = closes.length - period; i < closes.length; i++) {
    const d = closes[i] - closes[i - 1];
    if (d > 0) gains += d; else losses -= d;
  }
  const ag = gains / period, al = losses / period;
  if (al === 0) return 100;
  return parseFloat((100 - 100 / (1 + ag / al)).toFixed(1));
}

function computeEMA(data, period) {
  if (data.length < period) return data.map(() => null);
  const k = 2 / (period + 1);
  const result = [];
  let ema = data.slice(0, period).reduce((a, b) => a + b, 0) / period;
  for (let i = 0; i < period - 1; i++) result.push(null);
  result.push(ema);
  for (let i = period; i < data.length; i++) {
    ema = data[i] * k + ema * (1 - k);
    result.push(ema);
  }
  return result;
}

function computeMACD(closes) {
  const n = closes.length;
  if (n < 35) return { macd: null, macdSignal: null, macdHist: null, crossType: null, crossBarsAgo: null };

  const ema12 = computeEMA(closes, 12);
  const ema26 = computeEMA(closes, 26);

  // MACD line
  const macdLine = closes.map((_, i) =>
    ema12[i] != null && ema26[i] != null ? ema12[i] - ema26[i] : null
  );

  // Signal = EMA9 of valid MACD values
  const validMacd = macdLine.filter(v => v != null);
  if (validMacd.length < 9) return { macd: null, macdSignal: null, macdHist: null, crossType: null, crossBarsAgo: null };

  const ema9raw = computeEMA(validMacd, 9);
  const signalLine = Array(n).fill(null);
  let vi = 0;
  for (let i = 0; i < n; i++) {
    if (macdLine[i] != null) { signalLine[i] = ema9raw[vi]; vi++; }
  }

  // Check recent cross (last 20 bars)
  let crossType = null, crossBarsAgo = null;
  for (let i = n - 1; i >= Math.max(1, n - 20); i--) {
    const m0 = macdLine[i - 1], m1 = macdLine[i];
    const s0 = signalLine[i - 1], s1 = signalLine[i];
    if (m0 == null || m1 == null || s0 == null || s1 == null) continue;
    if (m0 <= s0 && m1 > s1) { crossType = 'golden'; crossBarsAgo = n - 1 - i; break; }
    if (m0 >= s0 && m1 < s1) { crossType = 'death';  crossBarsAgo = n - 1 - i; break; }
  }

  return {
    macd:       macdLine[n - 1],
    macdSignal: signalLine[n - 1],
    macdHist:   macdLine[n - 1] != null && signalLine[n - 1] != null ? macdLine[n - 1] - signalLine[n - 1] : null,
    crossType,
    crossBarsAgo,
  };
}

function computeIndicators(rawData) {
  const rows = (rawData || [])
    .filter(r => parseFloat(r.close || r.Close) > 0)
    .sort((a, b) => (a.date || a.Date || '').localeCompare(b.date || b.Date || ''));
  if (rows.length < 20) return null;
  const closes  = rows.map(r => parseFloat(r.close || r.Close));
  const volumes = rows.map(r => parseFloat(r.Trading_Volume || r.volume || r.Volume || 0));

  const macdRes = computeMACD(closes);
  const volMa20 = avg(volumes.slice(-20));
  const lastVol  = volumes[volumes.length - 1] || 0;

  return {
    ma5:  closes.length >= 5  ? +avg(closes.slice(-5)).toFixed(2)  : closes[closes.length - 1],
    ma20: closes.length >= 20 ? +avg(closes.slice(-20)).toFixed(2) : closes[closes.length - 1],
    ma60: closes.length >= 60 ? +avg(closes.slice(-60)).toFixed(2) : closes[closes.length - 1],
    rsi:  computeRSI(closes.slice(-15), 14),
    ...macdRes,
    volMa20,
    volRatio: volMa20 > 0 ? +(lastVol / volMa20).toFixed(2) : 1,
  };
}

// ── Extract unique stocks from data.js ───────────────────
function buildBaseUniverse() {
  const seen = new Set();
  const result = [];
  (window.INDUSTRIES || []).forEach(ind => {
    ['upstream', 'midstream', 'downstream'].forEach(seg => {
      (ind.nodes[seg] || []).forEach(node => {
        node.companies.forEach(c => {
          if (!seen.has(c.code)) {
            seen.add(c.code);
            result.push({
              code: c.code, name: c.name,
              ind: ind.name, indId: ind.id, mcap: c.mcap,
              price: 0, chg: 0,
              ma5: 0, ma20: 0, ma60: 0, rsi: 50,
              macd: null, macdSignal: null, macdHist: null,
              crossType: null, crossBarsAgo: null,
              volRatio: 1,
              loaded: false,
            });
          }
        });
      });
    });
  });
  return result;
}

// ── Scoring ───────────────────────────────────────────────
function investScore(s) {
  if (!s.loaded) return { score: 0, breakdown: [] };
  let score = 0;
  const bd = [];

  // 多頭排列 (0-25)
  if (s.price > s.ma5 && s.ma5 > s.ma20 && s.ma20 > s.ma60) {
    score += 25; bd.push({ label: '多頭排列', pts: 25, ok: true });
  } else if (s.price > s.ma20 && s.ma20 > s.ma60) {
    score += 12; bd.push({ label: '站上年線', pts: 12, ok: true });
  } else {
    bd.push({ label: '多頭排列', pts: 0, ok: false });
  }

  // MACD 多頭 (0-20)
  if (s.macd != null && s.macdSignal != null) {
    if (s.macd > 0 && s.macd > s.macdSignal) {
      score += 20; bd.push({ label: 'MACD 多頭', pts: 20, ok: true });
    } else if (s.macd > s.macdSignal) {
      score += 10; bd.push({ label: 'MACD 翻多', pts: 10, ok: true });
    } else {
      bd.push({ label: 'MACD 多頭', pts: 0, ok: false });
    }
  }

  // 黃金交叉 (0-25)
  if (s.crossType === 'golden') {
    if (s.crossBarsAgo <= 5)       { score += 25; bd.push({ label: `黃金交叉 ${s.crossBarsAgo}日前`, pts: 25, ok: true }); }
    else if (s.crossBarsAgo <= 15) { score += 15; bd.push({ label: `黃金交叉 ${s.crossBarsAgo}日前`, pts: 15, ok: true }); }
    else                            { bd.push({ label: '黃金交叉', pts: 0, ok: false }); }
  } else {
    bd.push({ label: '黃金交叉', pts: 0, ok: false });
  }

  // RSI 健康區 (0-15)
  if (s.rsi >= 45 && s.rsi <= 70) {
    score += 15; bd.push({ label: `RSI ${s.rsi}（健康）`, pts: 15, ok: true });
  } else if (s.rsi >= 30 && s.rsi < 45) {
    score += 8; bd.push({ label: `RSI ${s.rsi}（回升）`, pts: 8, ok: true });
  } else {
    bd.push({ label: `RSI ${s.rsi.toFixed(0)}`, pts: 0, ok: false });
  }

  // 量能 (0-15)
  if (s.volRatio >= 1.5)       { score += 15; bd.push({ label: `量比 ${s.volRatio}x 爆量`, pts: 15, ok: true }); }
  else if (s.volRatio >= 1.2)  { score += 8;  bd.push({ label: `量比 ${s.volRatio}x 量增`, pts: 8, ok: true }); }
  else                          { bd.push({ label: `量比 ${s.volRatio}x`, pts: 0, ok: false }); }

  return { score: Math.min(100, score), breakdown: bd };
}

function speculateScore(s) {
  if (!s.loaded) return { score: 0, breakdown: [] };
  let score = 0;
  const bd = [];

  // 黃金交叉新鮮度 (0-35)
  if (s.crossType === 'golden') {
    if (s.crossBarsAgo <= 3)       { score += 35; bd.push({ label: `黃金交叉 ${s.crossBarsAgo}日前🔥`, pts: 35, ok: true }); }
    else if (s.crossBarsAgo <= 7)  { score += 25; bd.push({ label: `黃金交叉 ${s.crossBarsAgo}日前`, pts: 25, ok: true }); }
    else if (s.crossBarsAgo <= 15) { score += 15; bd.push({ label: `黃金交叉 ${s.crossBarsAgo}日前`, pts: 15, ok: true }); }
    else                            { bd.push({ label: '黃金交叉（舊）', pts: 0, ok: false }); }
  } else {
    bd.push({ label: '黃金交叉', pts: 0, ok: false });
  }

  // 放量突破MA60 (0-30)
  if (s.price > s.ma60 && s.volRatio >= 1.5) {
    score += 30; bd.push({ label: `放量站年線 ${s.volRatio}x`, pts: 30, ok: true });
  } else if (s.price > s.ma60 && s.volRatio >= 1.2) {
    score += 15; bd.push({ label: `站上年線 ${s.volRatio}x`, pts: 15, ok: true });
  } else if (s.price > s.ma60) {
    score += 8;  bd.push({ label: '站上年線', pts: 8, ok: true });
  } else {
    bd.push({ label: '突破年線', pts: 0, ok: false });
  }

  // 短線強勢 (0-20)
  if (s.price > s.ma5 && s.chg >= 3) {
    score += 20; bd.push({ label: `強攻 +${s.chg}%`, pts: 20, ok: true });
  } else if (s.price > s.ma5 && s.chg >= 1) {
    score += 10; bd.push({ label: `偏強 +${s.chg}%`, pts: 10, ok: true });
  } else if (s.price > s.ma5) {
    score += 5;  bd.push({ label: '站上MA5', pts: 5, ok: true });
  } else {
    bd.push({ label: '短線強勢', pts: 0, ok: false });
  }

  // RSI 動能 (0-15)
  if (s.rsi >= 50 && s.rsi <= 65) {
    score += 15; bd.push({ label: `RSI ${s.rsi}（動能）`, pts: 15, ok: true });
  } else if (s.rsi > 65 && s.rsi <= 75) {
    score += 8;  bd.push({ label: `RSI ${s.rsi}（強勢）`, pts: 8, ok: true });
  } else {
    bd.push({ label: `RSI ${s.rsi.toFixed(0)}`, pts: 0, ok: false });
  }

  // 停損建議
  const stopLoss = s.ma20 < s.price && s.ma20 > s.price * 0.93
    ? +s.ma20.toFixed(1)   // MA20 在 -7% 之內，用 MA20 當停損
    : +(s.price * 0.93).toFixed(1);

  return { score: Math.min(100, score), breakdown: bd, stopLoss };
}

// ── Conditions (原有多因子篩選) ──────────────────────────
const CONDITIONS = [
  { id:'strong_bull',    cn:'強多排列',    en:'Strong Bull',
    desc:'股價 > MA5 > MA20 > MA60 · 完整多頭排列',
    test: r => r.loaded && r.price > r.ma5 && r.ma5 > r.ma20 && r.ma20 > r.ma60 },
  { id:'pullback',       cn:'多頭拉回',    en:'Bull Pullback',
    desc:'多頭排列中短線回測 MA5 附近，低接機會',
    test: r => r.loaded && r.ma5 > r.ma20 && r.ma20 > r.ma60 && r.price < r.ma5 * 1.02 && r.price > r.ma20 },
  { id:'above_ma60',     cn:'站上年線',    en:'Above MA60',
    desc:'股價站上 60MA，中長期趨勢向上',
    test: r => r.loaded && r.price > r.ma60 },
  { id:'rsi_bull',       cn:'RSI 多頭區',  en:'RSI Bullish',
    desc:'RSI 介於 50-70，動能健康未過熱',
    test: r => r.loaded && r.rsi >= 50 && r.rsi <= 70 },
  { id:'rsi_oversold',   cn:'RSI 超賣',    en:'RSI Oversold',
    desc:'RSI < 30，可能反彈',
    test: r => r.loaded && r.rsi < 30 },
  { id:'rsi_overbought', cn:'RSI 超買',    en:'RSI Overbought',
    desc:'RSI > 70，強勢但注意追高風險',
    test: r => r.loaded && r.rsi > 70 },
  { id:'strong_bear',    cn:'空頭排列',    en:'Strong Bear',
    desc:'股價 < MA5 < MA20 < MA60，完整空頭',
    test: r => r.loaded && r.price < r.ma5 && r.ma5 < r.ma20 && r.ma20 < r.ma60 },
  { id:'near_ma20',      cn:'貼近20MA',    en:'Near MA20',
    desc:'股價在 MA20 ±3% 內，觀察支撐或壓力',
    test: r => r.loaded && Math.abs(r.price - r.ma20) / r.ma20 <= 0.03 },
  { id:'golden_cross',   cn:'黃金交叉',    en:'Golden Cross',
    desc:'MACD 線近 15 日內上穿訊號線',
    test: r => r.loaded && r.crossType === 'golden' && r.crossBarsAgo != null && r.crossBarsAgo <= 15 },
  { id:'vol_surge',      cn:'爆量',        en:'Volume Surge',
    desc:'今日成交量 > 20日均量 1.5 倍以上',
    test: r => r.loaded && r.volRatio >= 1.5 },
];

// ── Strategy Presets ─────────────────────────────────────
const PRESETS = [
  {
    id: 'trend_rider',
    cn: '趨勢騎士',
    en: 'Trend Rider',
    tag: '投資',
    tagColor: 'var(--bull)',
    desc: '完整多頭排列＋MACD多頭＋RSI健康，適合中長線持有',
    conditions: ['strong_bull', 'rsi_bull'],
    matchMode: 'all',
    hint: '全部符合',
  },
  {
    id: 'golden_surge',
    cn: '黃金爆量',
    en: 'Golden Surge',
    tag: '投機',
    tagColor: '#c0392b',
    desc: '近期黃金交叉＋爆量放大，短線動能最強訊號',
    conditions: ['golden_cross', 'vol_surge'],
    matchMode: 'all',
    hint: '全部符合',
  },
  {
    id: 'low_rebound',
    cn: '低接反彈',
    en: 'Low Rebound',
    tag: '投機',
    tagColor: '#c0392b',
    desc: 'RSI超賣＋站上年線，超賣反彈佈局機會',
    conditions: ['rsi_oversold', 'above_ma60'],
    matchMode: 'any',
    hint: '任一符合',
  },
  {
    id: 'pullback_buy',
    cn: '多頭低接',
    en: 'Pullback Buy',
    tag: '投資',
    tagColor: 'var(--bull)',
    desc: '多頭排列中短線拉回，逢低加碼的最佳時機',
    conditions: ['pullback', 'rsi_bull'],
    matchMode: 'all',
    hint: '全部符合',
  },
  {
    id: 'breakout',
    cn: '突破年線',
    en: 'MA60 Breakout',
    tag: '投機',
    tagColor: '#c0392b',
    desc: '站上60MA年線＋MACD黃金交叉，趨勢反轉初期',
    conditions: ['above_ma60', 'golden_cross'],
    matchMode: 'all',
    hint: '全部符合',
  },
  {
    id: 'strong_momentum',
    cn: '強勢領漲',
    en: 'Strong Momentum',
    tag: '投機',
    tagColor: '#c0392b',
    desc: '強多排列＋RSI強勢＋爆量，市場最強勢的領漲股',
    conditions: ['strong_bull', 'rsi_overbought', 'vol_surge'],
    matchMode: 'all',
    hint: '全部符合',
  },
  {
    id: 'custom',
    cn: '自訂組合',
    en: 'Custom',
    tag: '自訂',
    tagColor: 'var(--ink-mute)',
    desc: '自行勾選條件，靈活組合',
    conditions: [],
    matchMode: 'all',
    hint: '',
  },
];

const BATCH_SIZE = 6;
const BATCH_DELAY = 120;

// ── Score Bar ─────────────────────────────────────────────
function ScoreBar({ score }) {
  const color = score >= 70 ? 'var(--bull)' : score >= 45 ? 'var(--gold)' : 'var(--bear)';
  return (
    <div style={{ display:'flex', alignItems:'center', gap:8 }}>
      <div style={{
        width: 36, height: 36, borderRadius: '50%', display:'flex',
        alignItems:'center', justifyContent:'center', flexShrink: 0,
        background: color, color:'#fff', fontWeight: 800, fontSize: 13,
      }}>{score}</div>
      <div style={{ flex:1, height: 4, background:'var(--paper-line)', borderRadius: 2 }}>
        <div style={{ width: score + '%', height:'100%', background: color, borderRadius: 2, transition:'width .4s' }} />
      </div>
    </div>
  );
}

// ── Score Breakdown Tooltip ───────────────────────────────
function BreakdownList({ items }) {
  return (
    <div style={{ display:'flex', flexWrap:'wrap', gap:4, marginTop:6 }}>
      {items.map((b, i) => (
        <span key={i} style={{
          fontSize: 'var(--fs-nano)', padding:'2px 6px', borderRadius: 2,
          background: b.ok ? 'rgba(42,107,76,.1)' : 'rgba(0,0,0,.04)',
          color: b.ok ? 'var(--bull)' : 'var(--ink-mute)',
          border: b.ok ? '1px solid rgba(42,107,76,.2)' : '1px solid var(--paper-line)',
        }}>
          {b.ok ? `✓ ${b.label} +${b.pts}` : `✗ ${b.label}`}
        </span>
      ))}
    </div>
  );
}

// ── Smart Radar Table ────────────────────────────────────
function SmartRadarTable({ universe, mode }) {
  const [expandCode, setExpandCode] = useState(null);

  const ranked = useMemo(() => {
    return universe
      .filter(s => s.loaded && s.price > 0)
      .map(s => {
        const { score, breakdown, stopLoss } = mode === 'invest'
          ? investScore(s)
          : speculateScore(s);
        return { ...s, score, breakdown, stopLoss };
      })
      .filter(s => s.score >= (mode === 'invest' ? 20 : 20))
      .sort((a, b) => b.score - a.score)
      .slice(0, 50);
  }, [universe, mode]);

  if (ranked.length === 0) {
    return (
      <div style={{ padding:40, textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic' }}>
        資料載入中，請稍候…
      </div>
    );
  }

  return (
    <div>
      <div style={{ marginBottom:12, fontSize:12, color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體, serif' }}>
        {mode === 'invest'
          ? '※ 評分項目：多頭排列(25) + MACD多頭(20) + 黃金交叉(25) + RSI健康(15) + 量能(15)，滿分100。'
          : '※ 評分項目：黃金交叉新鮮度(35) + 放量突破(30) + 短線強勢(20) + RSI動能(15)，滿分100。'}
        顯示前 50 名 · 點列展開明細
      </div>
      <div className="frame-card" style={{ overflowX:'auto' }}>
        <span className="corner-tl"/><span className="corner-tr"/>
        <span className="corner-bl"/><span className="corner-br"/>
        <table className="ed-table">
          <thead>
            <tr>
              <th style={{ width:36 }}>#</th>
              <th>Code</th>
              <th>Name</th>
              <th>Industry</th>
              <th className="right" style={{ width:120 }}>Score</th>
              <th className="right">Price</th>
              <th className="right">Chg%</th>
              <th className="right">MA排列</th>
              <th className="right">MACD</th>
              <th className="right">量比</th>
              {mode === 'speculate' && <th className="right">建議停損</th>}
            </tr>
          </thead>
          <tbody>
            {ranked.map((r, idx) => {
              const isOpen = expandCode === r.code;
              const maAlign = r.price > r.ma5 && r.ma5 > r.ma20 && r.ma20 > r.ma60 ? '強多'
                : r.price > r.ma20 && r.ma20 > r.ma60 ? '站年線'
                : r.price > r.ma60 ? '月上年'
                : '空排';
              const maColor = maAlign === '強多' ? 'var(--bull)' : maAlign === '站年線' ? 'var(--gold)' : 'var(--ink-mute)';
              const crossLabel = r.crossType === 'golden'
                ? `▲${r.crossBarsAgo}日`
                : r.crossType === 'death' ? '▼死叉' : '—';
              const crossColor = r.crossType === 'golden' ? 'var(--bull)' : r.crossType === 'death' ? 'var(--bear)' : 'var(--ink-mute)';
              return (
                <React.Fragment key={r.code}>
                  <tr
                    style={{ cursor:'pointer', background: isOpen ? 'rgba(184,146,77,.06)' : undefined }}
                    onClick={() => setExpandCode(isOpen ? null : r.code)}
                  >
                    <td style={{ color:'var(--ink-mute)', fontSize:12 }}>{idx + 1}</td>
                    <td className="code-cell">{r.code}</td>
                    <td className="kai">{r.name}</td>
                    <td className="kai" style={{ fontSize:12, color:'var(--ink-soft)' }}>{r.ind}</td>
                    <td className="right" style={{ minWidth:110 }}>
                      <ScoreBar score={r.score} />
                    </td>
                    <td className="right num-cell">
                      {r.price > 0 ? r.price.toLocaleString('en-US', { minimumFractionDigits:2, maximumFractionDigits:2 }) : '—'}
                    </td>
                    <td className={`right num-cell ${r.chg >= 0 ? 'bull-c' : 'bear-c'}`}>
                      {r.price > 0 ? (r.chg >= 0 ? '+' : '') + r.chg.toFixed(2) + '%' : '—'}
                    </td>
                    <td className="right" style={{ fontSize:12, color: maColor, fontWeight:600 }}>{maAlign}</td>
                    <td className="right" style={{ fontSize:12, color: crossColor, fontWeight:600 }}>{crossLabel}</td>
                    <td className="right num-cell" style={{
                      fontSize:12,
                      color: r.volRatio >= 1.5 ? 'var(--bull)' : r.volRatio >= 1.2 ? 'var(--gold)' : 'var(--ink-mute)',
                    }}>
                      {r.volRatio}x
                    </td>
                    {mode === 'speculate' && (
                      <td className="right num-cell" style={{ fontSize:12, color:'var(--bear)' }}>
                        {r.stopLoss ? r.stopLoss.toLocaleString() : '—'}
                      </td>
                    )}
                  </tr>
                  {isOpen && (
                    <tr style={{ background:'var(--paper)' }}>
                      <td colSpan={mode === 'speculate' ? 11 : 10} style={{ padding:'10px 16px 14px', borderTop:'1px solid var(--paper-line)' }}>
                        <div style={{ display:'flex', gap:32, flexWrap:'wrap', alignItems:'flex-start' }}>
                          <div>
                            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', marginBottom:4 }}>Score Breakdown</div>
                            <BreakdownList items={r.breakdown} />
                          </div>
                          <div style={{ fontSize:12, color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體, serif', lineHeight:1.8 }}>
                            <div>MA5 {r.ma5} · MA20 {r.ma20} · MA60 {r.ma60}</div>
                            <div>RSI {r.rsi} · 量比 {r.volRatio}x</div>
                            {mode === 'speculate' && r.stopLoss && (
                              <div style={{ color:'var(--bear)', fontWeight:600 }}>建議停損 ≤ {r.stopLoss}（-7% 或 MA20）</div>
                            )}
                          </div>
                          <button className="scan-button" style={{ marginLeft:'auto', alignSelf:'center' }}
                            onClick={e => { e.stopPropagation(); location.href = `個股技術分析.html?code=${r.code}`; }}>
                            查看個股 →
                          </button>
                        </div>
                      </td>
                    </tr>
                  )}
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ── App ──────────────────────────────────────────────────
function App() {
  const [universe, setUniverse]         = useState(() => buildBaseUniverse());
  const [loadedCount, setLoadedCount]   = useState(0);
  const [loadingState, setLoadingState] = useState('idle');
  const [loadError, setLoadError]       = useState('');
  const [tab, setTab]                   = useState('screener');   // 'screener' | 'radar'
  const [radarMode, setRadarMode]       = useState('invest');     // 'invest' | 'speculate'
  const [active, setActive]             = useState(new Set(['strong_bull']));
  const [matchMode, setMatchMode]       = useState('all');
  const [indFilter, setIndFilter]       = useState('all');
  const [preset, setPreset]             = useState('custom');
  const abortRef = useRef(false);

  const applyPreset = (p) => {
    setPreset(p.id);
    if (p.id !== 'custom') {
      setActive(new Set(p.conditions));
      setMatchMode(p.matchMode);
    }
  };

  const toggle = id => {
    setPreset('custom');
    const next = new Set(active);
    next.has(id) ? next.delete(id) : next.add(id);
    setActive(next);
  };

  const loadData = useCallback(async () => {
    abortRef.current = false;
    const base = buildBaseUniverse();
    setUniverse(base.map(s => ({ ...s, loaded: false })));
    setLoadedCount(0);
    setLoadingState('prices');
    setLoadError('');

    // Step 1: real prices
    let priceMap = {};
    try {
      const r = await fetch('/api/stock?type=list&market=all');
      if (!r.ok) throw new Error('HTTP ' + r.status);
      const list = await r.json();
      if (Array.isArray(list)) {
        list.forEach(s => {
          const close  = parseFloat(s.ClosingPrice) || 0;
          const change = parseFloat(s.Change) || 0;
          const prev   = close - change;
          const pct    = prev > 0 ? (change / prev) * 100 : 0;
          priceMap[s.Code] = { price: close, chg: parseFloat(pct.toFixed(2)) };
        });
      }
    } catch (e) {
      setLoadError('無法取得即時報價：' + e.message);
      setLoadingState('error');
      return;
    }

    setUniverse(prev => prev.map(s => ({
      ...s,
      price: priceMap[s.code]?.price || s.price,
      chg:   priceMap[s.code]?.chg   || s.chg,
    })));

    setLoadingState('history');

    // Step 2: price history → MA + RSI + MACD + volume
    const codes = base.map(s => s.code);
    let done = 0;

    for (let i = 0; i < codes.length; i += BATCH_SIZE) {
      if (abortRef.current) break;
      const batch = codes.slice(i, i + BATCH_SIZE);

      const results = await Promise.allSettled(
        batch.map(code =>
          fetch(`/api/stock?type=price&code=${code}`)
            .then(r => r.ok ? r.json() : null)
            .then(json => ({ code, data: json?.data || [] }))
            .catch(() => ({ code, data: [] }))
        )
      );

      const updates = {};
      results.forEach(res => {
        if (res.status !== 'fulfilled') return;
        const { code, data } = res.value;
        const ind = computeIndicators(data);
        if (ind) updates[code] = { ...ind, loaded: true };
      });

      done += batch.length;
      setLoadedCount(done);
      setUniverse(prev => prev.map(s =>
        updates[s.code] ? { ...s, ...updates[s.code] } : s
      ));

      if (i + BATCH_SIZE < codes.length) {
        await new Promise(r => setTimeout(r, BATCH_DELAY));
      }
    }

    setLoadingState('done');
  }, []);

  useEffect(() => {
    loadData();
    return () => { abortRef.current = true; };
  }, []);

  const totalCount = universe.length;
  const doneCount  = universe.filter(s => s.loaded).length;
  const isLoading  = loadingState === 'prices' || loadingState === 'history';
  const progressPct = totalCount > 0 ? Math.round(doneCount / totalCount * 100) : 0;

  const industryList = useMemo(() => {
    const seen = new Set();
    const list = [];
    (window.INDUSTRIES || []).forEach(ind => {
      if (!seen.has(ind.id)) { seen.add(ind.id); list.push({ id: ind.id, name: ind.name }); }
    });
    return list;
  }, []);

  const filtered = useMemo(() => {
    return universe.filter(r => {
      if (indFilter !== 'all' && r.indId !== indFilter) return false;
      if (active.size === 0) return r.loaded;
      const checks = CONDITIONS.filter(c => active.has(c.id)).map(c => c.test(r));
      return matchMode === 'all' ? checks.every(Boolean) : checks.some(Boolean);
    });
  }, [universe, active, matchMode, indFilter]);

  const downloadCSV = () => {
    const header = ['代號','名稱','產業','收盤','漲跌%','MA5','MA20','MA60','RSI','量比','市值'].join(',');
    const rows = filtered.map(r => [r.code, r.name, r.ind, r.price, r.chg.toFixed(2), r.ma5, r.ma20, r.ma60, r.rsi, r.volRatio, r.mcap].join(','));
    const blob = new Blob(['﻿' + [header, ...rows].join('\n')], { type:'text/csv;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `screener-${new Date().toISOString().slice(0,10)}.csv`;
    a.click();
  };

  // ── Radar mode summary counts ─────────────────────────
  const radarCounts = useMemo(() => {
    if (doneCount === 0) return { invest: 0, speculate: 0 };
    const investHits    = universe.filter(s => s.loaded && investScore(s).score >= 20).length;
    const speculateHits = universe.filter(s => s.loaded && speculateScore(s).score >= 20).length;
    return { invest: investHits, speculate: speculateHits };
  }, [universe, doneCount]);

  return (
    <div className="shell">
      <window.Masthead />
      <window.TickerTape />
      <window.NavBar active="screener" />

      {/* ── Page Header ─────────────────────────────── */}
      <section className="cover" style={{ paddingTop:32, marginBottom:18 }}>
        <div>
          <div className="eyebrow">Smart Stock Screener · 智慧選股</div>
          <h1>
            <span style={{ fontStyle:'italic' }}>The</span> Screener <span className="amp">&amp;</span> Radar
            <span className="kai-title">智 慧 選 股 雷 達</span>
          </h1>
          <p className="cover-lede">
            從 <b style={{color:'var(--ink)'}}>{totalCount}</b> 檔個股中自動評分篩選。
            <b style={{color:'var(--ink)'}}> 投資模式</b>：趨勢+MACD+量能複合評分；
            <b style={{color:'var(--ink)'}}> 投機模式</b>：黃金交叉新鮮度+放量突破+停損建議。
          </p>
        </div>
        <div className="cover-side">
          <h3>Status <span className="kai" style={{fontSize:13, color:'var(--ink-mute)', letterSpacing:'.18em'}}>· 載入狀態</span></h3>
          <div className="screener-result-big">
            <div className="srb-num num">{doneCount}</div>
            <div className="srb-of num">/ {totalCount}</div>
          </div>
          <div style={{ marginTop:6, fontSize:12, color:'var(--ink-mute)', fontStyle:'italic' }}>
            {loadingState === 'prices'  && '抓取即時報價中…'}
            {loadingState === 'history' && `計算 MA / MACD / RSI (${doneCount}/${totalCount})`}
            {loadingState === 'done'    && `全部載入完成 · ${doneCount} 檔`}
            {loadingState === 'error'   && <span style={{color:'var(--bear)'}}>{loadError}</span>}
          </div>
          {isLoading && (
            <div className="scan-progress" style={{ marginTop:8 }}>
              <div className="scan-progress-fill" style={{ width: progressPct + '%', transition:'width .3s' }} />
            </div>
          )}
          <div style={{ display:'flex', gap:8, marginTop:14 }}>
            <button className="scan-button" onClick={loadData} style={{ flex:1 }} disabled={isLoading}>
              {isLoading ? '載入中…' : '重新載入'}
            </button>
          </div>
        </div>
      </section>

      {/* ── Page Tab ────────────────────────────────── */}
      <div style={{
        display:'flex', gap:0, marginBottom:24,
        borderBottom:'2px solid var(--paper-line)',
      }}>
        {[
          { id:'screener', label:'多因子篩選', en:'Conditions' },
          { id:'radar',    label:'智慧選股雷達', en:'Smart Radar' },
        ].map(t => (
          <button key={t.id} onClick={() => setTab(t.id)} style={{
            padding:'10px 28px', border:'none', background:'none', cursor:'pointer',
            borderBottom: tab === t.id ? '2px solid var(--gold)' : '2px solid transparent',
            marginBottom: -2,
            color: tab === t.id ? 'var(--gold)' : 'var(--ink-mute)',
            fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:15,
            fontWeight: tab === t.id ? 700 : 400,
            transition:'all .2s',
          }}>
            {t.en} <span className="kai" style={{ fontSize:12, marginLeft:6 }}>{t.label}</span>
          </button>
        ))}
      </div>

      {/* ══ Tab 1: 多因子篩選 ══════════════════════════ */}
      {tab === 'screener' && (<>

        {/* Preset 組合包 */}
        <window.SectionHead kicker="Strategy Presets" title="策略組合" titleEn="Presets"
          sub="選擇預設組合快速篩選，或切換「自訂」自行勾選條件" />
        <div style={{ display:'flex', flexWrap:'wrap', gap:10, marginBottom:28 }}>
          {PRESETS.map(p => {
            const isOn = preset === p.id;
            return (
              <button key={p.id} onClick={() => applyPreset(p)} style={{
                padding:'12px 16px', border:'none', cursor:'pointer', borderRadius:3,
                background: isOn ? (p.id === 'custom' ? 'var(--paper-line)' : p.tagColor === 'var(--bull)' ? 'rgba(42,107,76,.1)' : p.tagColor === '#c0392b' ? 'rgba(156,43,43,.1)' : 'var(--paper-line)') : 'var(--paper)',
                outline: isOn ? `2px solid ${p.tagColor}` : '1px solid var(--paper-line)',
                textAlign:'left', minWidth:140, maxWidth:200, flex:'1 1 140px',
                transition:'all .18s',
              }}>
                <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:5 }}>
                  <span style={{
                    fontSize:'var(--fs-nano)', padding:'1px 6px', borderRadius:2, fontWeight:700,
                    background: p.tagColor === 'var(--bull)' ? 'rgba(42,107,76,.15)' : p.tagColor === '#c0392b' ? 'rgba(156,43,43,.15)' : 'rgba(0,0,0,.06)',
                    color: p.tagColor,
                    fontFamily:'BiauKai,標楷體, serif',
                  }}>{p.tag}</span>
                  {p.hint && <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>{p.hint}</span>}
                </div>
                <div style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontWeight: isOn ? 700 : 400, fontSize:15, color: isOn ? p.tagColor : 'var(--ink)', marginBottom:3 }}>
                  {p.en}
                </div>
                <div className="kai" style={{ fontSize:12, fontWeight:600, color: isOn ? p.tagColor : 'var(--ink)', marginBottom:5 }}>
                  {p.cn}
                </div>
                <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', lineHeight:1.5 }}>{p.desc}</div>
                {p.id !== 'custom' && (
                  <div style={{ marginTop:6, display:'flex', flexWrap:'wrap', gap:3 }}>
                    {p.conditions.map(cid => {
                      const c = CONDITIONS.find(c => c.id === cid);
                      return c ? (
                        <span key={cid} style={{ fontSize:'var(--fs-nano)', padding:'1px 5px', borderRadius:2, background:'var(--paper-line)', color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體, serif' }}>
                          {c.cn}
                        </span>
                      ) : null;
                    })}
                  </div>
                )}
              </button>
            );
          })}
        </div>

        <window.SectionHead
          kicker="Conditions"
          title="篩選條件"
          titleEn="Filters"
          sub={
            <span style={{ display:'inline-flex', gap:10, alignItems:'center' }}>
              <span>Match:</span>
              <button className={`match-toggle ${matchMode==='all' ? 'active' : ''}`} onClick={() => setMatchMode('all')}>
                <span className="kai">全部</span> · all
              </button>
              <button className={`match-toggle ${matchMode==='any' ? 'active' : ''}`} onClick={() => setMatchMode('any')}>
                <span className="kai">任一</span> · any
              </button>
            </span>
          }
        />

        <div className="cond-grid">
          {CONDITIONS.map(c => (
            <div key={c.id} className={`cond-card ${active.has(c.id) ? 'on' : ''}`} onClick={() => toggle(c.id)}>
              <div className="cond-chk">
                <span className="cond-chk-mark">{active.has(c.id) ? '✓' : ''}</span>
              </div>
              <div className="cond-body">
                <div className="cond-name">
                  <span className="kai">{c.cn}</span>
                  <span className="cond-en italic-en">{c.en}</span>
                </div>
                <div className="cond-desc kai">{c.desc}</div>
              </div>
            </div>
          ))}
        </div>

        <div className="scan-filters" style={{ marginTop:18 }}>
          <div className="filter-row">
            <div className="filter-label">
              <span style={{ fontStyle:'italic' }}>Industry</span>
              <span className="kai" style={{ marginLeft:8 }}>產業</span>
            </div>
            <div className="filter-pills">
              <button className={`filter-pill ${indFilter==='all' ? 'active' : ''}`} onClick={() => setIndFilter('all')}>
                <span className="kai">全部</span>
              </button>
              {industryList.map(ind => (
                <button key={ind.id} className={`filter-pill ${indFilter===ind.id ? 'active' : ''}`}
                        onClick={() => setIndFilter(ind.id)}>
                  <span className="kai">{ind.name}</span>
                </button>
              ))}
            </div>
          </div>
        </div>

        <window.SectionHead
          kicker="Matches"
          title="命中清單"
          titleEn="Hits"
          sub={
            <span style={{ display:'inline-flex', gap:16, alignItems:'center', flexWrap:'wrap' }}>
              {preset !== 'custom' && (() => {
                const p = PRESETS.find(p => p.id === preset);
                return p ? (
                  <span style={{ padding:'2px 10px', borderRadius:2, fontSize:12, fontWeight:700,
                    background: p.tagColor === 'var(--bull)' ? 'rgba(42,107,76,.12)' : 'rgba(156,43,43,.12)',
                    color: p.tagColor, fontFamily:'BiauKai,標楷體, serif' }}>
                    {p.cn}
                  </span>
                ) : null;
              })()}
              <span>{filtered.length} 檔命中 · {doneCount} 已載入</span>
              <button className="scan-button" onClick={downloadCSV} disabled={doneCount === 0} style={{ fontSize:12, padding:'4px 12px' }}>
                匯出 CSV
              </button>
            </span>
          }
        />

        <div className="frame-card">
          <span className="corner-tl"/><span className="corner-tr"/>
          <span className="corner-bl"/><span className="corner-br"/>
          <table className="ed-table">
            <thead>
              <tr>
                <th>Code</th>
                <th>Name</th>
                <th>Industry</th>
                <th className="right">Price</th>
                <th className="right">Chg%</th>
                <th className="right">MA5</th>
                <th className="right">MA20</th>
                <th className="right">MA60</th>
                <th className="right">RSI</th>
                <th className="right">量比</th>
                <th className="right">Mkt Cap</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map(r => (
                <tr key={r.code} onClick={() => location.href=`個股技術分析.html?code=${r.code}`} style={{cursor:'pointer'}}>
                  <td className="code-cell">{r.code}</td>
                  <td className="kai">{r.name}</td>
                  <td className="kai" style={{ fontSize:13, color:'var(--ink-soft)' }}>{r.ind}</td>
                  <td className="right num-cell">
                    {r.price > 0 ? r.price.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2}) : '—'}
                  </td>
                  <td className={`right num-cell ${r.chg >= 0 ? 'bull-c' : 'bear-c'}`}>
                    {r.price > 0 ? (r.chg >= 0 ? '+' : '') + r.chg.toFixed(2) + '%' : '—'}
                  </td>
                  {r.loaded ? (<>
                    <td className={`right num-cell ${r.price > r.ma5 ? 'bull-c' : 'bear-c'}`} style={{fontSize:12}}>
                      {r.ma5.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}
                    </td>
                    <td className={`right num-cell ${r.price > r.ma20 ? 'bull-c' : 'bear-c'}`} style={{fontSize:12}}>
                      {r.ma20.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}
                    </td>
                    <td className={`right num-cell ${r.price > r.ma60 ? 'bull-c' : 'bear-c'}`} style={{fontSize:12}}>
                      {r.ma60.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}
                    </td>
                    <td className="right num-cell">
                      <span style={{
                        padding:'2px 8px', borderRadius:2,
                        background: r.rsi > 70 ? 'rgba(156,43,43,.12)' : r.rsi < 30 ? 'rgba(42,107,76,.12)' : 'transparent',
                        color: r.rsi > 70 ? 'var(--bear)' : r.rsi < 30 ? 'var(--bull)' : 'inherit',
                      }}>
                        {r.rsi.toFixed(1)}
                      </span>
                    </td>
                    <td className="right num-cell" style={{
                      fontSize:12,
                      color: r.volRatio >= 1.5 ? 'var(--bull)' : r.volRatio >= 1.2 ? 'var(--gold)' : 'var(--ink-mute)',
                    }}>
                      {r.volRatio}x
                    </td>
                  </>) : (
                    <td colSpan={5} className="right" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>計算中…</td>
                  )}
                  <td className="right num-cell" style={{ fontSize:12 }}>{r.mcap}</td>
                </tr>
              ))}
            </tbody>
          </table>
          {filtered.length === 0 && doneCount > 0 && (
            <div style={{ padding:40, textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic' }}>
              No matches — try adjusting conditions.
            </div>
          )}
          {doneCount === 0 && isLoading && (
            <div style={{ padding:40, textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic' }}>
              資料載入中，請稍候…
            </div>
          )}
        </div>
      </>)}

      {/* ══ Tab 2: 智慧選股雷達 ════════════════════════ */}
      {tab === 'radar' && (<>
        {/* Mode switcher */}
        <div style={{ display:'flex', gap:12, marginBottom:24, alignItems:'center' }}>
          {[
            { id:'invest',    label:'投資模式', en:'Investment', desc:'趨勢穩健 · 適合長抱', color:'var(--bull)' },
            { id:'speculate', label:'投機模式', en:'Speculation', desc:'動能爆發 · 短線操作', color:'var(--bear)' },
          ].map(m => (
            <button key={m.id} onClick={() => setRadarMode(m.id)} style={{
              flex:1, maxWidth:300, padding:'16px 20px', border:'none', cursor:'pointer',
              background: radarMode === m.id ? (m.id === 'invest' ? 'rgba(42,107,76,.08)' : 'rgba(156,43,43,.08)') : 'var(--paper)',
              borderRadius:4,
              outline: radarMode === m.id ? `2px solid ${m.color}` : '1px solid var(--paper-line)',
              textAlign:'left', transition:'all .2s',
            }}>
              <div style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:18, color: radarMode === m.id ? m.color : 'var(--ink)', marginBottom:4 }}>
                {m.en}
              </div>
              <div className="kai" style={{ fontSize:14, fontWeight:600, color: radarMode === m.id ? m.color : 'var(--ink)', marginBottom:6 }}>
                {m.label}
                <span style={{ marginLeft:12, fontSize:12, color:'var(--gold)', fontWeight:400, fontFamily:'monospace' }}>
                  {m.id === 'invest' ? radarCounts.invest : radarCounts.speculate} 檔命中
                </span>
              </div>
              <div className="kai" style={{ fontSize:12, color:'var(--ink-mute)' }}>{m.desc}</div>
            </button>
          ))}

          <div style={{ marginLeft:'auto', fontSize:12, color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體, serif', lineHeight:1.8 }}>
            {loadingState === 'history' && <span style={{ color:'var(--gold)' }}>計算中 {progressPct}%…</span>}
            {loadingState === 'done' && <span>已載入 {doneCount} 檔 · 顯示前 50 名</span>}
          </div>
        </div>

        {/* 投資模式說明 */}
        {radarMode === 'invest' && (
          <div style={{ padding:'12px 16px', background:'rgba(42,107,76,.05)', border:'1px solid rgba(42,107,76,.15)', borderRadius:3, marginBottom:20, fontSize:12, fontFamily:'BiauKai,標楷體, serif', lineHeight:1.9, color:'var(--ink-soft)' }}>
            <b style={{color:'var(--bull)'}}>投資模式邏輯：</b>
            多頭排列（25分）— 多頭排列代表中長期趨勢健康；
            MACD 多頭區（20分）— 動能仍在上升軌道；
            黃金交叉（25分）— 近期訊號越新鮮分越高；
            RSI 45-70（15分）— 動能健康、未過熱；
            量比 &gt;1.2x（15分）— 主力資金流入佐證。
            <b> 建議：60分以上列入觀察，80分以上積極布局。</b>
          </div>
        )}
        {radarMode === 'speculate' && (
          <div style={{ padding:'12px 16px', background:'rgba(156,43,43,.05)', border:'1px solid rgba(156,43,43,.15)', borderRadius:3, marginBottom:20, fontSize:12, fontFamily:'BiauKai,標楷體, serif', lineHeight:1.9, color:'var(--ink-soft)' }}>
            <b style={{color:'var(--bear)'}}>投機模式邏輯：</b>
            黃金交叉新鮮度（35分）— 越新越強，超過 15 日不給分；
            放量突破年線（30分）— 量價齊揚為最強訊號；
            短線強勢（20分）— 當日漲幅佐證動能；
            RSI 50-65（15分）— 還有上漲空間但未過熱。
            <b> 停損建議：MA20 或 -7%（兩者取高），觸及即出場。</b>
          </div>
        )}

        <SmartRadarTable universe={universe} mode={radarMode} />
      </>)}

      <window.FooterBar />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
