(function(){
// 我的觀察頁 — 庫存 / 自選清單 雙 sub-tab
// localStorage keys:
//   holding_notes_{code}      → { buy_reason, sell_plan }（從 localStorage 讀）
//   watchlist_tabs_v1         → [{ id, name, codes: [] }]
const T = window.STOCK_TOKENS;

// ── localStorage helpers ──────────────────────────────────────────────────────
function getWatchlistTabs() {
  try {
    const raw = localStorage.getItem('watchlist_tabs_v1');
    if (raw) return JSON.parse(raw);
  } catch(e) {}
  return [{ id: 'default', name: '我的自選', codes: [] }];
}

function saveWatchlistTabs(tabs) {
  localStorage.setItem('watchlist_tabs_v1', JSON.stringify(tabs));
}

function getHoldingNotes(code) {
  try {
    const raw = localStorage.getItem('holding_notes_' + code);
    if (raw) return JSON.parse(raw);
  } catch(e) {}
  return { buy_reason: '', sell_plan: '' };
}

function saveHoldingNotes(code, notes) {
  localStorage.setItem('holding_notes_' + code, JSON.stringify(notes));
}

// ── alert_change badge 元件 ───────────────────────────────────────────────────
function AlertChangeBadge({ alertChange }) {
  if (!alertChange) return null;

  const cfg = {
    aligned:            { label: 'OK',    bg: T.downSoft,  color: T.down,    border: 'oklch(0.80 0.10 155)' },
    hold:               { label: 'OK',    bg: T.downSoft,  color: T.down,    border: 'oklch(0.80 0.10 155)' },
    no_initial_debate:  { label: 'OK',    bg: T.downSoft,  color: T.down,    border: 'oklch(0.80 0.10 155)' },
    consider_reduce:    { label: '考慮減碼', bg: T.warnSoft, color: 'oklch(0.52 0.16 55)', border: 'oklch(0.80 0.10 75)' },
    consider_exit:      { label: '考慮踢出', bg: T.upSoft,  color: T.up,      border: 'oklch(0.80 0.10 25)' },
  };

  const c = cfg[alertChange] || cfg.aligned;
  return (
    <span style={{
      fontSize: 9, fontWeight: 700, padding: '2px 7px', borderRadius: 3,
      background: c.bg, color: c.color, border: '1px solid ' + c.border,
      letterSpacing: 0.3, whiteSpace: 'nowrap',
    }}>
      {c.label}
    </span>
  );
}

// ── 9 面向 grid 資料準備 ───────────────────────────────────────────────────────
function buildMetricTiles(raw) {
  if (!raw) return [];
  const f = (v, d = 1) => (v == null ? '—' : Number(v).toFixed(d));
  const pct = (v) => (v == null ? '—' : (v >= 0 ? '+' : '') + Number(v).toFixed(1) + '%');
  const m = raw;

  const tiles = [];

  // 1. 基本面
  if (m.fundamentals) {
    const fu = m.fundamentals;
    tiles.push({
      key: 'fund', label: '基本面',
      items: [
        { k: 'EPS(TTM)', v: fu.eps_ttm != null ? f(fu.eps_ttm) : '—' },
        { k: 'PE', v: fu.pe_forward != null ? f(fu.pe_forward) : '—' },
        { k: '殖利率', v: fu.dividend_yield_pct != null ? f(fu.dividend_yield_pct) + '%' : '—' },
        { k: '淨值', v: fu.book_value != null ? f(fu.book_value) : '—' },
      ],
    });
  }

  // 2. 技術面
  if (m.technical) {
    const te = m.technical;
    const rsiVal = te.rsi != null ? Number(te.rsi).toFixed(1) : '—';
    const rsiWarn = te.rsi != null && (te.rsi > 80 || te.rsi < 20);
    tiles.push({
      key: 'tech', label: '技術面',
      items: [
        { k: 'MA20', v: te.ma20 != null ? f(te.ma20) : '—' },
        { k: 'KD', v: (te.kd_k != null && te.kd_d != null) ? f(te.kd_k) + '/' + f(te.kd_d) : '—' },
        { k: 'RSI', v: rsiVal, warn: rsiWarn, warnText: te.rsi > 80 ? '超買' : '超賣' },
        { k: '量比', v: te.vol_ratio != null ? f(te.vol_ratio) + 'x' : '—' },
      ],
    });
  }

  // 3. 籌碼面
  if (m.chips) {
    const ch = m.chips;
    tiles.push({
      key: 'chips', label: '籌碼面',
      items: [
        { k: '外資成本', v: ch.foreign_avg_cost != null ? f(ch.foreign_avg_cost) : '—' },
        { k: '連買', v: ch.foreign_buy_days != null ? ch.foreign_buy_days + 'd' : '—' },
        { k: '連賣', v: ch.foreign_sell_days != null ? ch.foreign_sell_days + 'd' : '—' },
        { k: '法人差%', v: ch.vs_foreign_pct != null ? pct(ch.vs_foreign_pct) : '—' },
      ],
    });
  }

  // 4. 月營收
  if (m.monthly_revenue) {
    const rv = m.monthly_revenue;
    tiles.push({
      key: 'revenue', label: '月營收',
      items: [
        { k: 'YoY', v: rv.yoy_pct != null ? pct(rv.yoy_pct) : '—' },
        { k: 'MoM', v: rv.mom_pct != null ? pct(rv.mom_pct) : '—' },
      ],
      note: rv._note === 'pending_finmind_integration' ? '待接 FinMind' : null,
    });
  }

  // 5. 融資融券
  if (m.margin_balance) {
    const mb = m.margin_balance;
    tiles.push({
      key: 'margin', label: '融資融券',
      items: [
        { k: '融資餘額', v: mb.buy != null ? mb.buy.toLocaleString() : '—' },
        { k: '融券餘額', v: mb.sell != null ? mb.sell.toLocaleString() : '—' },
        { k: '融資比率', v: mb.ratio != null ? f(mb.ratio) + '%' : '—' },
      ],
    });
  }

  // 6. 借券
  if (m.stock_borrow) {
    const sb = m.stock_borrow;
    tiles.push({
      key: 'borrow', label: '借券',
      items: [
        { k: '餘額', v: sb.balance != null ? sb.balance.toLocaleString() : '—' },
        { k: '變化', v: sb.change != null ? (sb.change >= 0 ? '+' : '') + sb.change.toLocaleString() : '—' },
      ],
    });
  }

  // 7. 行事曆
  if (m.calendar) {
    const ca = m.calendar;
    tiles.push({
      key: 'calendar', label: '行事曆',
      items: [
        { k: '法說/財報', v: ca.next_earnings || '—' },
        { k: '除息日', v: ca.next_dividend || '—' },
      ],
    });
  }

  // 8. 主力動向
  if (m.main_force) {
    const mf = m.main_force;
    const consec = mf.foreign_consec_days;
    const consecLabel = consec > 0 ? '連買 ' + consec + 'd' : consec < 0 ? '連賣 ' + Math.abs(consec) + 'd' : '震盪';
    tiles.push({
      key: 'mainforce', label: '主力動向',
      items: [
        { k: '14d 買', v: mf.foreign_buy_days_14d != null ? mf.foreign_buy_days_14d + 'd' : '—' },
        { k: '14d 賣', v: mf.foreign_sell_days_14d != null ? mf.foreign_sell_days_14d + 'd' : '—' },
        { k: '連續', v: consecLabel },
      ],
    });
  }

  // 9. 產業強弱
  if (m.industry_strength) {
    const is = m.industry_strength;
    tiles.push({
      key: 'industry', label: '產業強弱',
      items: [
        { k: '個股 30d', v: is.stock_30d_pct != null ? pct(is.stock_30d_pct) : '—' },
        { k: '大盤 30d', v: is.twii_30d_pct != null ? pct(is.twii_30d_pct) : '—' },
        { k: '相對強弱', v: is.rel_strength != null ? (is.rel_strength >= 0 ? '+' : '') + f(is.rel_strength) + '%' : '—' },
      ],
    });
  }

  return tiles;
}

// ── 9 面向 MetricGrid 元件 ─────────────────────────────────────────────────────
function MetricGrid({ rawMetrics, vp }) {
  const tiles = buildMetricTiles(rawMetrics);
  if (tiles.length === 0) return null;

  const cols = vp.isMobile ? 2 : vp.isTablet ? 3 : 4;

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'repeat(' + cols + ', 1fr)',
      gap: 8,
    }}>
      {tiles.map(tile => (
        <div key={tile.key} style={{
          background: T.surface2,
          border: '1px solid ' + T.border,
          borderRadius: T.radiusSm,
          padding: '8px 10px',
        }}>
          <div style={{
            fontSize: 9, fontWeight: 700, letterSpacing: 0.5,
            color: T.text3, marginBottom: 6, textTransform: 'uppercase',
          }}>
            {tile.label}
            {tile.note && (
              <span style={{ marginLeft: 4, fontWeight: 500, letterSpacing: 0, textTransform: 'none', color: T.warn }}>
                ({tile.note})
              </span>
            )}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            {tile.items.map(item => (
              <div key={item.k} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 4 }}>
                <span style={{ fontSize: 10, color: T.text3 }}>{item.k}</span>
                <span style={{
                  fontSize: 11, fontFamily: T.fontMono, fontWeight: 600,
                  color: item.warn ? T.up : T.text,
                }}>
                  {item.v}
                  {item.warn && item.warnText && (
                    <span style={{ marginLeft: 3, fontSize: 9, fontFamily: T.fontSans, fontWeight: 700, color: T.up }}>
                      {item.warnText}
                    </span>
                  )}
                </span>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

// ── LLM 詳細卡片（展開用）────────────────────────────────────────────────────
function LLMDetailCard({ llmCard }) {
  const [show, setShow] = React.useState(false);
  if (!llmCard) return null;

  const sections = [
    { k: 'fundamentals', l: '基本面' },
    { k: 'technical',    l: '技術面' },
    { k: 'chips',        l: '籌碼面' },
    { k: 'news_signal',  l: '新聞' },
  ].filter(s => llmCard[s.k]);

  return (
    <div>
      {llmCard.summary && (
        <window.AINote compact>
          {llmCard.summary}
        </window.AINote>
      )}

      {sections.length > 0 && (
        <>
          <button onClick={() => setShow(s => !s)} style={{
            marginTop: 8, fontSize: 11, color: T.primary, background: 'none',
            border: 'none', cursor: 'pointer', padding: 0, fontFamily: T.fontSans,
          }}>
            {show ? '▲ 收起詳細分析' : '▼ 展開 LLM 詳細分析'}
          </button>

          {show && (
            <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
              {sections.map(s => (
                <div key={s.k} style={{ fontSize: 12, color: T.text2, lineHeight: 1.6 }}>
                  <span style={{ fontWeight: 600, color: T.text3 }}>{s.l}：</span>
                  {llmCard[s.k]}
                </div>
              ))}
            </div>
          )}
        </>
      )}

      {llmCard.warnings && llmCard.warnings.length > 0 && (
        <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
          {llmCard.warnings.map((w, i) => (
            <window.AINote key={i} compact>
              <span style={{ color: T.up, fontWeight: 600 }}>⚠ </span>
              <span style={{ color: T.text2 }}>{w}</span>
            </window.AINote>
          ))}
        </div>
      )}
    </div>
  );
}

// ── 今日深度分析區塊（取代舊 DailyAnalysisBlock）────────────────────────────
function DailyAnalysisBlock({ code, daily, showBroker = false, vp }) {
  const vpFallback = vp || { isMobile: false, isTablet: false, isDesktop: true };

  if (!daily) {
    return (
      <div style={{ padding: '12px 16px', fontSize: 11, color: T.text4 }}>
        今日深度分析：等待生成（_data/daily_analysis/{code}.json）
      </div>
    );
  }

  const llm = daily.llm_card || null;
  const raw = daily.raw_metrics || null;
  const asOf = daily.as_of ? daily.as_of.slice(0, 10) : null;

  return (
    <div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
      {/* 標題列 */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{
          fontSize: 10, fontWeight: 700, color: T.primary,
          background: T.primarySoft, padding: '2px 6px', borderRadius: 3,
        }}>今日深度分析</span>
        {asOf && (
          <span style={{ fontSize: 10, color: T.text3, fontFamily: T.fontMono }}>{asOf}</span>
        )}
        {llm && llm.alert_change && (
          <AlertChangeBadge alertChange={llm.alert_change} />
        )}
      </div>

      {/* alert_change 說明文字（非 aligned/ok 才顯示）*/}
      {llm && llm.alert_change && !['aligned', 'hold', 'no_initial_debate'].includes(llm.alert_change) && (
        <div style={{
          padding: '8px 12px',
          background: llm.alert_change === 'consider_exit' ? T.upSoft : T.warnSoft,
          border: '1px solid ' + (llm.alert_change === 'consider_exit' ? 'oklch(0.82 0.08 25)' : 'oklch(0.82 0.08 75)'),
          borderRadius: T.radiusSm, fontSize: 12,
          color: llm.alert_change === 'consider_exit' ? T.up : 'oklch(0.45 0.14 55)',
        }}>
          ⚠ 變化警示：{llm.alert_change === 'consider_exit' ? '今日分析建議考慮出場' : '今日分析建議考慮減碼'}，請重新評估部位
        </div>
      )}

      {/* LLM 摘要 + 詳細 */}
      {llm && <LLMDetailCard llmCard={llm} />}

      {/* 9 面向 grid */}
      {raw && (
        <MetricGrid rawMetrics={raw} vp={vpFallback} />
      )}

      {/* 分點買賣（庫存頁才顯示） */}
      {showBroker && (
        <div style={{ marginTop: 4 }}>
          <div style={{
            fontSize: 9, fontWeight: 700, letterSpacing: 0.5,
            color: T.text3, marginBottom: 6, textTransform: 'uppercase',
          }}>
            今日分點買賣
          </div>
          <BrokerTopList code={code} />
        </div>
      )}
    </div>
  );
}

// ── 分點買賣歷史 row ──────────────────────────────────────────────────────────
function BrokerRow({ broker, stockCode }) {
  const [showHistory, setShowHistory] = React.useState(false);
  const [history, setHistory] = React.useState(null);
  const [histLoading, setHistLoading] = React.useState(false);
  const [histError, setHistError] = React.useState(false);

  const loadHistory = (days) => {
    setHistLoading(true);
    setHistError(false);
    setHistory(null);
    setShowHistory(true);
    fetch('_data/broker_history/' + stockCode + '_' + broker.broker_id + '_' + days + 'd.json', { cache: 'no-store' })
      .then(r => {
        if (!r.ok) throw new Error('not found');
        return r.json();
      })
      .then(d => { setHistory(d); setHistLoading(false); })
      .catch(() => { setHistError(true); setHistLoading(false); });
  };

  const net = broker.net_shares;
  const netFmt = (net >= 0 ? '+' : '') + Math.round(net / 1000) + 'K';
  const netColor = net >= 0 ? T.up : T.down;

  return (
    <div style={{ marginBottom: 4 }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '4px 0',
      }}>
        <span style={{ fontSize: 11, color: T.text2, flex: 1 }}>{broker.broker_name}</span>
        <span style={{
          fontSize: 11, fontFamily: T.fontMono, fontWeight: 600, color: netColor,
        }}>{netFmt}</span>
        <div style={{ display: 'flex', gap: 3 }}>
          {[30, 90, 120].map(d => (
            <button key={d} onClick={() => loadHistory(d)} style={{
              fontSize: 9, padding: '2px 5px', cursor: 'pointer',
              background: T.hover, color: T.text3, border: '1px solid ' + T.border,
              borderRadius: 3, fontFamily: T.fontSans,
            }}>{d}天</button>
          ))}
        </div>
      </div>

      {showHistory && (
        <div style={{
          marginLeft: 8, padding: '8px 10px',
          background: T.surface2, border: '1px solid ' + T.border,
          borderRadius: T.radiusSm, marginTop: 2, marginBottom: 4,
        }}>
          {histLoading && (
            <div style={{ fontSize: 11, color: T.text3 }}>載入歷史資料中...</div>
          )}
          {histError && !histLoading && (
            <div style={{ fontSize: 11, color: T.text3 }}>歷史資料尚未生成（等待 cron 更新）</div>
          )}
          {history && !histLoading && (
            <div>
              {history.summary && (
                <div style={{ display: 'flex', gap: 12, fontSize: 10, color: T.text3, marginBottom: 6 }}>
                  <span>淨買賣：
                    <span style={{ fontFamily: T.fontMono, fontWeight: 600, color: T.text }}>
                      {history.summary.total_net_shares != null
                        ? (history.summary.total_net_shares >= 0 ? '+' : '') + Math.round(history.summary.total_net_shares / 1000) + 'K'
                        : '—'}
                    </span>
                  </span>
                  <span>活躍天：
                    <span style={{ fontFamily: T.fontMono, fontWeight: 600, color: T.text }}>
                      {history.summary.active_days != null ? history.summary.active_days : '—'}
                      {history.period_days ? '/' + history.period_days + 'd' : ''}
                    </span>
                  </span>
                </div>
              )}
              {history.daily && history.daily.length > 0 && (
                <div style={{ overflowX: 'auto' }}>
                  <table style={{ fontSize: 10, borderCollapse: 'collapse', width: '100%', fontFamily: T.fontMono }}>
                    <thead>
                      <tr style={{ color: T.text3 }}>
                        <th style={{ textAlign: 'left', padding: '2px 6px 4px 0', fontWeight: 600 }}>日期</th>
                        <th style={{ textAlign: 'right', padding: '2px 6px 4px', fontWeight: 600 }}>買</th>
                        <th style={{ textAlign: 'right', padding: '2px 6px 4px', fontWeight: 600 }}>賣</th>
                        <th style={{ textAlign: 'right', padding: '2px 0 4px 6px', fontWeight: 600 }}>淨</th>
                      </tr>
                    </thead>
                    <tbody>
                      {history.daily.slice(0, 30).map((row, i) => {
                        const rowNet = (row.net_shares || 0);
                        return (
                          <tr key={i} style={{ borderTop: '1px solid ' + T.border }}>
                            <td style={{ padding: '2px 6px 2px 0', color: T.text3 }}>{row.date}</td>
                            <td style={{ padding: '2px 6px', textAlign: 'right', color: T.up }}>
                              {row.buy_shares ? '+' + Math.round(row.buy_shares / 1000) + 'K' : '—'}
                            </td>
                            <td style={{ padding: '2px 6px', textAlign: 'right', color: T.down }}>
                              {row.sell_shares ? '-' + Math.round(row.sell_shares / 1000) + 'K' : '—'}
                            </td>
                            <td style={{ padding: '2px 0 2px 6px', textAlign: 'right', color: rowNet >= 0 ? T.up : T.down, fontWeight: 600 }}>
                              {(rowNet >= 0 ? '+' : '') + Math.round(rowNet / 1000) + 'K'}
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              )}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── 分點買賣 Top5 列表 ────────────────────────────────────────────────────────
function BrokerTopList({ code }) {
  const [broker, setBroker] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    fetch('_data/broker/' + code + '.json', { cache: 'no-store' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { setBroker(d); setLoading(false); })
      .catch(() => { setBroker(null); setLoading(false); });
  }, [code]);

  if (loading) {
    return <div style={{ fontSize: 11, color: T.text3 }}>載入分點資料中...</div>;
  }

  if (!broker || broker._status !== 'live') {
    return (
      <window.EmptyState
        title="分點資料尚未生成"
        hint="明日 cron 自動更新（需 FINMIND_API_KEY）"
      />
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      <div style={{ fontSize: 10, color: T.text3, marginBottom: 2 }}>
        資料日期：<span style={{ fontFamily: T.fontMono, color: T.text2 }}>{broker.as_of}</span>
      </div>

      {/* 買超 */}
      {broker.top_buys && broker.top_buys.length > 0 && (
        <div>
          <div style={{
            fontSize: 9, fontWeight: 700, letterSpacing: 0.5,
            color: T.up, marginBottom: 4,
          }}>
            TOP 5 買超分點
          </div>
          {broker.top_buys.slice(0, 5).map(b => (
            <BrokerRow key={b.broker_id} broker={b} stockCode={code} />
          ))}
        </div>
      )}

      {/* 賣超 */}
      {broker.top_sells && broker.top_sells.length > 0 && (
        <div>
          <div style={{
            fontSize: 9, fontWeight: 700, letterSpacing: 0.5,
            color: T.down, marginBottom: 4,
          }}>
            TOP 5 賣超分點
          </div>
          {broker.top_sells.slice(0, 5).map(b => (
            <BrokerRow key={b.broker_id} broker={b} stockCode={code} />
          ))}
        </div>
      )}
    </div>
  );
}

// ── 本月策略卡片（新 schema）─────────────────────────────────────────────────
function MonthlyStrategyCard({ strategy }) {
  const [expanded, setExpanded] = React.useState(false);

  const hasInsufficient = strategy.patterns && strategy.patterns._data_quality === 'insufficient';

  return (
    <window.Card padding={0}>
      {/* 標題列 */}
      <div style={{
        padding: '12px 16px',
        display: 'flex', alignItems: 'center', gap: 8,
        borderBottom: '1px solid ' + T.border,
        cursor: 'pointer',
      }} onClick={() => setExpanded(e => !e)}>
        <window.AIBadge />
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 13, fontWeight: 600, color: T.text }}>
            📅 {strategy.month || ''} 月度操作建議
          </div>
          {strategy.as_of && (
            <div style={{ fontSize: 10, color: T.text3, fontFamily: T.fontMono, marginTop: 2 }}>
              更新：{strategy.as_of.slice(0, 10)}
            </div>
          )}
        </div>
        <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke={T.text3} strokeWidth="1.5"
          style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s', flexShrink: 0 }}>
          <path d="M3 6l5 5 5-5"/>
        </svg>
      </div>

      <div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 12 }}>
        {/* 樣本不足提示 */}
        {hasInsufficient && (
          <div style={{
            padding: '8px 12px',
            background: T.warnSoft,
            border: '1px solid oklch(0.82 0.08 75)',
            borderRadius: T.radiusSm,
            fontSize: 12, color: 'oklch(0.45 0.14 55)',
          }}>
            ⚠ 樣本不足，待累積 3-6 個月後參考。以下為初始建立期策略，供方向參考。
          </div>
        )}

        {/* patterns_summary */}
        {strategy.patterns_summary && (
          <window.AINote compact title="交易模式摘要">
            {strategy.patterns_summary}
          </window.AINote>
        )}

        {/* 績效數字（若有） */}
        {strategy.patterns && !hasInsufficient && (
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(4, 1fr)',
            gap: 8,
          }}>
            {[
              { k: '勝率', v: strategy.patterns.win_rate != null ? (strategy.patterns.win_rate * 100).toFixed(1) + '%' : '—' },
              { k: '均獲利', v: strategy.patterns.avg_win_pct != null ? '+' + strategy.patterns.avg_win_pct.toFixed(1) + '%' : '—' },
              { k: '均虧損', v: strategy.patterns.avg_loss_pct != null ? strategy.patterns.avg_loss_pct.toFixed(1) + '%' : '—' },
              { k: '盈虧比', v: strategy.patterns.profit_factor != null ? strategy.patterns.profit_factor.toFixed(2) : '—' },
            ].map(item => (
              <div key={item.k} style={{
                background: T.surface2, border: '1px solid ' + T.border,
                borderRadius: T.radiusSm, padding: '6px 10px', textAlign: 'center',
              }}>
                <div style={{ fontSize: 9, color: T.text3, letterSpacing: 0.3, fontWeight: 600 }}>{item.k}</div>
                <div style={{ fontSize: 14, fontFamily: T.fontMono, fontWeight: 700, color: T.text, marginTop: 2 }}>{item.v}</div>
              </div>
            ))}
          </div>
        )}

        {/* 展開後：行為盤點 + 推薦策略 + 警示 + 行動清單 */}
        {expanded && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {/* 行為盤點 */}
            {strategy.behavior_analysis && (
              <div>
                <div style={{
                  fontSize: 10, fontWeight: 700, color: T.text3, letterSpacing: 0.5,
                  marginBottom: 6, textTransform: 'uppercase',
                }}>行為盤點</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                  {(strategy.behavior_analysis.strengths || []).map((s, i) => (
                    <div key={'s' + i} style={{ fontSize: 12, color: T.text2 }}>
                      <span style={{ color: T.down, marginRight: 6 }}>✅</span>{s}
                    </div>
                  ))}
                  {(strategy.behavior_analysis.weaknesses || []).map((s, i) => (
                    <div key={'w' + i} style={{ fontSize: 12, color: T.text2 }}>
                      <span style={{ color: T.warn, marginRight: 6 }}>⚠</span>{s}
                    </div>
                  ))}
                  {(strategy.behavior_analysis.blind_spots || []).map((s, i) => (
                    <div key={'b' + i} style={{ fontSize: 12, color: T.text2 }}>
                      <span style={{ color: T.primary, marginRight: 6 }}>💡</span>{s}
                    </div>
                  ))}
                </div>
              </div>
            )}

            {/* 推薦策略 */}
            {strategy.recommended_strategies && strategy.recommended_strategies.length > 0 && (
              <div>
                <div style={{
                  fontSize: 10, fontWeight: 700, color: T.text3, letterSpacing: 0.5,
                  marginBottom: 6, textTransform: 'uppercase',
                }}>推薦策略</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                  {strategy.recommended_strategies.map((rs, i) => (
                    <div key={i} style={{
                      padding: '8px 12px',
                      background: T.surface2, border: '1px solid ' + T.border,
                      borderRadius: T.radiusSm,
                    }}>
                      <div style={{ fontSize: 12, fontWeight: 600, color: T.text, marginBottom: 4 }}>
                        {i + 1}. {rs.name}
                      </div>
                      {rs.rationale && (
                        <div style={{ fontSize: 11, color: T.text2, marginBottom: 4 }}>{rs.rationale}</div>
                      )}
                      {rs.rules && rs.rules.length > 0 && (
                        <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                          {rs.rules.map((rule, j) => (
                            <div key={j} style={{ fontSize: 11, color: T.text3, paddingLeft: 8 }}>
                              · {rule}
                            </div>
                          ))}
                        </div>
                      )}
                    </div>
                  ))}
                </div>
              </div>
            )}

            {/* 警示 */}
            {strategy.warnings && strategy.warnings.length > 0 && (
              <div>
                <div style={{
                  fontSize: 10, fontWeight: 700, color: T.up, letterSpacing: 0.5,
                  marginBottom: 4, textTransform: 'uppercase',
                }}>警示</div>
                {strategy.warnings.map((w, i) => (
                  <div key={i} style={{
                    fontSize: 12, color: T.up,
                    padding: '4px 0',
                  }}>⚠ {w}</div>
                ))}
              </div>
            )}

            {/* 本月行動清單 */}
            {strategy.monthly_action_items && strategy.monthly_action_items.length > 0 && (
              <div>
                <div style={{
                  fontSize: 10, fontWeight: 700, color: T.text3, letterSpacing: 0.5,
                  marginBottom: 6, textTransform: 'uppercase',
                }}>本月行動清單</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                  {strategy.monthly_action_items.map((item, i) => (
                    <div key={i} style={{
                      display: 'flex', alignItems: 'flex-start', gap: 8,
                      fontSize: 12, color: T.text2,
                    }}>
                      <span style={{
                        flexShrink: 0, width: 14, height: 14, marginTop: 1,
                        border: '1.5px solid ' + T.borderStrong, borderRadius: 3,
                        display: 'inline-flex',
                      }} />
                      {item}
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>
        )}

        {/* 收起/展開觸發 */}
        <button onClick={() => setExpanded(e => !e)} style={{
          fontSize: 11, color: T.primary, background: 'none',
          border: 'none', cursor: 'pointer', padding: 0,
          fontFamily: T.fontSans, alignSelf: 'flex-start',
        }}>
          {expanded ? '▲ 收起完整建議' : '▼ 展開行為盤點與策略'}
        </button>
      </div>
    </window.Card>
  );
}

// ── 主頁元件 ─────────────────────────────────────────────────────────────────
function WatchlistPage() {
  const [activeTab, setActiveTab] = React.useState('holdings'); // 'holdings' | 'watchlist'
  const vp = window.useViewport();
  const pad = vp.isMobile ? 12 : 24;

  return (
    <div style={{ padding: pad, display: 'flex', flexDirection: 'column', gap: 16 }}>
      {/* Sub-tab switcher */}
      <div style={{
        display: 'flex', gap: 0,
        background: T.hover, borderRadius: T.radius,
        padding: 3, width: 'fit-content',
      }}>
        {[
          { k: 'holdings', l: '庫存' },
          { k: 'watchlist', l: '自選清單' },
        ].map(t => (
          <button key={t.k} onClick={() => setActiveTab(t.k)} style={{
            padding: '6px 20px', border: 'none', cursor: 'pointer',
            borderRadius: 6, fontSize: 13, fontWeight: activeTab === t.k ? 600 : 500,
            background: activeTab === t.k ? T.surface : 'transparent',
            color: activeTab === t.k ? T.text : T.text3,
            boxShadow: activeTab === t.k ? '0 1px 3px rgba(0,0,0,0.06)' : 'none',
            transition: 'all 0.15s',
            fontFamily: T.fontSans,
          }}>
            {t.l}
          </button>
        ))}
      </div>

      {activeTab === 'holdings' ? <HoldingsTab vp={vp} /> : <WatchlistTab vp={vp} />}
    </div>
  );
}
window.WatchlistPage = WatchlistPage;

// ────────────────────────────────────────────────────────────────────────────
// 庫存分頁
// ────────────────────────────────────────────────────────────────────────────
function HoldingsTab({ vp }) {
  const live = window.STOCK_DATA_LIVE;

  const holdings = (live.loaded && live.portfolio && live.portfolio.holdings)
    ? live.portfolio.holdings
    : [];

  const monthlyStrategy = live.loaded && live.monthly_strategy
    ? live.monthly_strategy
    : null;

  if (holdings.length === 0) {
    return (
      <window.Card title="庫存" padding={32}>
        <div style={{ textAlign: 'center', color: T.text3, fontSize: 13 }}>
          <div style={{ fontSize: 15, color: T.text2, fontWeight: 500, marginBottom: 8 }}>尚無持股資料</div>
          <div>請編輯 <code style={{ background: T.hover, padding: '2px 6px', borderRadius: 4 }}>workspace/portfolio.json</code> 後 push，</div>
          <div style={{ marginTop: 4 }}>build_web_data.py 會自動同步到 _data/portfolio_latest.json</div>
        </div>
      </window.Card>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      {holdings.map(h => (
        <HoldingCard key={h.code} holding={h} vp={vp} />
      ))}

      {/* 本月操作建議 */}
      {monthlyStrategy ? (
        <MonthlyStrategyCard strategy={monthlyStrategy} />
      ) : (
        <window.Card padding={20}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
            <window.AIBadge />
            <span style={{ fontSize: 13, fontWeight: 600, color: T.text }}>本月操作建議</span>
          </div>
          <div style={{ color: T.text3, fontSize: 12, textAlign: 'center' }}>
            尚無本月策略（等待 monthly_strategy.json 產出）
          </div>
        </window.Card>
      )}
    </div>
  );
}

function HoldingCard({ holding: h, vp }) {
  const [expanded, setExpanded] = React.useState(false);
  const [editMode, setEditMode] = React.useState(false);
  const [debateExpanded, setDebateExpanded] = React.useState(false);
  const [, forceUpdate] = React.useReducer(x => x + 1, 0);
  const [notes, setNotes] = React.useState(() => {
    const saved = getHoldingNotes(h.code);
    return {
      buy_reason: saved.buy_reason || h.buy_reason || '',
      sell_plan: saved.sell_plan || h.sell_plan || '',
    };
  });
  const [draft, setDraft] = React.useState(notes);

  const live = window.STOCK_DATA_LIVE;
  // daily_analysis: undefined = not yet loaded, null = loaded but missing
  const dailyRaw = live.daily_analysis ? live.daily_analysis[h.code] : undefined;
  const daily = dailyRaw || null;
  const debate = live.debates && live.debates[h.code] ? live.debates[h.code] : null;

  React.useEffect(() => {
    if (expanded) {
      const loaders = [];
      if (dailyRaw === undefined && window.loadDailyAnalysis) loaders.push(window.loadDailyAnalysis(h.code));
      if (!debate && window.loadDebate) loaders.push(window.loadDebate(h.code));
      if (loaders.length > 0) Promise.all(loaders).then(() => forceUpdate());
    }
  }, [expanded]);

  // 現價：優先 daily.price（後端若有輸出），否則 portfolio 帶進來的 current_price
  const livePrice = (daily && daily.price) ? daily.price : (h.current_price || null);
  const pnlPct = livePrice && h.avg_cost
    ? ((livePrice - h.avg_cost) / h.avg_cost * 100)
    : null;
  const pnlColor = pnlPct == null ? T.text3 : pnlPct >= 0 ? T.up : T.down;

  // alert_change
  const alertChange = daily && daily.llm_card && daily.llm_card.alert_change
    ? daily.llm_card.alert_change
    : null;

  const [saveStatus, setSaveStatus] = React.useState('idle'); // idle | saving | saved | error
  const handleSaveNotes = async () => {
    // 先存 localStorage（即時 UX）
    saveHoldingNotes(h.code, draft);
    setNotes(draft);

    // 再 POST 到 Cloudflare Function 永久寫入 portfolio.json
    setSaveStatus('saving');
    const email = (localStorage.getItem('stocksense_user_email_v1') || '').toLowerCase().trim();
    if (!email) {
      setSaveStatus('error');
      return;
    }
    try {
      const res = await fetch('/api/portfolio-notes', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email,
          code: h.code,
          buy_reason: draft.buy_reason || '',
          sell_plan: draft.sell_plan || '',
        }),
      });
      if (res.ok) {
        setSaveStatus('saved');
        setTimeout(() => { setEditMode(false); setSaveStatus('idle'); }, 1200);
      } else {
        setSaveStatus('error');
      }
    } catch {
      setSaveStatus('error');
    }
  };

  return (
    <window.Card padding={0}>
      {/* 主列 */}
      <div style={{
        padding: '14px 16px',
        display: 'flex', alignItems: 'center', gap: 12,
        cursor: 'pointer',
      }} onClick={() => setExpanded(e => !e)}>
        <div style={{ flex: 1 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2, flexWrap: 'wrap' }}>
            <span style={{ fontFamily: T.fontMono, fontSize: 11, color: T.text3 }}>{h.code}</span>
            <span style={{ fontSize: 14, fontWeight: 600, color: T.text }}>{h.name || h.code}</span>
            {alertChange && <AlertChangeBadge alertChange={alertChange} />}
          </div>
          <div style={{ display: 'flex', gap: 12, fontSize: 12, color: T.text2, fontFamily: T.fontMono, flexWrap: 'wrap' }}>
            {h.shares != null && <span>持股 {h.shares.toLocaleString()} 張</span>}
            {h.avg_cost != null && <span>成本 {h.avg_cost}</span>}
            {livePrice != null && <span>現價 {livePrice}</span>}
            {pnlPct != null && (
              <span style={{ color: pnlColor, fontWeight: 600 }}>
                {pnlPct >= 0 ? '+' : ''}{pnlPct.toFixed(1)}%
              </span>
            )}
          </div>
        </div>
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke={T.text3} strokeWidth="1.5"
          style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s', flexShrink: 0 }}>
          <path d="M3 6l5 5 5-5"/>
        </svg>
      </div>

      {/* 買進理由 / 賣出計畫 */}
      <div style={{ padding: '0 16px 14px', borderTop: '1px solid ' + T.border }}>
        {editMode ? (
          <div style={{ paddingTop: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div>
              <div style={{ fontSize: 10, color: T.text3, marginBottom: 4, fontWeight: 600, letterSpacing: 0.3 }}>買進理由</div>
              <textarea value={draft.buy_reason} onChange={e => setDraft(d => ({ ...d, buy_reason: e.target.value }))}
                placeholder="例如：AI 半導體龍頭，2nm 領先..."
                style={{
                  width: '100%', minHeight: 60, padding: '8px 10px',
                  background: T.surface2, border: '1px solid ' + T.border,
                  borderRadius: T.radiusSm, fontSize: 12, color: T.text,
                  resize: 'vertical', fontFamily: T.fontSans,
                  boxSizing: 'border-box',
                }} />
            </div>
            <div>
              <div style={{ fontSize: 10, color: T.text3, marginBottom: 4, fontWeight: 600, letterSpacing: 0.3 }}>賣出計畫</div>
              <textarea value={draft.sell_plan} onChange={e => setDraft(d => ({ ...d, sell_plan: e.target.value }))}
                placeholder="例如：目標 1300 元減碼一半，跌破季線停損..."
                style={{
                  width: '100%', minHeight: 60, padding: '8px 10px',
                  background: T.surface2, border: '1px solid ' + T.border,
                  borderRadius: T.radiusSm, fontSize: 12, color: T.text,
                  resize: 'vertical', fontFamily: T.fontSans,
                  boxSizing: 'border-box',
                }} />
            </div>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              <window.Btn variant="primary" onClick={handleSaveNotes}
                disabled={saveStatus === 'saving'}>
                {saveStatus === 'saving' ? '儲存中...' : saveStatus === 'saved' ? '✓ 已儲存' : '儲存'}
              </window.Btn>
              <window.Btn variant="ghost" onClick={() => { setDraft(notes); setEditMode(false); setSaveStatus('idle'); }}>取消</window.Btn>
              <span style={{ fontSize: 10, color: saveStatus === 'error' ? T.down : T.text3, flex: 1 }}>
                {saveStatus === 'error' ? '⚠️ 雲端儲存失敗（已存 localStorage）' :
                 saveStatus === 'saved' ? '✓ 已存到 portfolio.json，~30 秒後生效' :
                 '儲存後自動 commit 到 GitHub repo'}
              </span>
            </div>
          </div>
        ) : (
          <div style={{ paddingTop: 10, display: 'flex', flexDirection: 'column', gap: 6 }}>
            <div style={{ display: 'flex', gap: 8, fontSize: 12 }}>
              <span style={{ color: T.text3, flexShrink: 0 }}>買進：</span>
              <span style={{ color: notes.buy_reason ? T.text2 : T.text4, flex: 1 }}>
                {notes.buy_reason || '（未填寫）'}
              </span>
            </div>
            <div style={{ display: 'flex', gap: 8, fontSize: 12 }}>
              <span style={{ color: T.text3, flexShrink: 0 }}>賣出：</span>
              <span style={{ color: notes.sell_plan ? T.text2 : T.text4, flex: 1 }}>
                {notes.sell_plan || '（未填寫）'}
              </span>
            </div>
            <div>
              <window.Btn variant="ghost" onClick={(e) => { e.stopPropagation(); setDraft(notes); setEditMode(true); }}
                style={{ marginTop: 2 }}>
                ✏️ 編輯
              </window.Btn>
            </div>
          </div>
        )}
      </div>

      {/* 展開詳情 */}
      {expanded && (
        <div style={{ borderTop: '1px solid ' + T.border }}>
          {/* 初次辯論結論 */}
          {debate ? (
            <div style={{ padding: '12px 16px', borderBottom: '1px solid ' + T.border }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
                <span style={{ fontSize: 10, fontWeight: 700, color: T.ai,
                  background: T.aiSoft, padding: '2px 6px', borderRadius: 3 }}>初次辯論</span>
                <span style={{ fontSize: 11, color: T.text3, fontFamily: T.fontMono }}>
                  Bull {debate.bull_score || '—'} vs Bear {debate.bear_score || '—'}
                </span>
                {debate.decision && (
                  <span style={{
                    fontSize: 10, fontWeight: 700, padding: '2px 6px', borderRadius: 3,
                    background: debate.decision === 'BUY' ? T.upSoft : debate.decision === 'SELL' ? T.downSoft : T.hover,
                    color: debate.decision === 'BUY' ? T.up : debate.decision === 'SELL' ? T.down : T.text2,
                  }}>{debate.decision}</span>
                )}
                <button onClick={() => setDebateExpanded(e => !e)} style={{
                  marginLeft: 'auto', fontSize: 10, color: T.primary, background: 'none',
                  border: 'none', cursor: 'pointer', padding: 0, fontFamily: T.fontSans,
                }}>
                  {debateExpanded ? '▲ 收起' : '▼ 展開辯論摘要'}
                </button>
              </div>
              {debateExpanded && debate.summary && (
                <div style={{ fontSize: 12, color: T.text2, lineHeight: 1.6, marginTop: 4 }}>
                  {debate.summary}
                </div>
              )}
            </div>
          ) : (
            <div style={{ padding: '10px 16px', borderBottom: '1px solid ' + T.border,
              fontSize: 11, color: T.text4 }}>
              尚無辯論記錄（跑 /stock-debate 後會出現）
            </div>
          )}

          {/* 今日深度分析（含分點） */}
          {dailyRaw === undefined && (
            <div style={{ padding: '10px 16px', fontSize: 11, color: T.text4 }}>
              載入中...
            </div>
          )}
          <DailyAnalysisBlock
            code={h.code}
            daily={daily}
            showBroker={true}
            vp={vp}
          />
        </div>
      )}
    </window.Card>
  );
}

// ────────────────────────────────────────────────────────────────────────────
// 自選清單分頁
// ────────────────────────────────────────────────────────────────────────────
function WatchlistTab({ vp }) {
  const [tabs, setTabs] = React.useState(getWatchlistTabs);
  const [activeTabId, setActiveTabId] = React.useState(() => getWatchlistTabs()[0]?.id || 'default');
  const live = window.STOCK_DATA_LIVE;

  const persist = (newTabs) => {
    setTabs(newTabs);
    saveWatchlistTabs(newTabs);
  };

  const activeTabData = tabs.find(t => t.id === activeTabId) || tabs[0];

  const handleAddTab = () => {
    const name = prompt('新分頁名稱：');
    if (!name || !name.trim()) return;
    const newTab = { id: 'tab_' + Date.now(), name: name.trim(), codes: [] };
    const newTabs = [...tabs, newTab];
    persist(newTabs);
    setActiveTabId(newTab.id);
  };

  const handleRenameTab = (tabId) => {
    const tab = tabs.find(t => t.id === tabId);
    if (!tab) return;
    const name = prompt('新名稱：', tab.name);
    if (!name || !name.trim()) return;
    persist(tabs.map(t => t.id === tabId ? { ...t, name: name.trim() } : t));
  };

  const handleAddCode = () => {
    const code = (prompt('輸入股票代號（4 位數）：') || '').trim();
    if (!code || !/^\d{4,5}$/.test(code)) {
      if (code) alert('請輸入 4-5 位數字代號（例：2330）');
      return;
    }
    if (!activeTabData) return;
    if (activeTabData.codes.includes(code)) {
      alert(code + ' 已在清單中');
      return;
    }
    persist(tabs.map(t => t.id === activeTabId
      ? { ...t, codes: [...t.codes, code] }
      : t
    ));
  };

  const handleRemoveCode = (code) => {
    if (!confirm('移除 ' + code + ' 嗎？')) return;
    persist(tabs.map(t => t.id === activeTabId
      ? { ...t, codes: t.codes.filter(c => c !== code) }
      : t
    ));
  };

  const handleDebateRequest = async (code) => {
    const email = (localStorage.getItem('stocksense_user_email_v1') || '').toLowerCase().trim();
    if (!email) {
      alert('請先登入');
      return;
    }
    try {
      const res = await fetch('/api/queue', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, code, source: 'watchlist' }),
      });
      if (res.ok) {
        const data = await res.json();
        alert('✅ 已加入排隊\n\n' + code + ' 將於明日 08:00 cron 自動跑深度辯論（限 2-3 支/天）。\n結果隔天會出現在個股展開卡片中。');
      } else {
        const err = await res.json().catch(() => ({}));
        alert('❌ 申請失敗：' + (err.error || ('HTTP ' + res.status)));
      }
    } catch (e) {
      alert('❌ 網路錯誤：' + (e.message || e));
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      {/* Tab bar */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
        {tabs.map(tab => (
          <div key={tab.id} style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
            <button onClick={() => setActiveTabId(tab.id)} style={{
              padding: '6px 12px', border: '1px solid ' + (activeTabId === tab.id ? T.primary : T.border),
              borderRadius: '6px 0 0 6px', cursor: 'pointer',
              background: activeTabId === tab.id ? T.primarySoft : T.surface,
              color: activeTabId === tab.id ? T.primary : T.text2,
              fontSize: 12, fontWeight: activeTabId === tab.id ? 600 : 500,
              fontFamily: T.fontSans,
            }}>
              {tab.name}
            </button>
            <button onClick={() => handleRenameTab(tab.id)} style={{
              padding: '6px 8px', border: '1px solid ' + (activeTabId === tab.id ? T.primary : T.border),
              borderLeft: 'none', borderRadius: '0 6px 6px 0', cursor: 'pointer',
              background: activeTabId === tab.id ? T.primarySoft : T.surface,
              color: T.text3, fontSize: 10, fontFamily: T.fontSans,
            }}>✏️</button>
          </div>
        ))}
        <button onClick={handleAddTab} style={{
          padding: '6px 12px', border: '1px dashed ' + T.borderStrong,
          borderRadius: 6, cursor: 'pointer', background: 'transparent',
          color: T.text3, fontSize: 12, fontFamily: T.fontSans,
        }}>+ 新增分頁</button>
      </div>

      {/* 股票清單 */}
      {activeTabData && (
        <window.Card padding={0}>
          {/* 表頭（desktop） */}
          {!vp.isMobile && (
            <div style={{
              padding: '8px 16px',
              display: 'grid', gridTemplateColumns: '60px 80px 80px 70px 1fr 100px 32px',
              gap: 8, fontSize: 10, fontWeight: 600, color: T.text3, letterSpacing: 0.3,
              borderBottom: '1px solid ' + T.border, background: T.surface2,
              borderRadius: T.radius + ' ' + T.radius + ' 0 0',
            }}>
              <span>代號</span><span>名稱</span><span>現價</span><span>漲跌%</span>
              <span>分析摘要</span><span style={{ textAlign: 'center' }}>操作</span><span />
            </div>
          )}

          {/* 股票列 */}
          {activeTabData.codes.length === 0 ? (
            <div style={{ padding: '40px 20px', textAlign: 'center', color: T.text3, fontSize: 13 }}>
              清單是空的。點下方「+ 新增股票」開始加入。
            </div>
          ) : (
            activeTabData.codes.map((code, idx) => (
              <WatchlistStockRow
                key={code} code={code} last={idx === activeTabData.codes.length - 1}
                onRemove={() => handleRemoveCode(code)}
                onDebate={() => handleDebateRequest(code)}
                vp={vp}
              />
            ))
          )}

          {/* 新增按鈕 */}
          <div style={{
            padding: '10px 16px', borderTop: activeTabData.codes.length > 0 ? '1px solid ' + T.border : 'none',
          }}>
            <window.Btn variant="ghost" onClick={handleAddCode}>+ 新增股票</window.Btn>
          </div>
        </window.Card>
      )}
    </div>
  );
}

function WatchlistStockRow({ code, last, onRemove, onDebate, vp }) {
  const [expanded, setExpanded] = React.useState(false);
  const [, forceUpdate] = React.useReducer(x => x + 1, 0);
  const live = window.STOCK_DATA_LIVE;

  const daily = live.daily_analysis && live.daily_analysis[code] ? live.daily_analysis[code] : null;

  React.useEffect(() => {
    if (expanded && !daily && window.loadDailyAnalysis) {
      window.loadDailyAnalysis(code).then(() => forceUpdate());
    }
  }, [expanded]);

  const price = daily && daily.price ? daily.price : null;
  const chgPct = daily && daily.change_pct != null ? daily.change_pct : null;
  const chgColor = chgPct == null ? T.text3 : chgPct >= 0 ? T.up : T.down;

  // 名稱：從 daily 讀，沒有就顯示代號
  const name = (daily && daily.name) ? daily.name : code;

  // 摘要：優先用 llm_card.summary，fallback 舊欄位
  const llmSummary = daily && daily.llm_card && daily.llm_card.summary
    ? daily.llm_card.summary
    : null;
  const legacySummary = daily && daily.technical_summary
    ? daily.technical_summary
    : null;
  const summaryFull = llmSummary || legacySummary || '等待明日生成';
  const summaryShort = summaryFull.length > 45 ? summaryFull.slice(0, 45) + '...' : summaryFull;

  // alert_change badge
  const alertChange = daily && daily.llm_card && daily.llm_card.alert_change
    ? daily.llm_card.alert_change
    : null;

  if (vp.isMobile) {
    return (
      <div style={{ borderBottom: last ? 'none' : '1px solid ' + T.border }}>
        <div style={{ padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 8,
          cursor: 'pointer' }} onClick={() => setExpanded(e => !e)}>
          <span style={{ fontFamily: T.fontMono, fontSize: 11, color: T.text3, width: 44 }}>{code}</span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <span style={{ fontSize: 13, fontWeight: 600, color: T.text }}>{name}</span>
              {alertChange && <AlertChangeBadge alertChange={alertChange} />}
            </div>
            {!expanded && (
              <div style={{ fontSize: 10, color: T.text3, marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {summaryShort}
              </div>
            )}
          </div>
          {price != null && (
            <span style={{ fontFamily: T.fontMono, fontSize: 12, color: T.text }}>{price}</span>
          )}
          {chgPct != null && (
            <span style={{ fontFamily: T.fontMono, fontSize: 11, color: chgColor, fontWeight: 500 }}>
              {chgPct >= 0 ? '+' : ''}{chgPct.toFixed(2)}%
            </span>
          )}
        </div>
        {expanded && (
          <div style={{ padding: '0 14px 12px', display: 'flex', flexDirection: 'column', gap: 8 }}>
            {/* 今日分析（不含分點） */}
            <DailyAnalysisBlock code={code} daily={daily} showBroker={false} vp={vp} />
            <div style={{ display: 'flex', gap: 8 }}>
              <window.Btn variant="primary" onClick={(e) => { e.stopPropagation(); onDebate(); }}>申請辯論</window.Btn>
              <button onClick={(e) => { e.stopPropagation(); onRemove(); }} style={{
                fontSize: 12, padding: '5px 10px', fontWeight: 500, cursor: 'pointer',
                background: 'transparent', color: T.down, border: '1px solid ' + T.border,
                borderRadius: T.radiusSm, fontFamily: T.fontSans,
              }}>移除</button>
            </div>
          </div>
        )}
      </div>
    );
  }

  // Desktop / Tablet
  return (
    <div style={{ borderBottom: last ? 'none' : '1px solid ' + T.border }}>
      <div style={{
        padding: '10px 16px',
        display: 'grid', gridTemplateColumns: '60px 80px 80px 70px 1fr 100px 32px',
        gap: 8, alignItems: 'center', cursor: 'pointer',
      }} onClick={() => setExpanded(e => !e)}>
        <span style={{ fontFamily: T.fontMono, fontSize: 11, color: T.text3 }}>{code}</span>
        <div style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 0 }}>
          <span style={{ fontSize: 12, fontWeight: 600, color: T.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            {name}
          </span>
        </div>
        <span style={{ fontFamily: T.fontMono, fontSize: 12, color: T.text }}>
          {price != null ? price : '—'}
        </span>
        <span style={{ fontFamily: T.fontMono, fontSize: 11, color: chgColor, fontWeight: 500 }}>
          {chgPct != null ? (chgPct >= 0 ? '+' : '') + chgPct.toFixed(2) + '%' : '—'}
        </span>
        {/* 摘要 + badge */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0, overflow: 'hidden' }}>
          {alertChange && <AlertChangeBadge alertChange={alertChange} />}
          <span style={{ fontSize: 11, color: T.text2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {summaryShort}
          </span>
        </div>
        <div style={{ display: 'flex', gap: 4, justifyContent: 'center' }}>
          <window.Btn variant="primary" onClick={(e) => { e.stopPropagation(); onDebate(); }}>申請</window.Btn>
          <button onClick={(e) => { e.stopPropagation(); onRemove(); }} style={{
            fontSize: 12, padding: '5px 8px', cursor: 'pointer',
            background: 'transparent', color: T.down, border: '1px solid ' + T.border,
            borderRadius: T.radiusSm, fontFamily: T.fontSans,
          }}>×</button>
        </div>
        <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke={T.text3} strokeWidth="1.5"
          style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}>
          <path d="M3 6l5 5 5-5"/>
        </svg>
      </div>
      {expanded && (
        <div style={{ padding: '0 16px 12px', borderTop: '1px solid ' + T.border }}>
          {/* 今日分析（不含分點、不含辯論、不含本月策略） */}
          <DailyAnalysisBlock code={code} daily={daily} showBroker={false} vp={vp} />
          <div style={{ marginTop: 8 }}>
            <window.Btn variant="primary" onClick={(e) => { e.stopPropagation(); onDebate(); }}>
              申請深度辯論
            </window.Btn>
          </div>
        </div>
      )}
    </div>
  );
}

})();
