(function(){
// Dashboard - 首頁總覽
// 接線：live.picks（今日重點）+ live.portfolio.totals + live.news.items + live.themes
const T = window.STOCK_TOKENS;

// ── DataMode 標示元件（每頁右上角顯示 LIVE / DEMO）──────────────────────
function DataModeBadge() {
  const live = window.STOCK_DATA_LIVE;
  const isLive = live.loaded && live.picks;
  const dateStr = live.manifest && live.manifest.generated_at
    ? live.manifest.generated_at.slice(0, 10) : '';
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 4,
      fontSize: 10, color: T.text3, fontFamily: T.fontMono }}>
      <span style={{
        display: 'inline-block', width: 6, height: 6, borderRadius: 3,
        background: isLive ? 'oklch(0.7 0.15 155)' : 'oklch(0.7 0 0)',
      }} />
      {isLive ? ('LIVE ' + dateStr) : 'DEMO'}
    </div>
  );
}

function Dashboard({ goPage }) {
  const vp = window.useViewport();
  const live = window.STOCK_DATA_LIVE;

  // ── market indices ────────────────────────────────────────────────────────
  const indices = live?.market_indices?.indices || [];
  const findIdx = (sym) => indices.find(i => i.symbol === sym) || null;

  // ── picks summary ─────────────────────────────────────────────────────────
  const picksSummary = live?.picks?.summary || null;

  // news for NewsList — 無 live 資料則空陣列
  const newsItems = (live.loaded && live.news && live.news.items &&
    (live.news._status === 'live' || (!live.news.not_implemented && Array.isArray(live.news.items))))
    ? live.news.items
    : [];

  // sectors / themes for HotSectors — 無 live 資料則空陣列
  const sectorItems = (live.loaded && live.themes && live.themes.themes)
    ? live.themes.themes.map(th => ({
        name: th.name,
        chg: th.heat * 0.5,
        momentum: th.heat * 10,
        news: th.stocks ? th.stocks.length : 0,
        hot: th.heat >= 7,
      }))
    : [];

  // flow for InstFlow — 無 live 資料則空陣列
  const twFlowItems = (live.loaded && live.flow_tw && live.flow_tw.institutions &&
    (live.flow_tw._status === 'live' || !live.flow_tw.not_implemented))
    ? live.flow_tw.institutions.map(f => ({
        // 真實 fetcher 寫的是 buy_b / sell_b / net_b（億），mock 用 buy / sell / net
        name: f.name,
        buy: (typeof f.buy_b === 'number') ? f.buy_b : (typeof f.buy === 'number' ? f.buy : 0),
        sell: (typeof f.sell_b === 'number') ? f.sell_b : (typeof f.sell === 'number' ? f.sell : 0),
        net: (typeof f.net_b === 'number') ? f.net_b : (typeof f.net === 'number' ? f.net : 0),
      }))
    : [];

  const pad = vp.isMobile ? 12 : 24;

  return (
    <div style={{ padding: pad, display: 'flex', flexDirection: 'column', gap: vp.isMobile ? 12 : 16 }}>
      {/* AI 盤前簡報 - 大卡 */}
      <AIBriefingCard live={live} vp={vp} />

      {/* 市場概況 4 卡 */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: vp.isMobile ? '1fr 1fr' : 'repeat(4, 1fr)',
        gap: vp.isMobile ? 8 : 12,
      }}>
        <IndexStatCard label="加權指數" idx={findIdx('TWII')} vp={vp} />
        <IndexStatCard label="櫃買指數" idx={findIdx('TPEx')} vp={vp} />
        <IndexStatCard label="費城半導體" idx={findIdx('SOX')} vp={vp} />
        <PicksStatCard picksSummary={picksSummary} vp={vp} />
      </div>

      {/* 下排：題材熱度 + 資金流向 + 新聞 */}
      <div style={{
        display: 'grid',
        gridTemplateColumns: vp.isMobile ? '1fr' : (vp.isTablet ? '1fr 1fr' : 'repeat(3, 1fr)'),
        gap: 16,
      }}>
        <window.Card title="題材熱度 Top 5" ai
          action={<window.Btn onClick={() => goPage('sector')}>看全部 →</window.Btn>}
          padding={0}>
          <HotSectors sectorItems={sectorItems} goPage={goPage} />
        </window.Card>
        <window.Card title="法人買賣超" padding={0}
          action={<window.Btn onClick={() => goPage('flow-tw')}>資金流向 →</window.Btn>}>
          <InstFlow twFlowItems={twFlowItems} />
        </window.Card>
        <window.Card title="即時新聞" ai padding={0}
          action={<window.Btn onClick={() => goPage('news')}>新聞中心 →</window.Btn>}>
          <NewsList newsItems={newsItems} />
        </window.Card>
      </div>

      {/* 右上 LIVE/DEMO 標示 */}
      {!vp.isMobile && (
        <div style={{ position: 'fixed', top: 18, right: 100 }}>
          <DataModeBadge />
        </div>
      )}
    </div>
  );
}
window.Dashboard = Dashboard;

