// 另類指標 — Almanac of Oddities (Live Data Edition)
const { useState, useMemo, useEffect } = React;

// ── Static definitions (blurb / labels) ─────────────────
const ORACLE_DEFS = [
  {
    id: 'pizza',   icon: 'pizza',
    cn: '恐慌晴雨表', en: 'VIX Fear Gauge', origin: 'VIX 恐慌指數 · 芝加哥選擇權交易所',
    unit: 'VIX 即時讀數', reverse: false,
    blurb: 'VIX 是市場對未來 30 日波動率的隱含預期，俗稱「恐懼指數」。VIX < 15 市場如沐春風；20–25 開始起霧；> 30 風雨欲來，各方開始深夜加班叫外送。',
    bull: '軍工、資安、防禦型股',
  },
  {
    id: 'saltchicken', icon: 'chicken',
    cn: '多頭廣度儀', en: 'Advance-Decline Meter', origin: '全市場漲跌家數 · TWSE + TPEx',
    unit: '全市場上漲家數佔比 %', reverse: false,
    blurb: '多頭廣度反映漲勢是否全面參與，而非只有少數龍頭撐盤。上漲家數佔比 > 60% 代表資金雨露均霑；< 40% 則是「指數漲、個股跌」的空頭假象。',
    bull: '小型股、內需、輪動題材',
  },
  {
    id: 'lipstick',  icon: 'lipstick',
    cn: '民生脈搏計', en: 'Consumer Pulse Index', origin: '食品/零售個股均漲跌 · 台股',
    unit: '內需消費股平均漲跌幅 %',  reverse: false,
    blurb: '食品、量販、便利商店等民生消費股的平均表現，反映庶民實質購買力與消費信心。這類股票波動小但方向穩，是景氣溫度的長期體溫計。',
    bull: '食品、超市、餐飲、零售',
  },
  {
    id: 'hemline',   icon: 'skirt',
    cn: '先鋒強勢表', en: 'Vanguard Strength Gauge', origin: '強勢股廣度 · 全市場',
    unit: '單日漲幅逾 1% 個股佔比 %', reverse: false,
    blurb: '強勢股廣度衡量市場中「真正在漲」的個股比例。比例高代表資金積極布局；比例低代表多頭動能集中在少數股，廣度不足，行情難以持久。',
    bull: '動能股、主流題材、高 beta 族群',
  },
  {
    id: 'bigmac',    icon: 'burger',
    cn: '台幣虛實錄', en: 'TWD Valuation Index', origin: 'USD/TWD 即時匯率 · 外匯市場',
    unit: 'USD/TWD 相對 PPP 偏離度', reverse: false,
    blurb: '以購買力平價（PPP）公允值約 25 元為基準，衡量新台幣相對美元的低估幅度。台幣愈被低估（匯率愈高），出口競爭力愈強，但進口成本與通膨壓力也愈重。',
    bull: '科技出口、電子、半導體',
  },
  {
    id: 'taxi',      icon: 'taxi',
    cn: '籌碼流動儀', en: 'Volume Activity Meter', origin: '台股整體成交量 · TWSE',
    unit: '市場成交量相對熱絡度（逆向）', reverse: true,
    blurb: '成交量是市場人氣的直接指標。量縮代表觀望氣氛濃、買賣雙方都在等訊號；量增代表籌碼積極換手、行情可能醞釀變化。本指標為逆向 — 量愈大，讀數愈低。',
    bull: '(逆向指標 — 讀數低代表市場活絡)',
  },
  {
    id: 'instant',   icon: 'noodle',
    cn: '殺盤寒暑表', en: 'Selloff Thermometer', origin: '重跌個股比例 · 全市場',
    unit: '跌幅逾 3% 個股佔全市場 %', reverse: true,
    blurb: '單日跌幅超過 3% 的個股佔比，直接衡量市場殺盤力道與恐慌程度。比例 > 10% 代表出現系統性賣壓；< 3% 代表跌勢局限少數個股，整體尚屬健康。',
    bull: '(逆向指標 — 讀數低代表殺盤少)',
  },
  {
    id: 'crane',     icon: 'crane',
    cn: '基建興衰錄', en: 'Infrastructure Pulse', origin: '營建/鋼鐵/水泥股均漲跌 · 台股',
    unit: '基建相關股平均漲跌幅 %', reverse: false,
    blurb: '台股營建、鋼鐵、水泥等基礎建設族群的平均表現，反映實體經濟投資動能。AI 資料中心、電網擴建、都市更新都是近年驅動本族群的主要催化劑。',
    bull: '營建、資產、鋼鐵、水泥、重電',
  },
  {
    id: 'gold-necklace', icon: 'gold',
    cn: '金鑑避險錄', en: 'Gold Haven Index', origin: '黃金期貨 GC=F · COMEX',
    unit: '黃金期貨單日漲跌幅 %', reverse: false,
    blurb: '紐約黃金期貨（COMEX GC=F）的即時漲跌幅，直接反映全球資金的避險情緒強度。金價大漲往往對應地緣風險升溫、通膨預期走高，或對美元信心動搖。',
    bull: '黃金 ETF、避險資產、礦業股',
  },
  {
    id: 'btc', icon: 'btc',
    cn: '幣圈脈搏儀', en: 'Crypto Pulse Meter', origin: 'BTC/USD · Coinbase/Yahoo Finance',
    unit: 'Bitcoin 單日漲跌幅 %', reverse: false,
    blurb: 'Bitcoin 作為全球流動性與風險偏好的先行指標。幣價大漲代表市場資金充裕、投機情緒旺盛；急跌往往是全球去槓桿的前兆，比傳統市場反應更快。',
    bull: '高 beta 科技股、遊戲、元宇宙',
  },
  {
    id: 'oil', icon: 'oil',
    cn: '黑金通膨表', en: 'Crude Oil Inflation Gauge', origin: 'WTI 原油期貨 CL=F · NYMEX',
    unit: 'WTI 原油單日漲跌幅 %', reverse: true,
    blurb: 'WTI 原油是通膨的火種。油價上漲推高運輸、製造、民生成本，央行被迫鷹派、升息預期升溫，對整體股市偏空；油價下跌則舒緩通膨壓力，有利市場流動性。本指標為逆向 — 油價愈漲，景氣壓力愈大。',
    bull: '(逆向指標 — 讀數低=油價低=通膨壓力小)',
  },
];

