// app.jsx — schil: state, routing, thema/dichtheid, tweaks.
const { useState: useStateApp, useEffect, useMemo } = React;

const ACCENTS = {
  '#2f7d5b': { under: '#2f7d5b', over: '#b4452f', fair: '#b78a2e' }, // groen / terracotta
  '#2563a8': { under: '#2563a8', over: '#c9912a', fair: '#7a7f8a' }, // blauw / goud
  '#3d3d3d': { under: '#3d7a55', over: '#7a4a3d', fair: '#888' },    // gedempt
  '#4338ca': { under: '#4338ca', over: '#e0a23b', fair: '#8a7fb0' }, // indigo / amber
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "dark": false,
  "density": "comfy",
  "accent": ["#2f7d5b", "#b4452f"],
  "layout": "table"
}/*EDITMODE-END*/;

function Brand() {
  return (
    <div className="brand">
      <span className="brand__mark" aria-hidden="true" />
      <span className="brand__name">Koerswaarde</span>
    </div>
  );
}

const LS = { watch: 'kw_watch', custom: 'kw_custom', user: 'kw_user', userVer: 'kw_user_v' };
function loadLS(key, fallback) { try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; } catch (e) { return fallback; } }
function saveLS(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch (e) { /* quota */ } }

// Eenmalige migratie: oude overrides bevatten nu-live-afgeleide marktvelden die de
// verse data zouden schaduwen (o.a. de foute dividend €0,00). Strip die één keer;
// lens-keuze en slider-tweaks (inputs) blijven behouden. Versie-flag voorkomt dat
// latere echte refreshes weer worden gewist.
const LIVE_FACT_KEYS = ['price', 'dayChange', 'low52', 'high52', 'dividendRate', 'dividendYield',
  'marketCap', 'history', 'divHistory', 'eps', 'peTTM', 'forwardPE', 'beta', 'fcf', 'shares',
  'payoutRatio', 'peg', 'nextDividend', 'sector', 'totalDebt', 'totalCash', 'debtToEquity',
  'returnOnEquity', 'profitMargins', 'revenueGrowth', 'fiscalAsOf', 'website', 'financials'];
function loadUser() {
  const u = loadLS(LS.user, {});
  // v3: opnieuw strippen — een verkeerde refresh kon foute marktdata opslaan
  // (bv. HAL→Halliburton i.p.v. HAL.AS); na strippen tonen we weer de live data.
  if (loadLS(LS.userVer, 0) < 3) {
    for (const tk of Object.keys(u)) {
      const e = u[tk]; if (!e) continue;
      if (e.facts) { for (const k of LIVE_FACT_KEYS) delete e.facts[k]; if (!Object.keys(e.facts).length) delete e.facts; }
      delete e.sources; delete e.refreshedAt;
    }
    saveLS(LS.user, u); saveLS(LS.userVer, 3);
  }
  // v4: oudere refreshes schreven gerapporteerde cijfers (dividend, WPA, VKS) als
  // "input" weg, waardoor de lens ze bevroor. Die horen uit het snapshot te komen.
  if (loadLS(LS.userVer, 0) < 4) {
    for (const tk of Object.keys(u)) {
      const e = u[tk]; if (!e || !e.inputs) continue;
      delete e.inputs.dividend; delete e.inputs.eps; delete e.inputs.cashflowPerShare;
      if (!Object.keys(e.inputs).length) delete e.inputs;
    }
    saveLS(LS.user, u); saveLS(LS.userVer, 4);
  }
  return u;
}