// ── 市場概況卡：單一指數 ─────────────────────────────────────────────────────
function IndexStatCard({ label, idx, vp }) {
  const isMob = vp && vp.isMobile;
  const hasData = idx && idx.value != null;
  const c = hasData && idx.change_pct != null ? window.dirColor(idx.change_pct) : T.text;
  return (
    <div style={{
      background: T.surface, border: '1px solid ' + T.border,
      borderRadius: T.radius, padding: isMob ? 12 : 16,
    }}>
      <div style={{ fontSize: 11, color: T.text3, marginBottom: 4 }}>{label}</div>
      <div style={{
        fontSize: isMob ? 18 : 22, fontWeight: 600,
        color: hasData ? c : T.text3,
        fontFamily: T.fontMono, letterSpacing: -0.5,
        fontVariantNumeric: 'tabular-nums',
      }}>
        {hasData ? window.fmt(idx.value, idx.value > 1000 ? 2 : 2) : '—'}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
        {hasData && idx.change_pct != null
          ? <window.ChangeChip value={idx.change_pct} />
          : <span style={{ fontSize: 11, color: T.text4 }}>待更新</span>
        }
      </div>
    </div>
  );
}

// ── 市場概況卡：今日推薦數 ────────────────────────────────────────────────────
function PicksStatCard({ picksSummary, vp }) {
  const isMob = vp && vp.isMobile;
  const mainCount = picksSummary?.main_picks ?? null;
  const smallCount = picksSummary?.small_picks ?? null;
  const totalCount = (mainCount != null && smallCount != null)
    ? mainCount + smallCount : null;
  return (
    <div style={{
      background: T.surface, border: '1px solid ' + T.border,
      borderRadius: T.radius, padding: isMob ? 12 : 16,
    }}>
      <div style={{ fontSize: 11, color: T.text3, marginBottom: 4 }}>今日推薦數</div>
      <div style={{
        fontSize: isMob ? 18 : 22, fontWeight: 600, color: T.text,
        fontFamily: T.fontMono, letterSpacing: -0.5,
        fontVariantNumeric: 'tabular-nums',
      }}>
        {totalCount != null ? (totalCount + ' 支') : '—'}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
        {totalCount != null
          ? <span style={{ fontSize: 11, color: T.text3 }}>
              {mainCount} 主力 {smallCount} 小型
            </span>
          : <span style={{ fontSize: 11, color: T.text4 }}>待更新</span>
        }
      </div>
    </div>
  );
}

