// sources.jsx — cijfers bijwerken + nieuw aandeel bouwen via de server-endpoints.
//
// Marktdata komt van /api/fundamentals (Yahoo — één leverancier per snapshot).
// Vooruitkijkende AANNAMES (winst-/dividendgroei, verwacht dividend, IR-URL) komen
// van /api/ai-estimate (GPT met web search), geankerd aan de Yahoo-cijfers en
// gereconcilieerd vóór gebruik. Geen mock: ontbrekende gerapporteerde cijfers
// blijven null → de UI toont "Niet beschikbaar".

const PRIMARY = { name: 'Yahoo Finance', short: 'YH' };
const clampS = (v, lo, hi) => Math.max(lo, Math.min(hi, v));

async function fetchPrimary(stock) {
  // Gebruik het exacte Yahoo-symbool als dat bekend is (zoek-toegevoegde aandelen);
  // anders ticker+beurs. Voorkomt ticker-collisies zoals HAL.AS vs Halliburton (HAL).
  const symQ = stock.ySymbol ? `&symbol=${encodeURIComponent(stock.ySymbol)}` : '';
  const res = await fetch(
    `/api/fundamentals?ticker=${encodeURIComponent(stock.ticker)}&exchange=${encodeURIComponent(stock.exchange || '')}${symQ}`
  );
  if (!res.ok) return null;
  const d = await res.json();
  return d && !d.error ? d : null;
}

// AI/GPT: uitsluitend de vooruitkijkende aannames + officiële IR-URL (web search).
// `anchors` (uit het verse Yahoo-snapshot) gronden het verwachte dividend en de
// groei aan de bekende cijfers; het IR-register op de server dwingt het officiële
// domein af.
async function aiEstimate(stock, anchors) {
  const a = anchors || {};
  try {
    const apiRes = await fetch('/api/ai-estimate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ticker: stock.ticker, name: stock.name, exchange: stock.exchange,
        website: a.website ?? stock.website ?? '',
        ttmDividend: a.dividendRate ?? stock.dividendRate ?? null,
        analystGrowth: a.analystGrowth ?? stock.analystGrowth ?? null,
      }),
    });
    if (!apiRes || !apiRes.ok) throw new Error('geen-ai');
    const j = await apiRes.json();
    if (j.error) throw new Error(j.error);
    let ir = String(j.ir || '').trim();
    if (ir && !/^https?:\/\//i.test(ir)) ir = 'https://' + ir;
    if (ir && !/\.[a-z]{2,}/i.test(ir)) ir = '';
    const nextDividend = (j.nextDividend != null && Number.isFinite(Number(j.nextDividend)))
      ? Math.max(0, Number(j.nextDividend)) : null;
    // Ontbreekt een groeiveld (server: onleesbaar → null), houd dan de bestaande
    // aanname aan i.p.v. stilzwijgend 0% te rekenen — en zeg dat erbij.
    const cur = stock.inputs || stock.inp || {};
    const rate = (v, keep) => (v != null && Number.isFinite(Number(v)) ? clampS(Number(v), 0, 0.30) : keep);
    const growth = rate(j.growth, null), divGrowth = rate(j.divGrowth, null);
    let rationale = String(j.rationale || '').slice(0, 140);
    if (growth == null || divGrowth == null) rationale = `${rationale} · deels geen AI-waarde, bestaande aanname aangehouden`.slice(0, 140);
    return {
      growth: growth ?? clampS(cur.growth ?? 0.08, 0, 0.30),
      divGrowth: divGrowth ?? clampS(cur.divGrowth ?? 0.03, 0, 0.30),
      nextDividend,
      fairPE: (Number.isFinite(Number(j.fairPE)) && Number(j.fairPE) > 0) ? clampS(Number(j.fairPE), 5, 60) : null,
      rationale,
      ir,
      fallback: false,
    };
  } catch (e) {
    // Geen AI bereikbaar → houd de bestaande aanname aan, duidelijk gelabeld.
    const cur = stock.inputs || stock.inp || {};
    return {
      growth: clampS(cur.growth ?? 0.08, 0, 0.30),
      divGrowth: clampS(cur.divGrowth ?? 0.03, 0, 0.30),
      nextDividend: null,
      fairPE: null,
      rationale: 'AI niet bereikbaar — bestaande groei-aanname aangehouden.',
      ir: '',
      fallback: true,
    };
  }
}