// ── Reading computation from real data ──────────────────
function clamp(v, lo=0, hi=100) { return Math.max(lo, Math.min(hi, Math.round(v))); }

function computeReadings(oddities, mkt) {
  const readings = {};

  // 五角大廈披薩 — VIX
  const vix = oddities?.vix?.price || 0;
  readings.pizza = vix
    ? clamp(vix >= 35 ? 95 : vix >= 28 ? 83 : vix >= 22 ? 68 : vix >= 18 ? 52 : vix >= 14 ? 36 : 22)
    : 50;

  // 鹹酥雞 — % stocks advancing
  const pctUp = mkt?.pctAdvancing || 50;
  readings.saltchicken = clamp(pctUp * 1.1);

  // 口紅 — consumer sector avg change
  const consumerChg = mkt?.consumerAvgChg || 0;
  readings.lipstick = clamp(50 + consumerChg * 8);

  // 裙襬 — % of stocks with chg > +1%
  const pctStrong = mkt?.pctStrongUp || 25;
  readings.hemline = clamp(pctStrong * 2.2);

  // 大麥克 — USD/TWD vs PPP ~25
  const rate = oddities?.usdtwd?.price || 32;
  const underval = ((rate - 25) / 25) * 100; // % undervaluation
  readings.bigmac = clamp(Math.max(0, 100 - underval * 2));

  // 小黃排班 — volume ratio (high vol = low reading = taxi busy)
  const volRatio = mkt?.volRatio || 1;
  readings.taxi = clamp(volRatio >= 1.5 ? 18 : volRatio >= 1.2 ? 30 : volRatio >= 0.9 ? 48 : volRatio >= 0.7 ? 65 : 80);

  // 泡麵 — % stocks down > 3%
  const pctDown3 = mkt?.pctBigDecline || 5;
  readings.instant = clamp(pctDown3 * 4.5);

  // 工地起重機 — construction/steel sector avg
  const craneChg = mkt?.constructionAvgChg || 0;
  readings.crane = clamp(50 + craneChg * 9);

  // 黃金大媽 — gold % change
  const goldChg = oddities?.gold?.chgPct || 0;
  readings['gold-necklace'] = clamp(50 + goldChg * 12);

  // BTC — daily % change
  const btcChg = oddities?.btc?.chgPct || 0;
  readings.btc = clamp(50 + btcChg * 5);

  // 原油 — WTI daily % change
  const oilChg = oddities?.oil?.chgPct || 0;
  readings.oil = clamp(50 + oilChg * 8);

  return readings;
}

