(function(){
// 共用視覺元件 - sparkline, mini-chart, AI bubble, 漲跌價格等
const T = window.STOCK_TOKENS;

// 數字千分位
const fmt = (n, d = 0) => Number(n).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });
const sign = (n) => (n > 0 ? '+' : n < 0 ? '' : '');
window.fmt = fmt;
window.sign = sign;

// 漲跌色
window.dirColor = (n) => (n > 0 ? T.up : n < 0 ? T.down : T.flat);
window.dirSoft  = (n) => (n > 0 ? T.upSoft : n < 0 ? T.downSoft : T.surface2);

// 隨機 sparkline 路徑
function sparkPath(points, w, h, pad = 2) {
  const min = Math.min(...points), max = Math.max(...points);
  const range = max - min || 1;
  return points.map((p, i) => {
    const x = pad + (i * (w - pad * 2)) / (points.length - 1);
    const y = h - pad - ((p - min) / range) * (h - pad * 2);
    return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)} ${y.toFixed(1)}`;
  }).join(' ');
}
window.sparkPath = sparkPath;

// 產生擬真價格序列
function genSeries(n, seed = 1, drift = 0, vol = 0.015) {
  const r = (() => { let s = seed; return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; })();
  const out = [100];
  for (let i = 1; i < n; i++) {
    out.push(out[i - 1] * (1 + drift + (r() - 0.5) * vol));
  }
  return out;
}
window.genSeries = genSeries;

// Sparkline
function Sparkline({ data, w = 80, h = 24, color, fill = false, strokeWidth = 1.5 }) {
  const c = color || (data[data.length - 1] >= data[0] ? T.up : T.down);
  const d = sparkPath(data, w, h);
  const min = Math.min(...data), max = Math.max(...data);
  const range = max - min || 1;
  const lastY = h - 2 - ((data[data.length - 1] - min) / range) * (h - 4);
  const lastX = w - 2;
  return (
    <svg width={w} height={h} style={{ display: 'block' }}>
      {fill && (
        <path d={`${d} L${w - 2} ${h} L2 ${h} Z`} fill={c} fillOpacity="0.08" />
      )}
      <path d={d} fill="none" stroke={c} strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" />
      <circle cx={lastX} cy={lastY} r="2" fill={c} />
    </svg>
  );
}
window.Sparkline = Sparkline;

// 漲跌幅小膠囊
function ChangeChip({ value, suffix = '%', size = 'sm' }) {
  const c = window.dirColor(value);
  const sizes = { sm: { fs: 11, py: 1, px: 5 }, md: { fs: 12, py: 2, px: 6 } };
  const s = sizes[size];
  return (
    <span style={{
      fontFamily: T.fontMono, fontSize: s.fs, fontWeight: 500,
      color: c, background: window.dirSoft(value),
      padding: `${s.py}px ${s.px}px`, borderRadius: 4, lineHeight: 1.2,
      display: 'inline-flex', alignItems: 'center', gap: 2,
      fontVariantNumeric: 'tabular-nums',
    }}>
      {sign(value)}{value.toFixed(2)}{suffix}
    </span>
  );
}
window.ChangeChip = ChangeChip;

// 卡片
function Card({ children, title, subtitle, action, ai, padding = 20, style = {} }) {
  return (
    <div style={{
      background: T.surface, border: `1px solid ${T.border}`, borderRadius: T.radius,
      ...style,
    }}>
      {(title || action) && (
        <div style={{
          padding: `14px ${padding}px`, borderBottom: `1px solid ${T.border}`,
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
            {ai && <AIBadge />}
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: 14, fontWeight: 600, color: T.text, letterSpacing: -0.1 }}>{title}</div>
              {subtitle && <div style={{ fontSize: 12, color: T.text3, marginTop: 2 }}>{subtitle}</div>}
            </div>
          </div>
          {action && <div>{action}</div>}
        </div>
      )}
      <div style={{ padding }}>{children}</div>
    </div>
  );
}
window.Card = Card;

// AI 標識小徽章
function AIBadge({ size = 'sm', label = 'AI' }) {
  const sizes = { sm: { w: 18, h: 18, fs: 9 }, md: { w: 22, h: 22, fs: 10 } };
  const s = sizes[size];
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      width: s.w, height: s.h, borderRadius: 4,
      background: `linear-gradient(135deg, ${T.ai}, oklch(0.58 0.15 290))`,
      color: 'white', fontSize: s.fs, fontWeight: 700, letterSpacing: 0.3,
      flexShrink: 0,
    }}>{label}</span>
  );
}
window.AIBadge = AIBadge;

// AI 註解區塊（淺紫漸層底）
function AINote({ children, title, action, compact = false }) {
  return (
    <div style={{
      background: `linear-gradient(135deg, ${T.aiSoft}, oklch(0.97 0.018 290))`,
      border: `1px solid ${T.aiBorder}`,
      borderRadius: T.radius,
      padding: compact ? '10px 12px' : '14px 16px',
      position: 'relative',
    }}>
      {title && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
          <AIBadge />
          <div style={{ fontSize: 12, fontWeight: 600, color: T.ai, letterSpacing: 0.2 }}>{title}</div>
          <div style={{ flex: 1 }} />
          {action}
        </div>
      )}
      <div style={{ fontSize: compact ? 12 : 13, color: T.text2, lineHeight: 1.55 }}>{children}</div>
    </div>
  );
}
window.AINote = AINote;

// Section 標題
function SectionHead({ title, sub, action }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 14 }}>
      <div>
        <div style={{ fontSize: 18, fontWeight: 600, color: T.text, letterSpacing: -0.3 }}>{title}</div>
        {sub && <div style={{ fontSize: 12, color: T.text3, marginTop: 2 }}>{sub}</div>}
      </div>
      {action}
    </div>
  );
}
window.SectionHead = SectionHead;

// 按鈕
function Btn({ children, variant = 'ghost', size = 'sm', onClick, icon, active, disabled, style: extraStyle }) {
  const sizes = { sm: { fs: 12, py: 5, px: 10 }, md: { fs: 13, py: 7, px: 14 } };
  const s = sizes[size];
  const variants = {
    primary: { bg: T.text, color: 'white', border: 'transparent' },
    ghost:   { bg: 'transparent', color: T.text2, border: T.border },
    ai:      { bg: T.aiSoft, color: T.ai, border: T.aiBorder },
    soft:    { bg: T.hover, color: T.text, border: 'transparent' },
  };
  const v = variants[variant];
  return (
    <button onClick={onClick} disabled={disabled} style={{
      fontSize: s.fs, padding: `${s.py}px ${s.px}px`, fontWeight: 500,
      background: active ? T.text : v.bg, color: active ? 'white' : v.color,
      border: `1px solid ${active ? T.text : v.border}`,
      borderRadius: T.radiusSm,
      cursor: disabled ? 'wait' : 'pointer',
      opacity: disabled ? 0.6 : 1,
      display: 'inline-flex', alignItems: 'center', gap: 5,
      fontFamily: T.fontSans, transition: 'all 0.12s',
      ...(extraStyle || {}),
    }}>
      {icon}{children}
    </button>
  );
}
window.Btn = Btn;

// 空狀態提示卡（LIVE 資料缺失時顯示）
function EmptyState({ title, hint }) {
  const T = window.STOCK_TOKENS;
  return (
    <div style={{
      padding: 60, textAlign: 'center', background: T.surface,
      border: `1px solid ${T.border}`, borderRadius: T.radius,
      color: T.text3, fontSize: 13,
    }}>
      <div style={{ fontSize: 28, marginBottom: 12, opacity: 0.4 }}>—</div>
      <div style={{ fontSize: 14, color: T.text2, fontWeight: 500, marginBottom: 6 }}>{title}</div>
      <div>{hint}</div>
    </div>
  );
}
window.EmptyState = EmptyState;

// K 線圖（簡化版，畫一段 candle + 均線 + 量）
function CandleChart({ width = 720, height = 320, ticker = '2330' }) {
  const n = 60;
  const seed = ticker.split('').reduce((a, c) => a + c.charCodeAt(0), 0);
  const r = (() => { let s = seed; return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; })();
  const candles = [];
  let p = 100;
  for (let i = 0; i < n; i++) {
    const o = p;
    const ch = (r() - 0.45) * 4;
    const c = o + ch;
    const hi = Math.max(o, c) + r() * 1.5;
    const lo = Math.min(o, c) - r() * 1.5;
    const v = 0.4 + r() * 0.6;
    candles.push({ o, c, hi, lo, v });
    p = c;
  }
  const allP = candles.flatMap(c => [c.hi, c.lo]);
  const min = Math.min(...allP), max = Math.max(...allP);
  const range = max - min;
  const padX = 40, padY = 20;
  const chartH = height - 90;
  const w = width - padX * 2;
  const cw = w / n;
  const y = (val) => padY + chartH - ((val - min) / range) * chartH;

  // MA20
  const ma20 = candles.map((_, i) => {
    if (i < 19) return null;
    const slice = candles.slice(i - 19, i + 1);
    return slice.reduce((s, c) => s + c.c, 0) / 20;
  });

  // volume bars
  const volH = 50;
  const volY = padY + chartH + 20;
  const maxV = Math.max(...candles.map(c => c.v));

  return (
    <svg width={width} height={height} style={{ display: 'block' }}>
      {/* grid */}
      {[0, 0.25, 0.5, 0.75, 1].map((p, i) => (
        <line key={i} x1={padX} x2={width - padX} y1={padY + chartH * p} y2={padY + chartH * p}
          stroke={T.border} strokeWidth="1" strokeDasharray={i === 0 || i === 4 ? '0' : '2 3'} />
      ))}
      {/* y-axis labels */}
      {[0, 0.25, 0.5, 0.75, 1].map((p, i) => (
        <text key={i} x={width - padX + 6} y={padY + chartH * p + 3}
          fontSize="10" fontFamily={T.fontMono} fill={T.text3}>
          {(max - range * p).toFixed(1)}
        </text>
      ))}
      {/* candles */}
      {candles.map((c, i) => {
        const x = padX + i * cw + cw / 2;
        const isUp = c.c >= c.o;
        const col = isUp ? T.up : T.down;
        return (
          <g key={i}>
            <line x1={x} x2={x} y1={y(c.hi)} y2={y(c.lo)} stroke={col} strokeWidth="1" />
            <rect x={x - cw * 0.35} y={y(Math.max(c.o, c.c))}
              width={cw * 0.7} height={Math.max(1, Math.abs(y(c.o) - y(c.c)))}
              fill={col} />
            <rect x={x - cw * 0.35} y={volY + volH - (c.v / maxV) * volH}
              width={cw * 0.7} height={(c.v / maxV) * volH}
              fill={col} fillOpacity="0.5" />
          </g>
        );
      })}
      {/* MA20 */}
      <path d={ma20.map((v, i) => {
        if (v === null) return '';
        const x = padX + i * cw + cw / 2;
        return `${i === 19 ? 'M' : 'L'}${x.toFixed(1)} ${y(v).toFixed(1)}`;
      }).join(' ')} fill="none" stroke={T.warn} strokeWidth="1.2" />
      {/* MA20 label */}
      <text x={padX} y={14} fontSize="10" fontFamily={T.fontMono} fill={T.warn}>MA20</text>
      <text x={padX + 38} y={14} fontSize="10" fontFamily={T.fontMono} fill={T.text3}>
        {ma20[ma20.length - 1]?.toFixed(2)}
      </text>
    </svg>
  );
}
window.CandleChart = CandleChart;

// ── useViewport hook（全域 RWD 斷點）────────────────────────────────────────
function useViewport() {
  const [width, setWidth] = React.useState(typeof window !== 'undefined' ? window.innerWidth : 1440);
  React.useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return {
    width,
    isMobile: width < 768,
    isTablet: width >= 768 && width < 1024,
    isDesktop: width >= 1024,
  };
}
window.useViewport = useViewport;

Object.assign(window, { fmt, sign, Sparkline, ChangeChip, Card, AIBadge, AINote, SectionHead, Btn, CandleChart, useViewport });

})();