// Poortwachter: reconcilieer de AI-schatting met de Yahoo-ankers vóór gebruik.
// Buiten de controleband → veld vervalt naar fallback, met zichtbare notitie —
// nooit stilzwijgend accepteren.
function reconcileAI(ai, p) {
  if (ai.fallback) return ai;
  const num = (v) => (Number.isFinite(Number(v)) && v !== null ? Number(v) : null);
  const note = (s) => { ai.rationale = `${ai.rationale} · ${s}`.slice(0, 140); };
  const ttm = num(p && p.dividendRate);
  if (ai.nextDividend != null && ttm > 0 && (ai.nextDividend < ttm * 0.5 || ai.nextDividend > ttm * 2)) {
    ai.nextDividend = null;                     // buiten band → projectie-fallback in de UI
    note('verwacht dividend buiten controleband, genegeerd');
  }
  const ag = num(p && p.analystGrowth);
  if (ag != null && Number.isFinite(ai.growth)
      && Math.abs(ai.growth - clampS(ag, 0, 0.30)) > 0.10) {
    note(`let op: wijkt af van analistenconsensus (${Math.round(ag * 100)}%)`);
  }
  return ai;
}

// Bronlabels met echte vintage: koers-achtige velden zijn live, fundamentals
// zijn per laatste rapportagedatum (fiscalAsOf) — geen "live"-etiket op
// kwartaalcijfers.
function makeSources(p, ai) {
  const yhLive = { kind: 'api', label: PRIMARY.name, short: PRIMARY.short, asOf: 'live' };
  const yhFund = { kind: 'api', label: PRIMARY.name, short: PRIMARY.short,
                   asOf: p && p.fiscalAsOf ? `per ${p.fiscalAsOf}` : 'laatste rapportage' };
  const aiS = { kind: 'ai', label: 'AI-schatting (GPT)', short: 'AI', asOf: 'live',
                note: ai.rationale, ir: ai.ir, fallback: ai.fallback };
  return { yhLive, yhFund, aiS };
}