function generateToday(id, readings, oddities, mkt) {
  const r = readings[id] ?? 50;
  const vix = oddities?.vix?.price?.toFixed(1) || '—';
  const goldPrice = oddities?.gold?.price ? '$' + Math.round(oddities.gold.price).toLocaleString('en-US') : '—';
  const goldChg = oddities?.gold?.chgPct?.toFixed(2) || '0';
  const rate = oddities?.usdtwd?.price?.toFixed(2) || '—';
  const pctUp = mkt?.pctAdvancing?.toFixed(0) || '—';
  const pctDown3 = mkt?.pctBigDecline?.toFixed(1) || '—';
  const volRatio = mkt?.volRatio?.toFixed(2) || '—';

  const todayMap = {
    pizza: vix !== '—'
      ? `VIX 恐慌指數今日報 ${vix}，${r >= 65 ? '市場情緒緊繃，地緣政治風險升溫，資安、國防族群留意。' : r >= 45 ? '恐慌程度溫和，市場尚在正常波動範圍內。' : '市場情緒平穩，投資人風險偏好良好。'}`
      : '目前無法取得 VIX 數據。',
    saltchicken: pctUp !== '—'
      ? `今日台股上漲家數佔全市場 ${pctUp}%，${r >= 65 ? '多頭氛圍旺盛，鹹酥雞攤前排隊的都是賺錢的人。' : r >= 45 ? '漲跌互現，鹹酥雞該不該多加起司需要考慮一下。' : '空頭壓力沉重，今晚鹹酥雞只配泡麵。'}`
      : '目前無法取得市場廣度數據。',
    lipstick: mkt
      ? `消費/零售相關股今日平均 ${(mkt.consumerAvgChg >= 0 ? '+' : '') + (mkt.consumerAvgChg?.toFixed(2) || '0')}%，${r >= 60 ? '內需消費族群強勢，平價奢侈品買氣可期。' : r >= 40 ? '消費股走勢平穩，口紅銷售不溫不火。' : '消費類股偏弱，省錢之道當道。'}`
      : '目前無法取得消費股數據。',
    hemline: pctUp !== '—'
      ? `市場漲幅逾 1% 個股佔比 ${mkt?.pctStrongUp?.toFixed(0) || '—'}%，${r >= 65 ? '強勢股遍地，裙襬正在悄悄升高。' : r >= 45 ? '強勢股比例適中，裙長維持在膝蓋附近。' : '強勢股稀少，長裙季節到來。'}`
      : '目前無法取得市場廣度數據。',
    bigmac: rate !== '—'
      ? `USD/TWD 今日報 ${rate} 元，PPP 公允值約 25 元，新台幣隱含低估約 ${((parseFloat(rate)-25)/25*100).toFixed(0)}%，${r <= 40 ? '低估幅度大，出口企業換匯有利，科技、電子族群受惠。' : r <= 60 ? '匯率適中，購買力平價偏差在正常範圍。' : '台幣偏強，進口族群相對有利。'}`
      : '目前無法取得匯率數據。',
    taxi: volRatio !== '—'
      ? `今日成交量比率 ${volRatio}（相對近期均量），${r <= 35 ? '市場量能充沛，小黃司機根本沒空排班，商業活動熱絡。' : r <= 55 ? '成交量適中，市場活動度一般。' : '量能偏低，市場清淡，小黃排班隊伍又拉長了。'}`
      : '目前無法取得成交量數據。',
    instant: pctDown3 !== '—'
      ? `今日重跌（＞3%）個股佔全市場 ${pctDown3}%，${r >= 65 ? '殺盤兇猛，超商泡麵貨架今晚將被掃空。' : r >= 40 ? '部分個股跌深，泡麵銷量小幅上升。' : '大跌個股稀少，外食族荷包尚安全。'}`
      : '目前無法取得個股跌幅數據。',
    crane: mkt
      ? `營建/鋼鐵/水泥相關股今日平均 ${(mkt.constructionAvgChg >= 0 ? '+' : '') + (mkt.constructionAvgChg?.toFixed(2) || '0')}%，${r >= 65 ? '基建族群強勁，天際線塔吊數量持續攀升。' : r >= 45 ? '營建族群走勢平穩，基建資金維持中性。' : '基建相關股偏弱，塔吊活動降溫。'}`
      : '目前無法取得營建股數據。',
    'gold-necklace': goldPrice !== '—'
      ? `黃金期貨今報 ${goldPrice}，日漲跌 ${goldChg >= 0 ? '+' : ''}${goldChg}%，${r >= 70 ? '金價大漲，銀樓門口婆媽大排長龍，避險情緒全面升溫。' : r >= 50 ? '金價小漲，避險需求溫和上升。' : '金價下跌，婆媽按兵不動，市場風險偏好回升。'}`
      : '目前無法取得黃金數據。',
    btc: (function() {
      var btcPrice = oddities?.btc?.price;
      var bc = oddities?.btc?.chgPct?.toFixed(2) || '0';
      if (!btcPrice) return '目前無法取得 BTC 數據。';
      var bp = '$' + Math.round(btcPrice).toLocaleString('en-US');
      return `Bitcoin 今報 ${bp}，日漲跌 ${bc >= 0 ? '+' : ''}${bc}%，${r >= 70 ? '幣圈狂歡，風險偏好滿載，市場資金動能充沛，高 beta 族群跟漲機率上升。' : r >= 50 ? 'BTC 小漲，加密市場情緒中性偏多。' : 'BTC 走弱，市場去槓桿疑慮升溫，注意流動性風險。'}`;
    })(),
    oil: (function() {
      var oilPrice = oddities?.oil?.price;
      var oc = oddities?.oil?.chgPct?.toFixed(2) || '0';
      if (!oilPrice) return '目前無法取得原油數據。';
      var op = '$' + oilPrice.toFixed(2);
      return `WTI 原油今報 ${op}，日漲跌 ${oc >= 0 ? '+' : ''}${oc}%，${r >= 70 ? '油價大漲，通膨壓力升溫，央行鷹派預期增強，製造業與航空成本吃緊——對市場整體偏空。' : r >= 50 ? '油價小漲，通膨壓力溫和，市場尚可承受。' : '油價走跌，通膨壓力舒緩，航空、塑化、製造業成本減輕，有利市場流動性。'}`;
    })(),
  };
  return todayMap[id] || '數據載入中…';
}

