// ═══════════════════════════════════════════════════════════
//  fincheck.js — 財報健檢規則引擎
//  輸入 fundamentals.js 正規化後的財報，照 Emmy「財報基本功」五步驟
//  產出紅/黃/綠燈 + 白話提醒，每條檢查連回對應名詞/心法/案例。
//  純規則、不抓網路；連結目標為 knowledge linkMap 的鍵，前端負責跳轉。
// ═══════════════════════════════════════════════════════════

const L = {
  營收: { target: '名詞/營收', label: '營收' },
  YoY: { target: '名詞/YoY', label: 'YoY' },
  QoQ: { target: '名詞/QoQ', label: 'QoQ' },
  毛利率: { target: '名詞/毛利率', label: '毛利率' },
  EPS: { target: '名詞/EPS', label: 'EPS' },
  淨利: { target: '名詞/淨利', label: '淨利' },
  Capex: { target: '名詞/Capex', label: 'Capex' },
  現金流: { target: '名詞/現金流量表', label: '現金流量表' },
  負債率: { target: '名詞/負債率', label: '負債率' },
  資產負債表: { target: '名詞/資產負債表', label: '資產負債表' },
  NonGAAP: { target: '名詞/Non-GAAP', label: 'Non-GAAP' },
  財測: { target: '名詞/財測', label: '財測' },
  進攻Capex: { target: 'Emmy 投資心法#原則三：進攻型Capex', label: '原則三：進攻型Capex' },
  定價權: { target: 'Emmy 投資心法#原則四：供給瓶頸定價權', label: '原則四：供給瓶頸定價權' },
  毛利溫度計: { target: 'Emmy 投資心法#原則四十七：毛利率是規模化的溫度計', label: '原則四十七：毛利率是規模化的溫度計' },
  財測重要: { target: 'Emmy 投資心法#原則十七：財測比財報重要', label: '原則十七：財測比財報重要' },
  總經優先: { target: 'Emmy 投資心法#原則五十：總經大於產業大於個股', label: '原則五十：總經>產業>個股' },
  財報基本功: { target: '學習分類/財報基本功', label: '財報基本功' },
  capex案例: { target: '案例講解/AI四巨頭Capex怎麼看', label: 'AI四巨頭Capex怎麼看' },
  財報案例: { target: '案例講解/NVIDIA財報怎麼看', label: 'NVIDIA財報怎麼看' },
};

const signed = (v, d = 0, suf = '%') => v == null || isNaN(v) ? '—' : (v >= 0 ? '+' : '') + v.toFixed(d) + suf;
const na = (id, step, label, note, links) => ({ id, step, label, status: 'na', value: '無資料', note, links: links || [] });

function yoy(cur, prev) { return (cur != null && prev) ? ((cur - prev) / Math.abs(prev)) * 100 : null; }
// 找最接近「一年前」的那一季（容忍 ±70 天），避免 EDGAR 不單獨揭露 Q4 造成的索引錯位
function yearAgo(q, ref) {
  if (!ref || !ref.end) return null;
  const target = new Date(ref.end); target.setUTCFullYear(target.getUTCFullYear() - 1);
  let best = null, bd = Infinity;
  for (let i = 1; i < q.length; i++) {
    if (!q[i].end) continue;
    const d = Math.abs(new Date(q[i].end) - target) / 86400000;
    if (d < bd) { bd = d; best = q[i]; }
  }
  return bd <= 70 ? best : null;
}