async function refreshFundamentals(stock) {
  const p = await fetchPrimary(stock);
  if (!p || p.price == null) throw new Error('geen live data');
  const ai = reconcileAI(await aiEstimate(stock, p), p);

  const num = (v) => (Number.isFinite(Number(v)) && v !== null ? Number(v) : null);
  const pos = (v) => (num(v) != null && num(v) > 0 ? num(v) : null);

  const price = num(p.price);
  const eps = pos(p.eps);
  const shares = pos(p.shares);
  const dividendRate = num(p.dividendRate);
  const beta = num(p.beta);
  const fcf = num(p.fcf);
  const forwardPE = num(p.forwardPE);
  const low52 = num(p.low52);
  const high52 = num(p.high52);
  const marketCap = num(p.marketCap) ?? ((price != null && shares != null) ? price * shares : null);
  const payoutRatio = num(p.payoutRatio) ?? ((eps > 0 && dividendRate > 0) ? Math.min(dividendRate / eps, 2) : null);

  // FastGraphs-stijl band op echte WPA per jaar + historische normale K/W
  const fh = window.buildFairHistory(p.priceHistory, p.epsHistory, stock.normalPE);
  const history = fh.history;
  const normalPE = fh.normalPE;
  const cy = new Date().getFullYear();
  if (fh.hasEps && eps != null && price != null && (!history.length || history[history.length - 1].year < cy)) {
    history.push({ year: cy, price, eps, fair: normalPE != null ? +(eps * normalPE).toFixed(2) : null });
  }
  const divHistory = Array.isArray(p.divHistory) ? p.divHistory : [];

  const factPatch = {
    price, eps, dividendRate,
    dividendYield: dividendRate == null ? null : (price ? dividendRate / price : 0),
    payoutRatio, forwardPE,
    peTTM: (price != null && eps > 0) ? price / eps : null,
    beta, fcf, shares,
    low52, high52, marketCap, normalPE,
    peg: (eps > 0 && ai.growth > 0) ? (price / eps) / (ai.growth * 100) : null,
    sector: p.sector || stock.sector || '—',
    website: p.website || stock.website || null,
    totalDebt: num(p.totalDebt), totalCash: num(p.totalCash),
    debtToEquity: num(p.debtToEquity), returnOnEquity: num(p.returnOnEquity),
    profitMargins: num(p.profitMargins), revenueGrowth: num(p.revenueGrowth),
    fiscalAsOf: p.fiscalAsOf || null,
    nextDividend: ai.nextDividend,
    history, divHistory, epsHistory: Array.isArray(p.epsHistory) ? p.epsHistory : [],
    financials: p.financials || null,
  };
  // Fair-value-K/W: AI (analist + sector) > Yahoo-analist-impliciet (target ÷ fwd-WPA)
  const analystPE = (num(p.targetMeanPrice) != null && num(p.forwardEps) > 0)
    ? +(p.targetMeanPrice / p.forwardEps).toFixed(1) : null;
  const fairPE = num(ai.fairPE) ?? analystPE;

  // Alleen AANNAMES in de inputs. Gerapporteerde cijfers (dividend, WPA, VKS)
  // worden in app.jsx altijd uit het actuele snapshot afgeleid — anders bevriest
  // een refresh ze voorgoed in de lens (bv. dividend van vóór een verlaging).
  const inputPatch = { growth: ai.growth, divGrowth: ai.divGrowth };
  if (fairPE != null) inputPatch.fairPE = fairPE;

  // bronlabels: marktdata = Yahoo (alleen waar echt geleverd); aannames = AI.
  const { yhLive, yhFund, aiS } = makeSources(p, ai);
  const sources = { growth: aiS, divGrowth: aiS, nextDividend: aiS };
  if (price != null) { sources.price = yhLive; sources.low52 = yhLive; sources.high52 = yhLive; }
  if (marketCap != null) sources.marketCap = yhLive;
  if (eps != null) { sources.eps = yhFund; sources.peTTM = yhFund; }
  if (dividendRate != null) { sources.dividend = yhLive; sources.dividendYield = yhLive; }
  if (payoutRatio != null) sources.payoutRatio = yhFund;
  if (forwardPE != null) sources.forwardPE = yhFund;
  if (beta != null) sources.beta = yhFund;
  if (fcf != null) { sources.fcf = yhFund; sources.cashflowPerShare = yhFund; }
  if (shares != null) sources.shares = yhFund;
  if (factPatch.totalDebt != null) sources.totalDebt = yhFund;
  if (factPatch.debtToEquity != null) sources.debtToEquity = yhFund;
  if (factPatch.returnOnEquity != null) sources.returnOnEquity = yhFund;
  if (factPatch.profitMargins != null) sources.profitMargins = yhFund;
  // PEG = K/W (Yahoo) ÷ groei (AI-schatting) → draagt het AI-label
  if (eps != null && ai.growth > 0) sources.peg = aiS;
  if (fairPE != null) sources.fairPE = (num(ai.fairPE) != null) ? aiS : yhFund;

  return { factPatch, inputPatch, sources, refreshedAt: Date.now() };
}