function levelOf(r) {
  if (r >= 75) return { tag: '熱', en: 'Hot',     color: '#9c2b2b' };
  if (r >= 55) return { tag: '溫', en: 'Warm',    color: '#b8924d' };
  if (r >= 40) return { tag: '平', en: 'Neutral', color: '#6a6a6a' };
  return             { tag: '冷', en: 'Cold',    color: '#2a6b8e' };
}

const ODD_ICONS = {
  pizza: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M4 8 L20 8 L12 22 Z"/><path d="M4 8 Q12 4 20 8"/><circle cx="10" cy="11" r="1.1" fill="currentColor"/><circle cx="14" cy="12" r="1.1" fill="currentColor"/><circle cx="12" cy="16" r="1.1" fill="currentColor"/></g>),
  chicken: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M6 10 Q4 6 8 5 Q10 3 13 5 Q18 5 18 10 Q20 13 17 16 L8 17 Q4 14 6 10 Z"/><circle cx="9" cy="9" r="1" fill="currentColor"/><circle cx="13" cy="8" r="1" fill="currentColor"/><path d="M10 17 L9 21 M14 17 L15 21"/></g>),
  lipstick: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M9 11 L9 4 Q9 3 11 3 Q13 3 13 5 L13 11 Z"/><rect x="8" y="11" width="6" height="9"/><line x1="8" y1="15" x2="14" y2="15"/></g>),
  skirt: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M8 5 L16 5 L20 19 Q12 22 4 19 Z"/><line x1="8" y1="5" x2="9" y2="11"/><line x1="16" y1="5" x2="15" y2="11"/></g>),
  burger: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M4 9 Q12 3 20 9 Z"/><line x1="5" y1="13" x2="19" y2="13"/><path d="M4 16 Q12 20 20 16 L20 17 Q12 21 4 17 Z"/></g>),
  taxi: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M3 16 L4 11 Q5 9 8 9 L16 9 Q19 9 20 11 L21 16 Z"/><rect x="9" y="5" width="6" height="4"/><circle cx="7" cy="17" r="1.6"/><circle cx="17" cy="17" r="1.6"/></g>),
  noodle: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M5 10 Q12 7 19 10 L17 19 Q12 21 7 19 Z"/><path d="M8 10 Q9 6 12 7 M12 10 Q13 5 16 7"/><line x1="14" y1="4" x2="19" y2="8"/></g>),
  crane: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><line x1="7" y1="21" x2="7" y2="5"/><path d="M7 5 L20 5"/><path d="M7 8 L11 5"/><line x1="16" y1="5" x2="16" y2="10"/><path d="M4 21 L10 21"/></g>),
  gold: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M5 16 L8 10 L12 14 L16 10 L19 16 Z"/><circle cx="12" cy="6" r="2"/></g>),
  btc: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M8 4 L8 20 M12 4 L12 20"/><path d="M8 4 L15 4 Q18 4 18 7.5 Q18 11 12 11"/><path d="M8 11 L16 11 Q19 11 19 14.5 Q19 20 12 20 L8 20"/><line x1="6" y1="4" x2="8" y2="4"/><line x1="6" y1="20" x2="8" y2="20"/></g>),
  oil: (<g fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"><path d="M8 20 L8 10 Q8 7 10 6 L14 4"/><path d="M14 4 Q16 3 17 5 L19 10 Q20 14 18 17 Q16 20 13 20 Z"/><path d="M8 14 Q11 12 13 14"/><circle cx="15" cy="11" r="1" fill="currentColor" opacity="0.5"/></g>),
};

