// Live market data — fetching, label helpers, and LiveDot indicator
const { useState, useEffect } = React;

const FINNHUB_TOKEN = 'd7kgjh9r01qiqbcua9m0d7kgjh9r01qiqbcua9mg';

function _tzParts(timeZone) {
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', hour12: false,
  }).formatToParts(new Date());
  const get = type => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);
  const hour = get('hour') % 24;
  const date = new Date(Date.UTC(get('year'), get('month') - 1, get('day')));
  return { mins: hour * 60 + get('minute'), dow: date.getUTCDay(), date };
}

function usMarketLabel(isOpen) {
  const { mins, dow, date } = _tzParts('America/New_York');
  const isWeekday = dow >= 1 && dow <= 5;
  // prefer API result (handles holidays); fall back to time-based
  const open = isOpen !== undefined ? isOpen : (isWeekday && mins >= 570 && mins < 960);
  const last = new Date(date);
  if (!open && isWeekday && mins < 570) last.setUTCDate(last.getUTCDate() - 1);
  if (last.getUTCDay() === 6) last.setUTCDate(last.getUTCDate() - 1);
  if (last.getUTCDay() === 0) last.setUTCDate(last.getUTCDate() - 2);
  const m = last.getUTCMonth() + 1, d = last.getUTCDate();
  return open
    ? { zh: `美股(${m}/${d} 盘中)`, en: `US Live ${m}/${d}` }
    : { zh: `美股(${m}/${d} 收盘)`, en: `US Close ${m}/${d}` };
}

function euMarketLabel() {
  const { mins, dow, date } = _tzParts('Europe/Paris');
  const isWeekday = dow >= 1 && dow <= 5;
  const open = isWeekday && mins >= 540 && mins < 1050;
  const last = new Date(date);
  if (!open && isWeekday && mins < 540) last.setUTCDate(last.getUTCDate() - 1);
  if (last.getUTCDay() === 6) last.setUTCDate(last.getUTCDate() - 1);
  if (last.getUTCDay() === 0) last.setUTCDate(last.getUTCDate() - 2);
  const m = last.getUTCMonth() + 1, d = last.getUTCDate();
  return open
    ? { zh: `欧股(${m}/${d} 盘中)`, en: `EU Live ${m}/${d}` }
    : { zh: `欧股(${m}/${d} 收盘)`, en: `EU Close ${m}/${d}` };
}

const LiveDot = ({ live }) => (
  <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
    <span style={{
      width: 6, height: 6, borderRadius: '50%',
      background: live ? '#1C6B40' : '#9A8F85',
      display: 'inline-block',
    }} />
    <span style={{
      fontFamily: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace',
      fontSize: 10, letterSpacing: 1.4, textTransform: 'uppercase',
      color: live ? '#1C6B40' : '#9A8F85',
    }}>
      {live ? 'Live' : 'Cached'}
    </span>
  </div>
);

// Returns the last two complete weekly bars for a set of kline text responses.
// bar format: [date, open, close, high, low, volume]
// date is the last trading day of that week.
function parseLastTwoWeekBars(text, sym) {
  try {
    const j = JSON.parse(text.replace(/^[^=]+=/, ''));
    const bars = j.data[sym]?.week;
    if (!bars?.length) return null;
    // Find this week's Monday in CST (UTC+8)
    const cst = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
    const monday = new Date(cst);
    monday.setDate(cst.getDate() - (cst.getDay() === 0 ? 6 : cst.getDay() - 1));
    monday.setHours(0, 0, 0, 0);
    // Last complete week = last bar whose end date < this week's Monday
    let lastIdx = -1;
    for (let i = bars.length - 1; i >= 1; i--) {
      if (new Date(bars[i][0]) < monday) { lastIdx = i; break; }
    }
    if (lastIdx < 1) return null;
    return { last: bars[lastIdx], prev: bars[lastIdx - 1] };
  } catch { return null; }
}