// ── nieuw aandeel bouwen uit marktdata + AI-groei ────────────────────────────
// Werkt voor elke ticker die Yahoo kent (US + EU + UK). Marktdata van
// /api/fundamentals, vooruitkijkende aannames van /api/ai-estimate.
async function buildStock(sel) {
  // sel = string-ticker (US) of zoekresultaat-object {symbol, name, exchange}
  const isObj = sel && typeof sel === 'object';
  const ySymbol = isObj ? String(sel.symbol || sel.ySymbol || '').trim() : '';
  const tk = (isObj
    ? String(sel.ticker || ySymbol.split('.')[0] || sel.symbol || '')
    : String(sel)).toUpperCase().trim();
  const qs = ySymbol ? `symbol=${encodeURIComponent(ySymbol)}` : `ticker=${encodeURIComponent(tk)}`;
  let d = null;
  try {
    const res = await fetch(`/api/fundamentals?${qs}`);
    if (res.ok) d = await res.json();
  } catch { d = null; }
  if (!d || d.error || d.price == null) throw new Error(`Geen data gevonden voor ${ySymbol || tk}.`);

  const num = (v) => (Number.isFinite(Number(v)) && v !== null ? Number(v) : null);
  const price = num(d.price);
  const eps = num(d.eps);
  const shares = num(d.shares);
  const dividendRate = num(d.dividendRate);
  const forwardPE = num(d.forwardPE);
  const dy = (dividendRate != null && price) ? dividendRate / price : 0;
  const normalPE = (forwardPE && forwardPE > 0) ? forwardPE : (eps > 0 ? price / eps : 20);

  const ai = reconcileAI(await aiEstimate(
    { ticker: tk, name: (isObj && sel.name) || d.name || tk, exchange: (isObj && sel.exchange) || d.exchange || '' },
    d,
  ), d);
  const growth = ai.growth, divGrowth = ai.divGrowth;

  const seed = {
    ticker: tk,
    ySymbol: ySymbol || undefined,
    name: String((isObj && sel.name) || d.name || tk).slice(0, 48),
    exchange: (isObj && sel.exchange) || d.exchange || '—',
    currency: d.currency || 'USD',
    sector: d.sector || '—',
    website: d.website || null,
    defaultLens: dy >= 0.03 ? 'dividend' : 'growth',
    normalPE,
    nextDividend: ai.nextDividend ?? (dividendRate != null ? dividendRate : null),
    price, dayChange: num(d.dayChange) ?? 0,
    eps, forwardPE,
    peTTM: (price != null && eps > 0) ? price / eps : null,
    peg: (eps > 0 && growth > 0) ? (price / eps) / (growth * 100) : null,
    dividendRate, dividendYield: dividendRate == null ? null : dy,
    payoutRatio: num(d.payoutRatio),
    low52: num(d.low52), high52: num(d.high52),
    marketCap: num(d.marketCap) ?? ((price != null && shares != null) ? price * shares : null),
    beta: num(d.beta), fcf: num(d.fcf), shares,
    totalDebt: num(d.totalDebt), totalCash: num(d.totalCash),
    debtToEquity: num(d.debtToEquity), returnOnEquity: num(d.returnOnEquity),
    profitMargins: num(d.profitMargins), revenueGrowth: num(d.revenueGrowth),
    fiscalAsOf: d.fiscalAsOf || null,
    loadedAt: Date.now(),
    forwardEps: num(d.forwardEps), targetMeanPrice: num(d.targetMeanPrice), analystGrowth: num(d.analystGrowth),
    recommendationKey: d.recommendationKey || null,
    priceHistory: Array.isArray(d.priceHistory) ? d.priceHistory : [],
    priceMonthly: Array.isArray(d.priceMonthly) ? d.priceMonthly : [],
    divHistory: Array.isArray(d.divHistory) ? d.divHistory : [],
    epsHistory: Array.isArray(d.epsHistory) ? d.epsHistory : [],
    financials: d.financials || null,
    inp: {
      // vaste startwaarde, bewust NIET afgeleid van de huidige yield: anders ligt
      // D/y per constructie onder de koers en beoordeelt het model zichzelf
      requiredYield: 0.05, divGrowth, growth,
      growthSource: ai.fallback ? undefined : 'ai',
      growthCap: Math.min(0.16, Math.max(0.08, growth)),
      discountRate: 0.09, terminalGrowth: 0.03, bondYield: 4.8,
      fairPE: ai.fairPE,   // AI (analist+sector); null → hydrate valt terug op Yahoo-analist
    },
  };
  const stock = window.hydrateStock(seed);

  const { yhLive, yhFund, aiS } = makeSources(d, ai);
  const tag = (v, s) => (v != null ? s : undefined);
  stock.sources = {
    price: tag(price, yhLive), eps: tag(eps, yhFund), dividend: tag(dividendRate, yhLive),
    dividendYield: tag(dividendRate, yhLive), payoutRatio: tag(stock.payoutRatio, yhFund),
    forwardPE: tag(forwardPE, yhFund), peTTM: tag(stock.peTTM, yhFund), beta: tag(stock.beta, yhFund),
    fcf: tag(stock.fcf, yhFund), shares: tag(shares, yhFund), low52: tag(stock.low52, yhLive),
    high52: tag(stock.high52, yhLive), marketCap: tag(stock.marketCap, yhLive),
    cashflowPerShare: tag(stock.fcfPerShare, yhFund),
    totalDebt: tag(stock.totalDebt, yhFund), debtToEquity: tag(stock.debtToEquity, yhFund),
    returnOnEquity: tag(stock.returnOnEquity, yhFund), profitMargins: tag(stock.profitMargins, yhFund),
    peg: tag(stock.peg, aiS),   // K/W (Yahoo) ÷ groei (AI) → AI-label
    fairPE: tag(stock.inputs && stock.inputs.fairPE, ai.fairPE != null ? aiS : yhFund),
    growth: aiS, divGrowth: aiS, nextDividend: aiS,
  };
  stock.refreshedAt = Date.now();
  return stock;
}