function Gauge({ reading, color, size = 150 }) {
  const cx = size / 2, cy = size * 0.62, r = size * 0.42;
  const startA = Math.PI, endA = 0;
  const valA = startA - (reading / 100) * Math.PI;
  const pt = (a, rad = r) => [cx + Math.cos(a) * rad, cy - Math.sin(a) * rad];
  const [sx, sy] = pt(startA), [ex, ey] = pt(endA), [nx, ny] = pt(valA, r - 6);
  const ticks = [0, 25, 50, 75, 100].map(t => {
    const a = startA - (t / 100) * Math.PI;
    const [x1, y1] = pt(a, r + 2), [x2, y2] = pt(a, r + 8);
    return { x1, y1, x2, y2 };
  });
  return (
    <svg viewBox={`0 0 ${size} ${size * 0.72}`} style={{ width: size, height: size * 0.72 }}>
      <path d={`M ${sx} ${sy} A ${r} ${r} 0 0 1 ${ex} ${ey}`}
            fill="none" stroke="var(--paper-line)" strokeWidth="7" strokeLinecap="round"/>
      <path d={`M ${sx} ${sy} A ${r} ${r} 0 0 1 ${nx} ${ny}`}
            fill="none" stroke={color} strokeWidth="7" strokeLinecap="round"/>
      {ticks.map((t, i) => (
        <line key={i} x1={t.x1} y1={t.y1} x2={t.x2} y2={t.y2} stroke="var(--ink-mute)" strokeWidth="1"/>
      ))}
      <line x1={cx} y1={cy} x2={nx} y2={ny} stroke="var(--ink)" strokeWidth="1.8" strokeLinecap="round"/>
      <circle cx={cx} cy={cy} r="4" fill="var(--ink)"/>
      <text x={cx} y={cy - 16} textAnchor="middle"
            fontFamily="Times New Roman" fontSize={size * 0.2} fontWeight="700" fill="var(--ink)">
        {reading}
      </text>
    </svg>
  );
}