function AIBriefingCard({ live, vp }) {
  // picks 前 3 筆當「今日重點」（mobile 只取 1 筆）
  const maxPicks = (vp && vp.isMobile) ? 1 : 3;
  const topPicks = (live.loaded && live.picks)
    ? [...(live.picks.main_zone || []), ...(live.picks.small_zone || [])].slice(0, maxPicks)
    : null;

  return (
    <div style={{
      background: 'linear-gradient(135deg, ' + T.aiSoft + ' 0%, oklch(0.97 0.018 290) 50%, oklch(0.98 0.015 250) 100%)',
      border: '1px solid ' + T.aiBorder, borderRadius: T.radiusLg,
      padding: vp && vp.isMobile ? 14 : 20, position: 'relative', overflow: 'hidden',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
        <window.AIBadge size="md" />
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, color: T.ai, letterSpacing: 0.2 }}>AI 盤前簡報</div>
          <div style={{ fontSize: 11, color: T.text3, fontFamily: T.fontMono }}>
            {live.manifest && live.manifest.generated_at
              ? live.manifest.generated_at.slice(0, 10).replace(/-/g, '/') + ' · ' +
                live.manifest.generated_at.slice(11, 16) + ' 生成'
              : '資料尚未生成'}
            {live.picks
              ? ' · 掃描 ' + (live.picks.summary && live.picks.summary.scanned
                  ? live.picks.summary.scanned : '—') + ' 檔'
              : ''}
          </div>
        </div>
        <div style={{ flex: 1 }} />
      </div>

      {topPicks ? (
        <div style={{
          display: 'grid',
          gridTemplateColumns: vp && vp.isMobile ? '1fr' : 'repeat(3, 1fr)',
          gap: vp && vp.isMobile ? 10 : 14,
        }}>
          {topPicks.map((pk, i) => (
            <BriefBlock key={pk.code}
              tag={i === 0 ? '今日主推' : '精選'}
              title={pk.code + ' ' + pk.name}
              body={(pk.key_reasons && pk.key_reasons.slice(0, 2).join('、')) ||
                (pk.momentum_reasons && pk.momentum_reasons.slice(0, 2).join('、')) ||
                (pk.llm_card && pk.llm_card.fallback) || ''}
              highlight={i === 0}
            />
          ))}
        </div>
      ) : (
        <div style={{
          padding: '20px 0', textAlign: 'center', color: T.text3, fontSize: 13,
        }}>
          <div style={{ fontSize: 14, color: T.text2, fontWeight: 500, marginBottom: 6 }}>資料尚未生成</div>
          <div>明天 08:00（台灣）pipeline 自動跑後填入</div>
        </div>
      )}

      {/* decorative gradient orb */}
      <div style={{ position: 'absolute', right: -60, top: -60, width: 200, height: 200,
        borderRadius: '50%',
        background: 'radial-gradient(circle, oklch(0.85 0.08 290 / 0.4), transparent 70%)',
        pointerEvents: 'none' }} />
    </div>
  );
}

function BriefBlock({ tag, title, body, highlight, warn }) {
  const accent = warn ? T.warn : highlight ? T.up : T.ai;
  return (
    <div style={{
      background: 'rgba(255,255,255,0.55)', border: '1px solid rgba(255,255,255,0.8)',
      borderRadius: 10, padding: 14, backdropFilter: 'blur(8px)',
    }}>
      <div style={{ fontSize: 10, fontWeight: 600, color: accent, letterSpacing: 0.4,
        textTransform: 'uppercase', marginBottom: 6 }}>● {tag}</div>
      <div style={{ fontSize: 14, fontWeight: 600, color: T.text, marginBottom: 6, letterSpacing: -0.2 }}>{title}</div>
      <div style={{ fontSize: 12, color: T.text2, lineHeight: 1.55 }}>{body}</div>
    </div>
  );
}