function useLiveMarketData(d) {
  const [liveOverseas, setLiveOverseas] = useState({});
  const [liveIndices, setLiveIndices] = useState(null);
  const [liveWeekSummary, setLiveWeekSummary] = useState(null);
  const [liveWeekSummaryLabel, setLiveWeekSummaryLabel] = useState(null);

  useEffect(() => {
    if (!FINNHUB_TOKEN) return;

    // Finnhub ETF proxies for US + Europe (free tier; identical % moves to actual indices)
    const usDefs = [
      { name: '道指',    sym: 'DIA' },           // DIA ≈ ^DJI
      { name: '纳指',    sym: 'QQQ' },           // QQQ ≈ ^IXIC (Nasdaq-100)
      { name: '标普500', sym: 'SPY' },           // SPY ≈ ^GSPC
      { name: 'NVDA',   sym: 'NVDA', showPrice: true },
    ];
    const euDefs = [
      { name: 'DAX',     sym: 'EWG' },  // EWG ≈ ^GDAXI
      { name: 'CAC 40',  sym: 'EWQ' },  // EWQ ≈ ^FCHI
      { name: '富时100', sym: 'EWU' },  // EWU ≈ ^FTSE
    ];
    const quotesP = Promise.all(
      [...usDefs, ...euDefs].map(({ sym }) =>
        fetch(`https://finnhub.io/api/v1/quote?symbol=${encodeURIComponent(sym)}&token=${FINNHUB_TOKEN}`)
          .then(r => r.json()).catch(() => ({}))
      )
    );

    const statusP = fetch(`https://finnhub.io/api/v1/stock/market-status?exchange=US&token=${FINNHUB_TOKEN}`)
      .then(r => r.json()).catch(() => null);

    // Tencent Finance — real Chinese/HK index data, CORS: Access-Control-Allow-Origin: *
    const tencentP = fetch('https://qt.gtimg.cn/q=sh000001,sz399001,sz399006,hkHSI')
      .then(r => r.text()).catch(() => null);

    // Tencent weekly kline — for 上周累计; always relative to current date
    const today = new Date().toLocaleString('en-CA', { timeZone: 'Asia/Shanghai' }).slice(0, 10);
    const weeklySyms = [
      { sym: 'sh000001', name: '上证' },
      { sym: 'sz399001', name: '深证' },
      { sym: 'sz399006', name: '创业板' },
    ];
    const weeklyP = Promise.all(
      weeklySyms.map(({ sym }) =>
        fetch(`https://web.ifzq.gtimg.cn/appstock/app/kline/kline?_var=kline_week&param=${sym},week,2026-01-01,${today},12`)
          .then(r => r.text()).catch(() => null)
      )
    );

    Promise.all([quotesP, statusP, tencentP, weeklyP]).then(([results, status, tencentText, weeklyTexts]) => {
      const toEntry = (data, def) => {
        if (data.dp == null || data.c === 0) return null;
        const up = data.dp >= 0;
        const entry = { name: def.name, change: `${up ? '+' : ''}${data.dp.toFixed(2)}%`, up };
        if (def.showPrice && data.c) {
          entry.value = data.c.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
        }
        return entry;
      };
      const usEntries = results.slice(0, usDefs.length).map((r, i) => toEntry(r, usDefs[i])).filter(Boolean);
      const euEntries = results.slice(usDefs.length).map((r, i) => toEntry(r, euDefs[i])).filter(Boolean);

      const patch = {};
      if (usEntries.length === usDefs.length) patch.usPrev   = usEntries;
      if (euEntries.length === euDefs.length) patch.europe   = euEntries;
      if (status?.isOpen !== undefined)       patch.usMarketOpen = status.isOpen;

      if (Object.keys(patch).length) setLiveOverseas(patch);

      // Tencent Finance: parse `v_CODE="f0~f1~f2~price~prev~..."` per line
      if (tencentText) {
        const byCode = {};
        tencentText.trim().split('\n').forEach(line => {
          const inner = line.split('"')[1];
          if (!inner) return;
          const f = inner.split('~');
          const price = parseFloat(f[3]), prev = parseFloat(f[4]);
          if (!isNaN(price) && !isNaN(prev) && prev !== 0) byCode[f[2]] = { price, prev };
        });
        const codeMap = { SSE: '000001', SZI: '399001', GEM: '399006', HSI: 'HSI' };
        if (Object.keys(byCode).length) {
          const updatedIndices = d.indices.map(idx => {
            const q = byCode[codeMap[idx.code]];
            if (!q) return idx;
            const pct = (q.price - q.prev) / q.prev * 100;
            const up = pct >= 0;
            return {
              ...idx,
              value: q.price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
              change: `${up ? '+' : ''}${pct.toFixed(2)}%`,
              up,
            };
          });
          setLiveIndices(updatedIndices);
        }
      }
      // Weekly kline: compute last complete week's cumulative change
      const weekPairs = weeklySyms.map(({ sym }, i) =>
        weeklyTexts[i] ? parseLastTwoWeekBars(weeklyTexts[i], sym) : null
      );
      if (weekPairs.every(Boolean)) {
        const summary = weekPairs.map(({ last, prev }, i) => {
          const lastClose = parseFloat(last[2]), prevClose = parseFloat(prev[2]);
          const pct = (lastClose - prevClose) / prevClose * 100;
          const up = pct >= 0;
          const fmt2 = n => n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
          return {
            name: weeklySyms[i].name,
            change: `${up ? '+' : ''}${pct.toFixed(2)}%`,
            up,
            startVal: fmt2(prevClose),
            endVal: fmt2(lastClose),
          };
        });
        setLiveWeekSummary(summary);

        // Build label from last complete week's date range
        const [ey, em, ed] = weekPairs[0].last[0].split('-').map(Number);
        const endDate = new Date(ey, em - 1, ed);
        const startDate = new Date(ey, em - 1, ed);
        startDate.setDate(ed - (endDate.getDay() === 0 ? 6 : endDate.getDay() - 1));
        const fmt = dt => `${dt.getMonth() + 1}/${dt.getDate()}`;
        setLiveWeekSummaryLabel(`上周 (${fmt(startDate)}–${fmt(endDate)}) 累计`);
      }
    }).catch(() => {});
  }, []);

  return {
    indices:             liveIndices || d.indices,
    overseas:            { ...d.overseas, ...liveOverseas },
    liveIndices:         !!liveIndices,
    liveOverseas,
    weekSummary:         liveWeekSummary || d.weekSummary,
    weekSummaryLabel:    liveWeekSummaryLabel || d.weekSummaryLabel,
    liveWeekSummary:     !!liveWeekSummary,
  };
}

function cnMarketSession() {
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone: 'Asia/Shanghai',
    weekday: 'short', hour: '2-digit', minute: '2-digit', hour12: false,
  }).formatToParts(new Date());
  const get = type => parts.find(p => p.type === type)?.value ?? '';
  const weekday = get('weekday');
  const mins = (parseInt(get('hour'), 10) % 24) * 60 + parseInt(get('minute'), 10);
  if (weekday === 'Sat' || weekday === 'Sun') return '休市';
  if (mins < 9*60+30)  return '盘前';
  if (mins < 11*60+30) return '早盘';
  if (mins < 13*60)    return '午休';
  if (mins < 15*60)    return '午盘';
  return '盘后';
}

window.usMarketLabel    = usMarketLabel;
window.euMarketLabel    = euMarketLabel;
window.cnMarketSession  = cnMarketSession;
window.LiveDot          = LiveDot;
window.useLiveMarketData = useLiveMarketData;