export function buildReport(f) {
  const q = f.quarters || [];
  const a = f.annual || [];
  const bal = f.balance || {};
  const checks = []; // {step}
  const add = c => checks.push(c);
  const cur = q[0] || null;
  const ya = cur ? yearAgo(q, cur) : null;

  // ── 步驟 1：營收成長 ──
  if (cur && ya && cur.revenue != null && ya.revenue != null) {
    const g = yoy(cur.revenue, ya.revenue);
    add({ id: 'rev_yoy', step: 1, label: `營收年增 YoY（${cur.label} vs ${ya.label}）`, value: signed(g, 0),
      status: g >= 15 ? 'good' : g >= 0 ? 'warn' : 'bad',
      note: g >= 15 ? '營收年增強勁，需求結構性成長。' : g >= 0 ? '營收仍在成長但動能偏緩，留意是否放慢。' : '營收較去年同期衰退，需求或競爭出問題。',
      links: [L.營收, L.YoY] });
  } else if (a.length >= 2 && a[0].revenue != null && a[1].revenue != null) {
    const g = yoy(a[0].revenue, a[1].revenue);
    add({ id: 'rev_yoy', step: 1, label: `營收年增（${a[1].label}→${a[0].label}，年度）`, value: signed(g, 0),
      status: g >= 15 ? 'good' : g >= 0 ? 'warn' : 'bad',
      note: '季資料不足，改用年度營收比較。', links: [L.營收, L.YoY] });
  } else add(na('rev_yoy', 1, '營收年增 YoY', '財報期數不足，無法計算年增。', [L.營收]));

  if (q.length >= 2 && cur.revenue != null && q[1].revenue != null) {
    const g = yoy(cur.revenue, q[1].revenue);
    add({ id: 'rev_qoq', step: 1, label: `營收環比 QoQ（${cur.label} vs ${q[1].label}）`, value: signed(g, 0),
      status: g >= 0 ? 'good' : g >= -10 ? 'warn' : 'bad',
      note: '看短期動能；高成長股最怕增速放慢。注意產業淡旺季與會計年度。', links: [L.QoQ] });
  }

  // ── 步驟 2：毛利率與獲利品質 ──
  const gm = q.find(p => p.grossMargin != null)?.grossMargin ?? a.find(p => p.grossMargin != null)?.grossMargin;
  if (gm != null) {
    add({ id: 'gm_level', step: 2, label: '毛利率水準', value: gm.toFixed(1) + '%',
      status: gm >= 50 ? 'good' : gm >= 25 ? 'warn' : 'bad',
      note: gm >= 50 ? '毛利率高，通常代表定價權、技術門檻或供給瓶頸。' : gm >= 25 ? '毛利率中等，視產業而定（硬體偏低、軟體偏高）。' : '毛利率偏低，定價權或成本結構需留意。',
      links: [L.毛利率, L.定價權] });
    if (cur && ya && cur.grossMargin != null && ya.grossMargin != null) {
      const d = cur.grossMargin - ya.grossMargin;
      add({ id: 'gm_trend', step: 2, label: '毛利率趨勢（年比）', value: signed(d, 1, 'pp'),
        status: d >= 0.5 ? 'good' : d >= -1 ? 'warn' : 'bad',
        note: d >= 0.5 ? '毛利率走升，是規模化／產品組合優化的訊號。' : d >= -1 ? '毛利率大致持平。' : '毛利率下滑，留意是否降價、成本上升或組合變差。',
        links: [L.毛利溫度計] });
    }
  } else add(na('gm_level', 2, '毛利率', '此來源未提供毛利資料（部分公司不揭露銷貨成本）。', [L.毛利率]));

  if (cur && ya && cur.operatingMargin != null && ya.operatingMargin != null) {
    const d = cur.operatingMargin - ya.operatingMargin;
    add({ id: 'om_trend', step: 2, label: '營業利潤率趨勢（年比）', value: signed(d, 1, 'pp'),
      status: d >= 0 ? 'good' : d >= -2 ? 'warn' : 'bad',
      note: '營收成長率高於淨利成長時，常代表營運槓桿開始發酵。', links: [L.淨利] });
  }

  // ── 步驟 3：EPS 與獲利 ──
  if (cur && ya && cur.eps != null && ya.eps != null) {
    const g = yoy(cur.eps, ya.eps);
    add({ id: 'eps_yoy', step: 3, label: `EPS 年增（${cur.label} vs ${ya.label}）`, value: signed(g, 0),
      status: g >= 15 ? 'good' : g >= 0 ? 'warn' : 'bad',
      note: f.source === 'Yahoo Finance' ? 'EPS 以淨利÷流通股數估算，僅供概略參考。' : 'EPS 取自申報的稀釋每股盈餘。',
      links: [L.EPS] });
  } else add(na('eps_yoy', 3, 'EPS 年增', 'EPS 期數不足或來源未提供。', [L.EPS]));

  const nm = q.find(p => p.netMargin != null)?.netMargin;
  if (nm != null) {
    add({ id: 'net_margin', step: 3, label: '淨利率', value: nm.toFixed(1) + '%',
      status: nm >= 15 ? 'good' : nm > 0 ? 'warn' : 'bad',
      note: nm > 0 ? '最後真正落袋的獲利佔營收比例。' : '目前淨利為負，須看是投入期還是結構性虧損。',
      links: [L.淨利] });
  }

  // ── 步驟 4：Capex 與現金流 ──
  const ay = a.find(p => p.capex != null && p.netIncome != null);
  if (ay) {
    const burn = Math.abs(ay.capex) / Math.abs(ay.netIncome);
    const profitable = ay.netIncome > 0;
    add({ id: 'capex_burn', step: 4, label: `燒錢倍數 Capex ÷ 淨利（${ay.label}）`, value: profitable ? burn.toFixed(2) + 'x' : '淨利為負',
      status: !profitable ? 'bad' : burn <= 0.6 ? 'good' : burn <= 1 ? 'warn' : 'bad',
      note: !profitable ? '淨利為負時的高 Capex 風險較大。' : burn <= 0.6 ? 'Capex 遠低於獲利，擴張遊刃有餘。' : burn <= 1 ? 'Capex 接近全年獲利，留意現金消耗。' : 'Capex 高於獲利＝在燒錢擴張，須判斷是「進攻型」還是壓力。',
      links: [L.Capex, L.進攻Capex, L.capex案例] });
  } else add(na('capex_burn', 4, '燒錢倍數 Capex÷淨利', '年度 Capex 或淨利資料不足。', [L.Capex, L.進攻Capex]));

  const ocfY = a.find(p => p.ocf != null);
  if (ocfY) {
    add({ id: 'ocf', step: 4, label: `營業現金流（${ocfY.label}）`, value: fmtMoneyShort(ocfY.ocf),
      status: ocfY.ocf > 0 ? 'good' : 'bad',
      note: ocfY.ocf > 0 ? '本業真的有現金流入，不只是帳面獲利。' : '營業現金流為負，獲利品質要打問號。',
      links: [L.現金流] });
  }

  // ── 步驟 5：資產負債 / 槓桿 ──
  if (bal.debtToAssets != null) {
    add({ id: 'leverage', step: 5, label: '負債率（總負債 ÷ 總資產）', value: bal.debtToAssets.toFixed(0) + '%',
      status: bal.debtToAssets < 50 ? 'good' : bal.debtToAssets <= 70 ? 'warn' : 'bad',
      note: bal.debtToAssets < 50 ? '負債比例健康，高利率環境下抗風險能力較強。' : bal.debtToAssets <= 70 ? '負債比例中等，留意利息負擔。' : '負債比例偏高，升息環境下風險較大。',
      links: [L.負債率, L.資產負債表] });
  } else add(na('leverage', 5, '負債率', '資產負債資料不足。', [L.資產負債表]));

  // ── 統計 + 結論 ──
  const cnt = { good: 0, warn: 0, bad: 0, na: 0 };
  for (const c of checks) cnt[c.status]++;
  let verdict, verdictColor;
  if (cnt.good + cnt.warn + cnt.bad === 0) { verdict = '資料不足'; verdictColor = 'warn'; }
  else if (cnt.bad >= 3) { verdict = '偏弱'; verdictColor = 'bad'; }
  else if (cnt.bad >= 1) { verdict = '留意'; verdictColor = 'warn'; }
  else if (cnt.warn >= 3) { verdict = '尚可'; verdictColor = 'warn'; }
  else { verdict = '穩健'; verdictColor = 'good'; }

  const STEP_TITLES = ['', '營收成長', '毛利率與獲利品質', 'EPS 與獲利', 'Capex 與現金流', '資產負債與槓桿'];
  const steps = [1, 2, 3, 4, 5].map(n => ({ num: n, title: STEP_TITLES[n], checks: checks.filter(c => c.step === n) }));

  const caveats = [
    { text: '財報只是判斷的一面。Emmy 強調 {link}，下任何個股判斷前先看天氣（總經與大盤水位）。同一份財報在多頭與空頭環境，市場解讀可能完全不同。',
      links: [L.總經優先] },
    { text: '本工具使用申報的 GAAP 數字；看科技股時市場常用 {link}，記得對照差異與是否每季都有「一次性費用」。財報也別只看好壞，要和 {link}（市場預期）一起看。',
      links: [L.NonGAAP, L.財測] },
    { text: '想學完整看財報的方法，回到 {link} 與 {link}。',
      links: [L.財報基本功, L.財報案例] },
  ];

  return { summary: { ...cnt, verdict, verdictColor }, steps, caveats };
}

function fmtMoneyShort(v) {
  if (v == null || isNaN(v)) return '—';
  const x = Math.abs(v), s = v < 0 ? '-' : '';
  if (x >= 1e12) return s + '$' + (x / 1e12).toFixed(2) + 'T';
  if (x >= 1e9) return s + '$' + (x / 1e9).toFixed(2) + 'B';
  if (x >= 1e6) return s + '$' + (x / 1e6).toFixed(2) + 'M';
  return s + '$' + x.toFixed(0);
}
