/* 觀察清單資料引擎 — 整合 FinMind 日 K + KCStructure + Intraday quote
 *
 * window.WatchlistEngine = {
 *   resolveRows(syms, options?) → Promise<Row[]>
 *   mergeQuotes(rows, quotes)    → Row[]
 *   subscribe(syms, callback)    → unsubscribe()
 * }
 *
 * Row schema 對齊 mock 的 WATCHLIST：
 *   { sym, name, market, price, chg, pct, vol, volMa, rsi, ma, ts, spark, alert, news, _loading?, _error? }
 *   ma：Bullish / Sideways / Bearish
 */

function calcRSI(candles, period = 14) {
  if (!candles || candles.length < period + 1) return null;
  let gains = 0, losses = 0;
  for (let i = 1; i <= period; i++) {
    const d = candles[i].c - candles[i - 1].c;
    if (d >= 0) gains += d; else losses -= d;
  }
  let avgG = gains / period;
  let avgL = losses / period;
  for (let i = period + 1; i < candles.length; i++) {
    const d = candles[i].c - candles[i - 1].c;
    if (d >= 0) {
      avgG = (avgG * (period - 1) + d) / period;
      avgL = (avgL * (period - 1)) / period;
    } else {
      avgG = (avgG * (period - 1)) / period;
      avgL = (avgL * (period - 1) + (-d)) / period;
    }
  }
  if (avgL === 0) return 100;
  const rs = avgG / avgL;
  return 100 - 100 / (1 + rs);
}

function lookupMockMeta(sym) {
  const list = (window.MOCK && window.MOCK.WATCHLIST) || [];
  return list.find((r) => r.sym === sym) || null;
}

function symMarket(sym) {
  return sym.endsWith(".TW") ? "TW" : "US";
}

function symName(sym, nameMap) {
  const m = lookupMockMeta(sym);
  if (m) return m.name;
  if (nameMap && nameMap.get(sym)) return nameMap.get(sym);
  return sym.replace(/\.TW$/, "");
}

function findLastTrend(events) {
  if (!Array.isArray(events)) return null;
  for (let i = events.length - 1; i >= 0; i--) {
    if (events[i].type === "trend") return events[i].label;
  }
  return null;
}

function trendToMa(label) {
  if (label === "多頭") return "Bullish";
  if (label === "空頭") return "Bearish";
  if (label === "盤整") return "Sideways";
  return "Sideways";
}

async function resolveOne(sym, nameMap) {
  const mock = lookupMockMeta(sym);
  const market = symMarket(sym);
  const baseRow = {
    sym,
    name: symName(sym, nameMap),
    market,
    alert: mock ? mock.alert : "off",
    news: mock ? mock.news : 0,
    _loading: false,
    _error: null,
  };

  if (!window.FinMind) {
    return { ...baseRow, _error: "FinMind 未載入", price: 0, chg: 0, pct: 0, vol: 0, volMa: 1, rsi: 50, ma: "Sideways", ts: "", spark: [] };
  }

  let candles;
  try {
    candles = await window.FinMind.getCandles(sym);
  } catch (e) {
    return { ...baseRow, _error: e.message, price: mock?.price || 0, chg: 0, pct: 0, vol: 0, volMa: 1, rsi: 50, ma: "Sideways", ts: "", spark: mock?.spark || [] };
  }
  if (!candles || candles.length < 25) {
    return { ...baseRow, _error: "資料不足", price: mock?.price || 0, chg: 0, pct: 0, vol: 0, volMa: 1, rsi: 50, ma: "Sideways", ts: "", spark: mock?.spark || [] };
  }

  const last = candles[candles.length - 1];
  const prev = candles[candles.length - 2];
  const chg = last.c - prev.c;
  const pct = prev.c > 0 ? (chg / prev.c) * 100 : 0;

  /* volMa（當日量 / 20 日平均量）*/
  const volWin = candles.slice(-20);
  const volSum = volWin.reduce((a, c) => a + c.v, 0);
  const volMa20 = volSum / volWin.length;
  const volMa = volMa20 > 0 ? last.v / volMa20 : 1;

  const rsi = calcRSI(candles, 14) ?? 50;

  /* 趨勢：跑 KCStructure 取最後 trend label */
  let ma = "Sideways";
  if (window.KCStructure) {
    try {
      const events = window.KCStructure.compute(candles);
      const trend = findLastTrend(events);
      if (trend) ma = trendToMa(trend);
    } catch (e) {
      /* 留 Sideways */
    }
  }

  const spark = candles.slice(-20).map((c) => c.c);

  return {
    ...baseRow,
    price: last.c,
    chg,
    pct,
    vol: last.v,
    volMa,
    rsi,
    ma,
    ts: last.date.toISOString().slice(0, 10),
    spark,
  };
}

/* 批次解析 — 並發抓 FinMind，個別失敗不影響其他
 * 先載入一次台股 universe 當 name lookup（localStorage 24h 快取） */
async function resolveRows(syms) {
  let nameMap = null;
  if (syms.some((s) => s.endsWith(".TW")) && window.FinMind && window.FinMind.getTaiwanUniverse) {
    try {
      const list = await window.FinMind.getTaiwanUniverse();
      nameMap = new Map(list.map((s) => [s.sym, s.name]));
    } catch (e) { /* universe 抓不到不影響主流程 */ }
  }
  const results = await Promise.allSettled(syms.map((s) => resolveOne(s, nameMap)));
  return results.map((r, i) => {
    if (r.status === "fulfilled") return r.value;
    return {
      sym: syms[i],
      name: symName(syms[i], nameMap),
      market: symMarket(syms[i]),
      price: 0, chg: 0, pct: 0, vol: 0, volMa: 1, rsi: 50, ma: "Sideways",
      ts: "", spark: [], alert: "off", news: 0,
      _error: r.reason?.message || String(r.reason),
    };
  });
}

/* 把 intraday quotes 套到 rows — 即時覆蓋 price/chg/pct/vol/ts */
function mergeQuotes(rows, quotes) {
  if (!quotes || quotes.size === 0) return rows;
  return rows.map((r) => {
    const q = quotes.get(r.sym);
    if (!q || q.price == null) return r;
    const chg = q.prevClose != null ? q.price - q.prevClose : r.chg;
    const pct = q.prevClose ? (chg / q.prevClose) * 100 : r.pct;
    const ts = new Date(q.ts);
    return {
      ...r,
      price: q.price,
      chg,
      pct,
      vol: q.vol ?? r.vol,
      ts: `${ts.toISOString().slice(0, 10)} ${ts.toLocaleTimeString("zh-TW", { hour12: false })}`,
      _live: true,
    };
  });
}

window.WatchlistEngine = { resolveRows, mergeQuotes };