// ── AI-samenvatting: duiding van de berekende cijfers mét context ────────────
// De getallen komen uitsluitend uit deze payload (leesbare labels — geen interne
// veldnamen); het model mag web search alléén gebruiken voor kwalitatieve
// context (IR/strategie/markt, met bronvermelding). Zie api/ai-summary.js.
async function fetchNews(stock) {
  const qs = new URLSearchParams({ ticker: stock.ticker || '', exchange: stock.exchange || '', name: stock.name || '' });
  const res = await fetch(`/api/news?${qs}`);
  if (!res.ok) throw new Error('Nieuws niet beschikbaar');
  const j = await res.json();
  if (j.error) throw new Error(j.error);
  return j;   // { items, change5d, change1m }
}

async function aiSummary(stock, ev, news) {
  const fin = (v) => (Number.isFinite(v) ? +v.toFixed(4) : undefined);
  const data = {
    'koers': fin(stock.price),
    'reële waarde (composiet van de methodes)': fin(ev.composite),
    'koopprijs na veiligheidsmarge': fin(ev.buy),
    'veiligheidsmarge': fin(stock.inputs.safetyMargin),
    'potentieel t.o.v. reële waarde': fin(ev.upside),
    'status': STATUS_NL[ev.status],
    'K/W (TTM)': fin(stock.peTTM),
    'verwachte K/W': fin(stock.forwardPE),
    'PEG': fin(stock.peg),
    'dividendrendement': fin(stock.dividendYield),
    'uitkeringsratio (winst)': fin(stock.payoutRatio),
    'verwacht jaarrendement 5j (modeluitkomst)': fin(ev.proj),
    'historisch jaarrendement': fin(ev.hist),
    'winstgroei-aanname': fin(stock.inputs.growth),
    'dividendgroei-aanname': fin(stock.inputs.divGrowth),
    'nettoschuld': (Number.isFinite(stock.totalDebt) && Number.isFinite(stock.totalCash))
      ? fin(stock.totalDebt - stock.totalCash) : undefined,
    'schuld/eigen vermogen (%)': fin(stock.debtToEquity),
    'rendement op eigen vermogen': fin(stock.returnOnEquity),
    'nettomarge': fin(stock.profitMargins),
  };
  for (const m of ev.val.methods) data[`uitkomst ${m.label}`] = fin(m.value);
  data['koersverandering 5 handelsdagen'] = fin(news && news.change5d);
  data['koersverandering 1 maand'] = fin(news && news.change1m);
  const newsItems = ((news && news.items) || []).slice(0, 8).map((n) => ({
    titel: n.title, uitgever: n.publisher, datum: new Date(n.publishedAt).toISOString().slice(0, 10),
    'koersreactie op dag': fin(n.reaction1d), 'koers sinds bericht': fin(n.sinceNews),
  }));
  const res = await fetch('/api/ai-summary', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      ticker: stock.ticker, name: stock.name, exchange: stock.exchange,
      sector: stock.sector, website: stock.website || '',
      currency: stock.currency, lens: ev.lens, data, news: newsItems,
    }),
  });
  if (!res.ok) throw new Error('AI-samenvatting niet beschikbaar');
  const j = await res.json();
  if (j.error || !j.summary) throw new Error(j.error || 'AI-samenvatting niet beschikbaar');
  return j.summary;
}

Object.assign(window, { refreshFundamentals, buildStock, aiSummary, fetchNews, PRIMARY_SOURCE: PRIMARY });