function AddDialog({ onClose, onAdd, existing, busy, error }) {
  const [val, setVal] = useStateApp('');
  const [results, setResults] = useStateApp([]);
  const [searching, setSearching] = useStateApp(false);
  const suggestions = LIBRARY_SUGGESTIONS.filter((s) => !existing.includes(s.ticker));

  // debounced zoeken op ticker én bedrijfsnaam
  useEffect(() => {
    const q = val.trim();
    if (q.length < 2) { setResults([]); setSearching(false); return; }
    let cancelled = false;
    setSearching(true);
    const id = setTimeout(async () => {
      try {
        const r = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
        const d = r.ok ? await r.json() : { results: [] };
        if (!cancelled) setResults(d.results || []);
      } catch { if (!cancelled) setResults([]); } finally { if (!cancelled) setSearching(false); }
    }, 250);
    return () => { cancelled = true; clearTimeout(id); };
  }, [val]);

  const submitText = () => { const t = val.trim().toUpperCase(); if (t && !busy) onAdd(t); };
  const showDrop = !busy && val.trim().length >= 2 && (results.length > 0 || searching);
  return (
    <div className="modal" onMouseDown={(e) => { if (e.target === e.currentTarget && !busy) onClose(); }}>
      <div className="modal__card" role="dialog" aria-label="Aandeel toevoegen">
        <div className="modal__head">
          <h3>Aandeel toevoegen</h3>
          <button className="modal__x" onClick={onClose} aria-label="Sluiten">✕</button>
        </div>
        <p className="modal__sub">Zoek op ticker of bedrijfsnaam en kies het juiste aandeel uit de lijst. Een onbekende naam wordt via AI opgebouwd (met bronvermelding).</p>
        <form className="addform" onSubmit={(e) => { e.preventDefault(); submitText(); }} autoComplete="off">
          <div className="addform__field">
            <input autoFocus className="addform__input" placeholder="bv. KPN, Accenture, AAPL…"
                   value={val} onChange={(e) => setVal(e.target.value)} disabled={busy} autoComplete="off" />
            {showDrop && (
              <div className="addform__results">
                {searching && !results.length && <div className="addresult addresult--muted">Zoeken…</div>}
                {results.map((res) => (
                  <button type="button" key={res.symbol} className="addresult" disabled={busy} onClick={() => onAdd(res)}>
                    <b className="addresult__sym">{res.symbol}</b>
                    <span className="addresult__nm">{res.name}</span>
                    <span className="addresult__ex">{res.exchange}</span>
                  </button>
                ))}
              </div>
            )}
          </div>
          <button type="submit" className="addform__btn" disabled={busy || !val.trim()}>
            {busy ? <span className="spin" /> : 'Toevoegen'}
          </button>
        </form>
        {busy && <div className="addform__busy">Kerncijfers ophalen…</div>}
        {error && <div className="addform__err">{error}</div>}
        {suggestions.length > 0 && (
          <>
            <div className="addform__label">Snel toevoegen</div>
            <div className="chips">
              {suggestions.map((s) => (
                <button key={s.ticker} className="qchip" disabled={busy} onClick={() => onAdd(s.ticker)}>
                  <b>{s.ticker}</b><span>{s.name}</span>
                </button>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [universe, setUniverse] = useStateApp(null);   // alle bekende aandelen (basis + bibliotheek + AI)
  const [watch, setWatch] = useStateApp(null);         // tickers in de volglijst (volgorde telt)
  const [user, setUser] = useStateApp(loadUser);
  const [selected, setSelected] = useStateApp(null);
  const [query, setQuery] = useStateApp('');
  const [refreshing, setRefreshing] = useStateApp(null); // ticker dat wordt bijgewerkt
  const [addOpen, setAddOpen] = useStateApp(false);
  const [adding, setAdding] = useStateApp(false);
  const [addErr, setAddErr] = useStateApp('');
  const [manage, setManage] = useStateApp(false);
  // mobiel: standaard kaarten (de tabel kapt af op smalle schermen)
  const [isMobile, setIsMobile] = useStateApp(() => typeof window !== 'undefined' && window.matchMedia('(max-width:680px)').matches);
  const [mobileLayout, setMobileLayout] = useStateApp('cards');

  useEffect(() => {
    const mq = window.matchMedia('(max-width:680px)');
    const on = () => setIsMobile(mq.matches);
    on();
    mq.addEventListener ? mq.addEventListener('change', on) : mq.addListener(on);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', on) : mq.removeListener(on); };
  }, []);

  useEffect(() => {
    fetchQuotes().then(async (base) => {
      const custom = loadLS(LS.custom, []);
      // Toegevoegde aandelen (bibliotheek + AI) lopen niet via fetchQuotes mee —
      // ververs hun marktdata live; faalt dat, dan tonen ze "Niet beschikbaar".
      const customLive = await Promise.all(custom.map((s) => withLiveData(s)));
      setUniverse([...base, ...customLive]);
      const savedW = loadLS(LS.watch, null);
      setWatch(savedW && savedW.length ? savedW : base.map((s) => s.ticker));
    });
  }, []);

  useEffect(() => { if (watch) saveLS(LS.watch, watch); }, [watch]);
  useEffect(() => { if (universe) saveLS(LS.custom, universe.filter((s) => !BASE_TICKERS.includes(s.ticker))); }, [universe]);
  useEffect(() => { saveLS(LS.user, user); }, [user]);

  // thema + dichtheid + accent op de root
  useEffect(() => {
    const root = document.documentElement;
    root.dataset.theme = t.dark ? 'dark' : 'light';
    root.dataset.density = t.density;
    const a = ACCENTS[t.accent && t.accent[0]] || ACCENTS['#2f7d5b'];
    root.style.setProperty('--under', a.under);
    root.style.setProperty('--over', a.over);
    root.style.setProperty('--fair', a.fair);
  }, [t.dark, t.density, t.accent]);

  // merge gebruikers-overrides in elk aandeel. Vintage-regel: VERSE data wint —
  // opgeslagen refresh-facts gelden alleen als ze nieuwer zijn dan het live
  // snapshot van deze pageload (direct na "Bijwerken"), of als offline-fallback
  // wanneer de live fetch faalde. Snapshots zijn atomair: nooit oude velden
  // over een verser snapshot heen mengen.
  const merged = useMemo(() => {
    if (!universe) return null;
    return universe.map((s) => {
      const u = user[s.ticker] || {};
      const facts = u.facts || {};
      const factsNewer = u.refreshedAt && (s.loadedAt == null || u.refreshedAt > s.loadedAt);
      const m = factsNewer ? { ...s, ...facts } : { ...s };
      m.fcfPerShare = (m.fcf != null && m.shares) ? m.fcf / m.shares : null;
      m.lens = u.lens || s.defaultLens;
      m.inputs = { ...s.inputs, ...(u.inputs || {}) };
      m.inputs.eps = m.eps;                        // houd afgeleide inputs in sync
      m.inputs.cashflowPerShare = m.fcfPerShare;
      // dividend volgt het getoonde snapshot, tenzij de gebruiker de slider zelf zette
      m.inputs.dividend = (u.inputs && u.inputs.dividend != null) ? u.inputs.dividend : m.dividendRate;
      m.sources = u.sources || null;
      // toon de leeftijd van het snapshot dat daadwerkelijk in beeld staat
      m.refreshedAt = factsNewer ? (u.refreshedAt || null) : (u.sources ? s.loadedAt : null);
      m.isOffline = s.loadedAt == null && !!factsNewer;
      return m;
    });
  }, [universe, user]);

  const rows = useMemo(() => {
    if (!merged || !watch) return [];
    const byT = Object.fromEntries(merged.map((s) => [s.ticker, s]));
    const q = query.trim().toLowerCase();
    return watch.map((tk) => byT[tk]).filter(Boolean)
      .filter((s) => !q || s.ticker.toLowerCase().includes(q) || s.name.toLowerCase().includes(q))
      .map((s) => ({ stock: s, ev: evaluateStock(s) }));
  }, [merged, watch, query]);

  const selStock = merged && selected ? merged.find((s) => s.ticker === selected) : null;
  const selEv = selStock ? evaluateStock(selStock) : null;

  const setLens = (ticker, lens) =>
    setUser((u) => ({ ...u, [ticker]: { ...u[ticker], lens } }));
  const patchInputs = (ticker, patch) =>
    setUser((u) => ({ ...u, [ticker]: { ...u[ticker], inputs: { ...(u[ticker]?.inputs || {}), ...patch } } }));
  // herstel de standaard-aannames: verwijder alleen de slider-overrides
  const resetInputs = (ticker) =>
    setUser((u) => {
      const e = { ...(u[ticker] || {}) };
      delete e.inputs;
      return { ...u, [ticker]: e };
    });
  const refreshStock = async (stock) => {
    setRefreshing(stock.ticker);
    try {
      const { factPatch, inputPatch, sources, refreshedAt } = await refreshFundamentals(stock);
      setUser((u) => ({
        ...u,
        [stock.ticker]: {
          ...u[stock.ticker],
          facts: { ...(u[stock.ticker]?.facts || {}), ...factPatch },
          inputs: { ...(u[stock.ticker]?.inputs || {}), ...inputPatch },
          sources, refreshedAt,
        },
      }));
    } catch (e) {
      console.warn('Bijwerken mislukt:', e);
    } finally {
      setRefreshing(null);
    }
  };

  const removeStock = (ticker) => {
    setWatch((w) => w.filter((tk) => tk !== ticker));
    if (selected === ticker) setSelected(null);
  };
  // sel = string-ticker (vrije tekst / chip) of zoekresultaat {symbol, name, exchange}
  const addStock = async (sel) => {
    setAddErr('');
    const isObj = sel && typeof sel === 'object';
    const tk = (isObj
      ? String(sel.ticker || (sel.symbol || '').split('.')[0] || sel.symbol || '')
      : String(sel)).trim().toUpperCase();
    if (!tk) return;
    if (watch.includes(tk)) { setAddErr(`${tk} staat al in je volglijst.`); return; }
    const inUni = (universe || []).find((s) => s.ticker === tk);
    if (inUni) {                       // al in de universe → alleen aan volglijst toevoegen
      setWatch((w) => [...w, tk]);
      setAddOpen(false);
      return;
    }
    setAdding(true);
    try {
      const known = isObj ? null : await liveLookup(tk);   // bootstrap-ticker → live data
      const stock = known || await buildStock(sel);        // anders uit marktdata + AI-groei
      setUniverse((u) => [...u, stock]);
      setWatch((w) => [...w, tk]);
      setAddOpen(false);
    } catch (e) {
      setAddErr(e.message || 'Toevoegen mislukt.');
    } finally {
      setAdding(false);
    }
  };

  // effectieve volglijst-layout: mobiel start op kaarten, toggle blijft werken
  const layout = isMobile ? mobileLayout : t.layout;
  const setLayout = (v) => (isMobile ? setMobileLayout(v) : setTweak('layout', v));

  const tweaksPanel = (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Weergave" />
      <TweakToggle label="Donkere modus" value={t.dark} onChange={(v) => setTweak('dark', v)} />
      <TweakRadio label="Dichtheid" value={t.density}
                  options={[{ value: 'compact', label: 'Compact' }, { value: 'comfy', label: 'Comfortabel' }]}
                  onChange={(v) => setTweak('density', v)} />
      <TweakRadio label="Volglijst" value={t.layout}
                  options={[{ value: 'table', label: 'Tabel' }, { value: 'cards', label: 'Kaarten' }]}
                  onChange={(v) => setTweak('layout', v)} />
      <TweakSection label="Waarderingskleuren" />
      <TweakColor label="Onder / over" value={t.accent}
                  options={[['#2f7d5b', '#b4452f'], ['#2563a8', '#c9912a'], ['#3d3d3d', '#888'], ['#4338ca', '#e0a23b']]}
                  onChange={(v) => setTweak('accent', v)} />
    </TweaksPanel>
  );

  if (!universe || !watch) {
    return (
      <div className="app">
        <div className="loading"><span className="loading__dot" />Live koersen ophalen…</div>
        {tweaksPanel}
      </div>
    );
  }

  return (
    <div className="app">
      <header className="topbar">
        <Brand />
        {!selStock && (
          <div className="search">
            <svg width="15" height="15" viewBox="0 0 15 15"><circle cx="6.5" cy="6.5" r="4.5" fill="none" stroke="currentColor" strokeWidth="1.4" /><path d="M10 10l3 3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" /></svg>
            <input placeholder="Zoek ticker of naam…" value={query} onChange={(e) => setQuery(e.target.value)} />
          </div>
        )}
        <div className="topbar__actions">
          {!selStock && (
            <div className="seg2 seg2--sm">
              <button className={layout === 'table' ? 'is-on' : ''} onClick={() => setLayout('table')} title="Tabel">
                <svg width="15" height="15" viewBox="0 0 15 15"><path d="M1.5 3.5h12M1.5 7.5h12M1.5 11.5h12" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" /></svg>
              </button>
              <button className={layout === 'cards' ? 'is-on' : ''} onClick={() => setLayout('cards')} title="Kaarten">
                <svg width="15" height="15" viewBox="0 0 15 15"><rect x="1.5" y="1.5" width="5" height="5" rx="1" fill="none" stroke="currentColor" strokeWidth="1.3" /><rect x="8.5" y="1.5" width="5" height="5" rx="1" fill="none" stroke="currentColor" strokeWidth="1.3" /><rect x="1.5" y="8.5" width="5" height="5" rx="1" fill="none" stroke="currentColor" strokeWidth="1.3" /><rect x="8.5" y="8.5" width="5" height="5" rx="1" fill="none" stroke="currentColor" strokeWidth="1.3" /></svg>
              </button>
            </div>
          )}
          <button className="iconbtn" onClick={() => setTweak('dark', !t.dark)} aria-label="Thema wisselen">
            {t.dark
              ? <svg width="16" height="16" viewBox="0 0 16 16"><circle cx="8" cy="8" r="3.4" fill="none" stroke="currentColor" strokeWidth="1.3" /><path d="M8 1v1.6M8 13.4V15M1 8h1.6M13.4 8H15M3 3l1.1 1.1M11.9 11.9 13 13M13 3l-1.1 1.1M4.1 11.9 3 13" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" /></svg>
              : <svg width="16" height="16" viewBox="0 0 16 16"><path d="M13.2 9.4A5.4 5.4 0 0 1 6.6 2.8 5.4 5.4 0 1 0 13.2 9.4Z" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round" /></svg>}
          </button>
        </div>
      </header>

      <main className="main">
        {selStock ? (
          <Detail stock={selStock} ev={selEv} onBack={() => setSelected(null)}
                  onSetLens={(l) => setLens(selStock.ticker, l)}
                  onPatch={(p) => patchInputs(selStock.ticker, p)}
                  onReset={() => resetInputs(selStock.ticker)}
                  onRefresh={() => refreshStock(selStock)}
                  refreshing={refreshing === selStock.ticker} />
        ) : (
          <div className="watch" data-screen-label="volglijst">
            <div className="watch__head">
              <div>
                <h1 className="watch__title">Volglijst</h1>
                <p className="watch__sub">{rows.length} {rows.length === 1 ? 'aandeel' : 'aandelen'} · klik voor de volledige waardering</p>
              </div>
              <div className="watch__actions">
                <div className="legend2">
                  <span className="legend2__i"><i className="dot dot--under" />Ondergewaardeerd</span>
                  <span className="legend2__i"><i className="dot dot--fair" />Redelijk</span>
                  <span className="legend2__i"><i className="dot dot--over" />Overgewaardeerd</span>
                </div>
                <div className="watch__btns">
                  <button className={`ghostbtn ${manage ? 'is-on' : ''}`} onClick={() => setManage((m) => !m)}>
                    {manage ? 'Klaar' : 'Beheren'}
                  </button>
                  <button className="addbtn" onClick={() => { setAddErr(''); setAddOpen(true); }}>
                    <svg width="14" height="14" viewBox="0 0 14 14"><path d="M7 2v10M2 7h10" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>
                    Aandeel toevoegen
                  </button>
                </div>
              </div>
            </div>
            {rows.length === 0 ? (
              <div className="emptywatch">
                {query.trim()
                  ? <p>Geen resultaten voor “{query.trim()}”.</p>
                  : <>
                      <p>Je volglijst is leeg.</p>
                      <button className="addbtn" onClick={() => { setAddErr(''); setAddOpen(true); }}>
                        <svg width="14" height="14" viewBox="0 0 14 14"><path d="M7 2v10M2 7h10" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>
                        Aandeel toevoegen
                      </button>
                    </>}
              </div>
            ) : layout === 'table'
              ? <WatchlistTable rows={rows} onSelect={setSelected} onRemove={removeStock} manage={manage} />
              : <WatchlistCards rows={rows} onSelect={setSelected} onRemove={removeStock} manage={manage} />}
            <p className="disclaimer">Koersen live en fundamentals per laatste rapportage via Yahoo Finance; vooruitkijkende aannames via AI-schatting — herkenbaar aan de AI-bronlabels. Waarderingen zijn modeluitkomsten, geen beleggingsadvies.</p>
          </div>
        )}
      </main>

      {tweaksPanel}
      {addOpen && <AddDialog onClose={() => setAddOpen(false)} onAdd={addStock} existing={watch} busy={adding} error={addErr} />}
    </div>
  );
}

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