function HoldingsTable({ holdings, vp }) {
  const filtered = holdings.filter(h => h.shares > 0);
  if (filtered.length === 0) {
    return (
      <div style={{ padding: 32, textAlign: 'center', color: T.text3, fontSize: 13 }}>
        尚未匯入持股，請編輯 Stock/workspace/portfolio.json
      </div>
    );
  }

  // mobile 卡片模式
  if (vp && vp.isMobile) {
    return (
      <div>
        {filtered.map((h, i) => {
          const cost = h.avgCost * h.shares;
          const value = h.price * h.shares;
          const pnl = value - cost;
          const pnlPct = cost > 0 ? (pnl / cost) * 100 : 0;
          return (
            <div key={h.code + i} style={{
              padding: '12px 16px',
              borderBottom: i < filtered.length - 1 ? '1px solid ' + T.border : 'none',
              cursor: 'pointer',
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                  <span style={{ fontFamily: T.fontMono, fontSize: 10, color: T.text3 }}>{h.code}</span>
                  <span style={{ fontSize: 13, fontWeight: 600, color: T.text }}>{h.name}</span>
                </div>
                <window.ChangeChip value={h.dayChg} />
              </div>
              <div style={{ display: 'flex', gap: 16, fontSize: 11, color: T.text3 }}>
                <span>現價 <span style={{ color: T.text, fontFamily: T.fontMono }}>{window.fmt(h.price, h.price < 100 ? 2 : 1)}</span></span>
                <span>損益 <span style={{ color: window.dirColor(pnl), fontFamily: T.fontMono, fontWeight: 600 }}>{window.sign(pnlPct)}{pnlPct.toFixed(1)}%</span></span>
                <span>權重 <span style={{ color: T.text2, fontFamily: T.fontMono }}>{h.weight.toFixed(1)}%</span></span>
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  return (
    <div>
      <div style={{
        display: 'grid', gridTemplateColumns: '1.6fr 0.8fr 0.9fr 0.9fr 0.7fr 1fr',
        padding: '8px 16px', fontSize: 10, color: T.text3, fontWeight: 500,
        letterSpacing: 0.3, textTransform: 'uppercase', borderBottom: '1px solid ' + T.border,
        background: T.surface2,
      }}>
        <div>標的</div>
        <div style={{ textAlign: 'right' }}>現價</div>
        <div style={{ textAlign: 'right' }}>持有損益</div>
        <div style={{ textAlign: 'right' }}>今日</div>
        <div style={{ textAlign: 'right' }}>權重</div>
        <div style={{ textAlign: 'right' }}>趨勢 / AI</div>
      </div>
      {filtered.map((h, i) => {
        const cost = h.avgCost * h.shares;
        const value = h.price * h.shares;
        const pnl = value - cost;
        const pnlPct = cost > 0 ? (pnl / cost) * 100 : 0;
        const series = window.genSeries(20, h.code.charCodeAt(0) + i, pnlPct > 0 ? 0.003 : -0.001);
        return (
          <div key={h.code + i} style={{
            display: 'grid', gridTemplateColumns: '1.6fr 0.8fr 0.9fr 0.9fr 0.7fr 1fr',
            padding: '12px 16px', borderBottom: i < filtered.length - 1 ? '1px solid ' + T.border : 'none',
            fontSize: 12, alignItems: 'center', cursor: 'pointer',
          }}>
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <span style={{ fontFamily: T.fontMono, fontSize: 11, color: T.text3 }}>{h.code}</span>
                <span style={{ fontWeight: 600, color: T.text }}>{h.name}</span>
              </div>
              <div style={{ fontSize: 10, color: T.text3, marginTop: 2 }}>
                {h.shares} 股 · 均價 {h.avgCost} · {h.sector}
              </div>
            </div>
            <div style={{ textAlign: 'right', fontFamily: T.fontMono, fontWeight: 500, color: T.text }}>
              {window.fmt(h.price, h.price < 100 ? 2 : 1)}
            </div>
            <div style={{ textAlign: 'right', fontFamily: T.fontMono }}>
              <div style={{ color: window.dirColor(pnl), fontWeight: 600 }}>{window.sign(pnl)}{window.fmt(pnl)}</div>
              <div style={{ fontSize: 10, color: window.dirColor(pnl), opacity: 0.8 }}>{window.sign(pnlPct)}{pnlPct.toFixed(2)}%</div>
            </div>
            <div style={{ textAlign: 'right' }}>
              <window.ChangeChip value={h.dayChg} />
            </div>
            <div style={{ textAlign: 'right', fontFamily: T.fontMono, fontSize: 11, color: T.text2 }}>
              {h.weight.toFixed(1)}%
              <div style={{ width: 40, height: 3, background: T.hover, borderRadius: 2, marginLeft: 'auto', marginTop: 3 }}>
                <div style={{ width: Math.min(100, h.weight * 2) + '%', height: '100%', background: T.primary, borderRadius: 2 }} />
              </div>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 8 }}>
              <window.Sparkline data={series} w={60} h={20} fill />
              {pnlPct > 30 && <span style={{ fontSize: 9, fontWeight: 700, color: T.warn,
                background: T.warnSoft, padding: '2px 5px', borderRadius: 3, letterSpacing: 0.2 }}>過熱</span>}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function SectorConcentration({ holdings, vp }) {
  if (!holdings || holdings.length === 0) {
    return (
      <div style={{ padding: '16px 0', textAlign: 'center', color: T.text3, fontSize: 12 }}>
        尚未匯入持股
      </div>
    );
  }
  // 按 sector 統計權重
  const sectorMap = {};
  holdings.forEach(h => {
    const s = h.sector || '其他';
    sectorMap[s] = (sectorMap[s] || 0) + (h.weight || 0);
  });
  const sectors = Object.entries(sectorMap).map(([name, pct]) => ({ name, pct }));
  const colors = ['oklch(0.58 0.20 25)', 'oklch(0.55 0.13 245)', 'oklch(0.6 0.13 290)', 'oklch(0.6 0.13 60)', T.text4, T.border];
  return (
    <div>
      <div style={{ display: 'flex', height: 28, borderRadius: 4, overflow: 'hidden', marginBottom: 12 }}>
        {sectors.map((s, i) => (
          <div key={s.name} style={{ width: (s.pct * 1.1) + '%', background: colors[i % colors.length] }} />
        ))}
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {sectors.map((s, i) => (
          <div key={s.name} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 11 }}>
            <span style={{ width: 8, height: 8, background: colors[i % colors.length], borderRadius: 2 }} />
            <span style={{ flex: 1, color: T.text2 }}>{s.name}</span>
            <span style={{ fontFamily: T.fontMono, color: T.text, fontWeight: 500 }}>{s.pct.toFixed(1)}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function MarketSummary() {
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 4 }}>
        <span style={{ fontSize: 26, fontWeight: 600, color: T.up, fontFamily: T.fontMono, letterSpacing: -0.5 }}>—</span>
      </div>
      <div style={{ fontSize: 11, color: T.text3, fontFamily: T.fontMono, marginBottom: 12 }}>
        市場資料待更新
      </div>
      <window.Sparkline data={window.genSeries(40, 23, 0.001, 0.008)} w={260} h={50} fill strokeWidth={1.8} />
    </div>
  );
}

function HotSectors({ sectorItems, goPage }) {
  if (!sectorItems || sectorItems.length === 0) {
    return (
      <div style={{ padding: 24, textAlign: 'center', color: T.text3, fontSize: 12 }}>
        題材資料尚未更新
      </div>
    );
  }
  return (
    <div>
      {sectorItems.slice(0, 5).map((s, i) => (
        <div key={s.name} style={{
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '10px 16px', borderBottom: i < 4 ? '1px solid ' + T.border : 'none',
          cursor: 'pointer',
        }} onClick={() => goPage('sector')}>
          <span style={{ width: 18, fontSize: 10, color: T.text3, fontFamily: T.fontMono }}>{i + 1}</span>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: T.text }}>{s.name}</div>
            <div style={{ fontSize: 10, color: T.text3, marginTop: 1 }}>動能 {s.momentum} · {s.news} 則新聞</div>
          </div>
          <div style={{ width: 50, height: 6, background: T.hover, borderRadius: 3 }}>
            <div style={{ width: s.momentum + '%', height: '100%',
              background: s.momentum > 70 ? T.up : T.primary, borderRadius: 3 }} />
          </div>
          <window.ChangeChip value={s.chg} />
        </div>
      ))}
    </div>
  );
}

function InstFlow({ twFlowItems }) {
  if (!twFlowItems || twFlowItems.length === 0) {
    return (
      <div style={{ padding: 24, textAlign: 'center', color: T.text3, fontSize: 12 }}>
        資金流功能開發中
      </div>
    );
  }
  const max = Math.max(...twFlowItems.flatMap(f => [Math.abs(f.buy), Math.abs(f.sell), Math.abs(f.net)]));
  const total = twFlowItems.reduce((s, f) => s + f.net, 0);
  return (
    <div style={{ padding: 16 }}>
      {twFlowItems.map((f) => (
        <div key={f.name} style={{ marginBottom: 12 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4, fontSize: 11 }}>
            <span style={{ color: T.text2, fontWeight: 500 }}>{f.name}</span>
            <span style={{ color: window.dirColor(f.net), fontFamily: T.fontMono, fontWeight: 600 }}>
              {window.sign(f.net)}{f.net.toFixed(1)} 億
            </span>
          </div>
          <div style={{ display: 'flex', height: 14, gap: 2 }}>
            <div style={{ flex: f.buy / max, background: T.up, borderRadius: 2,
              display: 'flex', alignItems: 'center', justifyContent: 'flex-end', paddingRight: 4 }}>
              <span style={{ fontSize: 9, color: 'white', fontFamily: T.fontMono }}>{f.buy.toFixed(0)}</span>
            </div>
            <div style={{ flex: f.sell / max, background: T.down, borderRadius: 2,
              display: 'flex', alignItems: 'center', justifyContent: 'flex-end', paddingRight: 4 }}>
              <span style={{ fontSize: 9, color: 'white', fontFamily: T.fontMono }}>{f.sell.toFixed(0)}</span>
            </div>
            <div style={{ flex: 1 - (f.buy + f.sell) / (max * 2) }} />
          </div>
        </div>
      ))}
      <div style={{ borderTop: '1px solid ' + T.border, paddingTop: 10, marginTop: 4,
        display: 'flex', justifyContent: 'space-between', fontSize: 11 }}>
        <span style={{ color: T.text3 }}>三大法人合計</span>
        <span style={{ color: window.dirColor(total), fontFamily: T.fontMono, fontWeight: 600 }}>
          {window.sign(total)}{total.toFixed(1)} 億
        </span>
      </div>
    </div>
  );
}

function NewsList({ newsItems }) {
  if (!newsItems || newsItems.length === 0) {
    return (
      <div style={{ padding: 24, textAlign: 'center', color: T.text3, fontSize: 12 }}>
        新聞收集器尚未實作
      </div>
    );
  }
  return (
    <div>
      {newsItems.slice(0, 4).map((n, i) => (
        <div key={i} style={{
          padding: '10px 16px', borderBottom: i < 3 ? '1px solid ' + T.border : 'none',
          cursor: 'pointer', display: 'flex', gap: 10, alignItems: 'flex-start',
        }}>
          <div style={{
            width: 4, height: 4, borderRadius: 2, marginTop: 6, flexShrink: 0,
            background: n.sentiment > 0.3 ? T.up : n.sentiment < -0.3 ? T.down : T.text3,
          }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 12, color: T.text, fontWeight: 500, lineHeight: 1.45 }}>
              {n.title}
            </div>
            <div style={{ fontSize: 10, color: T.text3, marginTop: 4, fontFamily: T.fontMono,
              display: 'flex', gap: 8, alignItems: 'center' }}>
              <span>{n.time}</span>
              <span>·</span>
              <span>{n.src}</span>
              {n.related && n.related.length > 0 && <>
                <span>·</span>
                <span style={{ color: T.primary }}>關聯 {n.related.length} 檔</span>
              </>}
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

})();