// ── App ─────────────────────────────────────────────────
function App() {
  const [oddities, setOddities] = useState(null);
  const [mkt, setMkt]           = useState(null);
  const [loadingData, setLoadingData] = useState(true);
  const [dataError, setDataError]     = useState('');
  const [aiReading, setAiReading]     = useState(null);
  const [aiLoading, setAiLoading]     = useState(false);
  const [updatedAt, setUpdatedAt]     = useState('');

  useEffect(function() {
    setLoadingData(true);
    Promise.allSettled([
      fetch('/api/stock?type=oddities').then(function(r) { return r.json(); }),
      fetch('/api/stock?type=list&market=all').then(function(r) { return r.json(); }),
    ]).then(function(results) {
      // Oddities (VIX, Gold, USD/TWD)
      if (results[0].status === 'fulfilled' && results[0].value && !results[0].value.error) {
        var od = results[0].value;
        // null = individual symbol failed, keep partial data
        setOddities(od);
        if (!od.vix && !od.gold && !od.usdtwd) setDataError('外部數據暫時無法取得');
      } else {
        setDataError('外部數據暫時無法取得，指標將使用估算值');
      }

      // TWSE list → market breadth metrics
      if (results[1].status === 'fulfilled' && Array.isArray(results[1].value)) {
        var list = results[1].value;
        var valid = list.filter(function(s) {
          return parseFloat(s.ClosingPrice) > 0 && s.Change !== undefined;
        });
        var total = valid.length || 1;

        // chg% for each stock
        var chgs = valid.map(function(s) {
          var close  = parseFloat(s.ClosingPrice) || 0;
          var change = parseFloat(s.Change) || 0;
          var prev   = close - change;
          return prev > 0 ? (change / prev) * 100 : 0;
        });

        var advancing   = chgs.filter(function(c) { return c > 0; }).length;
        var strongUp    = chgs.filter(function(c) { return c >= 1; }).length;
        var bigDecline  = chgs.filter(function(c) { return c <= -3; }).length;
        var avgChg      = chgs.reduce(function(a, b) { return a + b; }, 0) / total;
        var totalVol    = valid.reduce(function(s, s2) { return s + (parseInt(s2.TradeVolume) || 0); }, 0);

        // Consumer stocks (food, retail keywords)
        var consumerChgs = valid.filter(function(s) {
          return /食品|零售|超市|量販|便利|統一|全家|7-|菜|飲|糖|米/.test(s.Name || '');
        }).map(function(s) {
          var close = parseFloat(s.ClosingPrice)||0, change = parseFloat(s.Change)||0, prev = close-change;
          return prev > 0 ? (change/prev)*100 : 0;
        });

        // Construction stocks
        var constructionChgs = valid.filter(function(s) {
          return /營建|水泥|鋼鐵|建設|建材|預拌/.test(s.Name || '');
        }).map(function(s) {
          var close = parseFloat(s.ClosingPrice)||0, change = parseFloat(s.Change)||0, prev = close-change;
          return prev > 0 ? (change/prev)*100 : 0;
        });

        var avgVol = totalVol / total;
        // volRatio: today's avg volume vs a simple baseline (can't compute true avg without history)
        // Use absolute level as proxy: > 500萬 shares = high, < 100萬 = low
        var volRatio = avgVol > 5000000 ? 1.6 : avgVol > 2000000 ? 1.2 : avgVol > 800000 ? 0.9 : 0.6;

        setMkt({
          pctAdvancing:       (advancing / total) * 100,
          pctStrongUp:        (strongUp / total) * 100,
          pctBigDecline:      (bigDecline / total) * 100,
          avgChg:             avgChg,
          volRatio:           volRatio,
          consumerAvgChg:     consumerChgs.length ? consumerChgs.reduce(function(a,b){return a+b;},0)/consumerChgs.length : avgChg,
          constructionAvgChg: constructionChgs.length ? constructionChgs.reduce(function(a,b){return a+b;},0)/constructionChgs.length : avgChg,
        });
      }

      setUpdatedAt(new Date().toLocaleTimeString('en-US', { hour12: false }));
      setLoadingData(false);
    });
  }, []);

  var readings = useMemo(function() {
    return computeReadings(oddities, mkt);
  }, [oddities, mkt]);

  var oracles = useMemo(function() {
    return ORACLE_DEFS.map(function(def) {
      var r = readings[def.id] ?? 50;
      return {
        ...def,
        reading: r,
        today: generateToday(def.id, readings, oddities, mkt),
        needle: levelOf(r).tag + (r >= 55 ? '↑' : r <= 45 ? '↓' : '→'),
      };
    });
  }, [readings, oddities, mkt]);

  var composite = useMemo(function() {
    var adj = oracles.map(function(o) {
      return o.reverse ? 100 - o.reading : o.reading;
    });
    return Math.round(adj.reduce(function(a, b) { return a + b; }, 0) / adj.length);
  }, [oracles]);

  var compLevel = composite >= 65 ? { tag:'熱絡', en:'Buoyant', color:'#9c2b2b' }
                : composite >= 50 ? { tag:'回溫', en:'Warming', color:'#b8924d' }
                : composite >= 40 ? { tag:'中性', en:'Neutral', color:'#6a6a6a' }
                                  : { tag:'清淡', en:'Subdued', color:'#2a6b8e' };

  const askOracle = async () => {
    setAiLoading(true); setAiReading(null);
    try {
      var list = oracles.map(function(o) { return o.cn + ': ' + o.reading + '/100 (' + o.today + ')'; }).join('\n');
      var prompt = '你是 Formosa & Co. 的「民俗經濟觀測家」,風格詼諧但有見地。\n以下是今日的另類經濟指標讀數(皆為真實數據,滿分 100):\n' + list + '\n庶民景氣綜合指數:' + composite + '/100。\n請用繁體中文寫一段 120 字以內的「今日民俗盤勢解讀」,語氣像老練又幽默的專欄作家,把這些趣味指標串成一個對台股氛圍的判斷,最後給一句俏皮的結語。不要用 markdown,直接寫散文。';
      var reply = await window.claudeComplete({ messages: [{ role:'user', content: prompt }] });
      setAiReading(reply);
    } catch (e) {
      setAiReading('(觀測家今日休診:' + (e?.message || e) + ')');
    } finally {
      setAiLoading(false);
    }
  };

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

      <section className="cover" style={{ paddingTop: 32, marginBottom: 18 }}>
        <div>
          <div className="eyebrow">Almanac of Market Oddities · 民俗指標通書</div>
          <h1>
            <span style={{ fontStyle:'italic' }}>The</span> Oddities <span className="amp">&amp;</span> Omens
            <span className="kai-title">另 類 指 標 通 書</span>
          </h1>
          <p className="cover-lede">
            VIX、黃金、匯率、漲跌家數、成交量——這些<b style={{color:'var(--ink)'}}>真實數據</b>化身民間玄學指標,
            半認真、半趣味。每次開啟頁面自動抓取最新數據。
          </p>
          {loadingData && (
            <div style={{ marginTop:8, fontSize:12, color:'var(--ink-mute)', fontStyle:'italic' }}>
              正在抓取 VIX、黃金、匯率、台股廣度…
            </div>
          )}
          {!loadingData && updatedAt && (
            <div style={{ marginTop:8, fontSize:12, color:'var(--ink-mute)', fontStyle:'italic' }}>
              數據已更新 · {updatedAt}
              {dataError && <span style={{ color:'var(--bear)', marginLeft:10 }}>{dataError}</span>}
            </div>
          )}
        </div>
        <div className="cover-side">
          <h3>People's Index <span className="kai" style={{fontSize:13, color:'var(--ink-mute)', letterSpacing:'.18em'}}>· 庶民景氣</span></h3>
          <div className="composite-box">
            <Gauge reading={composite} color={compLevel.color} size={180}/>
            <div className="composite-verdict">
              <span className="cv-tag kai" style={{ color: compLevel.color, borderColor: compLevel.color }}>
                {compLevel.tag}
              </span>
              <span className="cv-en italic-en">{compLevel.en}</span>
            </div>
            <div className="composite-note kai">
              {loadingData ? '計算中…' : '綜合 11 項指標（真實數據）'}
            </div>
          </div>
        </div>
      </section>

      {/* AI 觀測家解讀 */}
      <div className="oracle-ai">
        <div className="oracle-ai-l">
          <div className="oracle-ai-title">
            <span className="ornament" style={{ color:'var(--gold)' }}>❦</span>
            <span className="kai">今日民俗盤勢</span>
            <span className="italic-en" style={{ marginLeft: 10, color:'var(--ink-mute)', fontSize: 14 }}>Folk Reading of the Day</span>
          </div>
          {!aiReading && !aiLoading && (
            <div className="oracle-ai-sub kai">讓民俗經濟觀測家綜合今日 9 項真實數據，給一段詼諧的盤勢解讀。</div>
          )}
          {aiLoading && (
            <div className="oracle-ai-sub kai">觀測家正在掐指一算<span className="dots"><span>.</span><span>.</span><span>.</span></span></div>
          )}
          {aiReading && (
            <div className="oracle-ai-text kai">
              <span className="oai-quote">"</span>{aiReading}
            </div>
          )}
        </div>
        <button className="oracle-ai-btn" onClick={askOracle} disabled={aiLoading || loadingData}>
          {aiLoading ? '掐指中…' : aiReading ? <span className="kai">再算一次</span> : <><span className="kai">掐指一算</span><span style={{marginLeft:6, fontStyle:'italic'}}>→</span></>}
        </button>
      </div>

      <window.SectionHead
        kicker="The Indicators"
        title="指標一覽"
        titleEn="Live Oddities"
        sub={'11 live indicators · ' + (loadingData ? '數據載入中…' : '數據已更新 ' + updatedAt)}
      />

      <div className="oracle-grid">
        {oracles.map(function(o) {
          var lv = levelOf(o.reading);
          return (
            <article key={o.id} className="oracle-card">
              <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
              <div className="oracle-card-head">
                <div className="oracle-icon">
                  <svg viewBox="0 0 24 24" width="34" height="34" style={{ color:'var(--ink)' }}>
                    {ODD_ICONS[o.icon]}
                  </svg>
                </div>
                <div className="oracle-titles">
                  <div className="oracle-cn kai">{o.cn}</div>
                  <div className="oracle-en italic-en">{o.en}</div>
                  <div className="oracle-origin">{o.origin}</div>
                </div>
                <div className="oracle-level kai" style={{ color: lv.color, borderColor: lv.color }}>
                  {lv.tag}
                </div>
              </div>

              <div className="oracle-gauge-row">
                <Gauge reading={o.reading} color={lv.color} size={130}/>
                <div className="oracle-gauge-meta">
                  <div className="ogm-unit italic-en">{o.unit}</div>
                  <div className="ogm-needle kai" style={{ color: lv.color }}>{o.needle}</div>
                  {o.reverse && <div className="ogm-reverse kai">⟲ 逆向指標</div>}
                </div>
              </div>

              <div className="oracle-blurb kai">{o.blurb}</div>

              <div className="oracle-today">
                <div className="ot-label">
                  <span style={{ fontStyle:'italic' }}>Today</span>
                  <span className="kai" style={{ marginLeft:6 }}>今日數據</span>
                </div>
                <div className="ot-text kai">
                  {loadingData ? '數據載入中…' : o.today}
                </div>
              </div>

              <div className="oracle-foot">
                <span className="of-label kai">受惠 ·</span>
                <span className="of-sectors kai">{o.bull}</span>
              </div>
            </article>
          );
        })}
      </div>

      <div className="oracle-disclaimer">
        <span className="kai">編者鄭重聲明 · </span>
        本通書讀數雖來自 VIX、黃金期貨、台股廣度等真實數據，但指標詮釋純屬趣味類比，<b>絕非投資建議</b>。
        披薩吃多會胖，鹹酥雞要少炸，看盤之餘，身體要顧。
      </div>

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

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