/* ============================================================
   app.jsx — shell, navegación, fetch de datos reales
   ============================================================ */
const NAV = [
  { id: 'dashboard', label: 'Dashboard',     icon: 'dashboard' },
  { id: 'gastos',    label: 'Finanzas',      icon: 'wallet' },
  { id: 'mercado',   label: 'Inversiones',   icon: 'market' },
  { id: 'config',    label: 'Configuración', icon: 'settings' },
];
const TITLES = {
  dashboard: { h: 'Dashboard',           sub: 'Tus finanzas y el mercado, en un solo lugar' },
  gastos:    { h: 'Finanzas personales',  sub: 'Ingresos, gastos y disponible para invertir' },
  mercado:   { h: 'Inversiones',          sub: 'Mercado argentino y tu portfolio' },
  config:    { h: 'Configuración',        sub: 'Integraciones y preferencias' },
};

/* ---- estado inicial vacío mientras carga ---- */
function emptyData() {
  const S = window.STATIC;
  return {
    fx: [], brecha: null, macro: [],
    cedears:  S.CEDEARS.map(t => ({ ...t, px: null, v: null, signal: null, reason: null })),
    bonos:    S.BONOS,
    acciones: S.ACCIONES.map(t => ({ ...t, px: null, v: null, signal: null, reason: null })),
    finance:  { series: [], categories: [], tx: [] },
    opportunities: [],
    pionex:   { balances: null, bots: null, error: null },
    news:     [],
    top5:     [],
    updated: null,
    _priceMap: {},
    _mepRate: 1,
  };
}

/* ---- orquesta todos los fetches en paralelo ---- */
async function loadAll() {
  const S   = window.STATIC;
  const API = window.API;
  const equitySyms = [...S.CEDEARS, ...S.ACCIONES].map(t => t.yfSym).concat(['^MERV']);
  const bonoSyms   = S.BONOS.map(b => b.yfSym).filter(Boolean);
  const allSyms    = [...equitySyms, ...bonoSyms];

  const hasPionex = !!localStorage.getItem('fin_pionex_key');
  const [rFx, rBCRA27, rBCRA1, rQuotes, rSheet, rPionexBal, rPionexBots, rNews, rBonos] = await Promise.allSettled([
    API.fetchDolares(),
    API.fetchBCRAVariable(27),   // inflación IPC mensual
    API.fetchBCRAVariable(4),    // reservas internacionales
    API.fetchQuotes(allSyms),
    API.fetchSheetData(),
    hasPionex ? API.fetchPionex('balances') : Promise.reject(new Error('no configurado')),
    hasPionex ? API.fetchPionex('bots')     : Promise.reject(new Error('no configurado')),
    API.fetchNews(),
    API.fetchBonosLive(),
  ]);

  /* FX */
  const fx     = rFx.status === 'fulfilled' ? rFx.value : [];
  const mep    = fx.find(f => f.name === 'MEP')?.value     ?? 1;
  const blue   = fx.find(f => f.name === 'Blue')?.value    ?? 1;
  const ofic   = fx.find(f => f.name === 'Oficial')?.value ?? 1;
  const brecha = ofic ? ((blue / ofic - 1) * 100) : null;

  /* Macro */
  const macro = [];
  let inflMensual = 4; // fallback razonable
  if (rBCRA27.status === 'fulfilled') {
    const d = rBCRA27.value;
    inflMensual = d.value;
    macro.push({ name: 'Inflación mensual', value: d.value.toFixed(1), unit: '%', delta: d.delta, good: d.delta < 0, hint: `IPC · ${d.date}` });
  }
  if (rBCRA1.status === 'fulfilled') {
    const d = rBCRA1.value;
    macro.push({ name: 'Reservas BCRA', value: (d.value / 1000).toFixed(1), unit: 'B US$', delta: d.delta, good: d.delta > 0, hint: `Reservas internacionales · ${d.date}` });
  }
  const priceMap = rQuotes.status === 'fulfilled' ? rQuotes.value : {};
  const merv = priceMap['^MERV'];
  if (merv?.price) {
    const mervDelta = merv.prev ? ((merv.price / merv.prev - 1) * 100) : 0;
    macro.push({ name: 'Merval ARS', value: Math.round(merv.price).toLocaleString('es-AR'), unit: 'pts', delta: mervDelta, good: mervDelta >= 0, hint: 'Índice Merval en ARS' });
    const mervUSD = mep ? (merv.price / mep) : null;
    if (mervUSD) macro.push({ name: 'Merval USD', value: Math.round(mervUSD).toLocaleString('es-AR'), unit: 'pts', delta: mervDelta, good: mervDelta >= 0, hint: 'Merval convertido al MEP' });
  }

  /* Contexto macro para scoring */
  const macroCtx = { brechaFX: brecha ?? 50, inflMensual };

  /* Pesos del portfolio para penalizar concentración */
  const pfTemp = S.computePortfolio(priceMap, mep);
  const portfolioWeights = {};
  if (pfTemp.totalMV > 0) {
    pfTemp.rows.forEach(r => { portfolioWeights[r.tk] = r.mv / pfTemp.totalMV; });
  }

  /* Equities con scoring multi-factor */
  const enrich = (t) => {
    const q = priceMap[t.yfSym];
    if (!q?.price) return { ...t, px: null, v: null, signal: null, reason: null, score: null, indicators: null, closes: [] };
    const v      = q.prev ? ((q.price / q.prev - 1) * 100) : 0;
    const scored = S.scoreAsset(t.tk, t.cls, q.indicators, macroCtx, portfolioWeights);
    return {
      ...t,
      px:         q.price,
      v,
      closes:     q.closes ?? [],
      indicators: q.indicators ?? null,
      signal:     scored?.signal ?? S.calcSignal(v),
      reason:     scored?.reason ?? null,
      score:      scored?.score  ?? null,
    };
  };
  const cedears  = S.CEDEARS.map(enrich);
  const acciones = S.ACCIONES.map(enrich);

  /* Bonos con precios live si disponibles + TIR */
  const bonos = S.enrichBonos(priceMap, macroCtx);

  /* Oportunidades: score real, mezcla equities + bonos top */
  const oppEquity = [...cedears, ...acciones].filter(t => t.score != null && t.score >= 55);
  const oppBonos  = bonos.filter(b => b.signal === 'COMPRAR' || b.signal === 'ACUMULAR');
  const opportunities = [...oppEquity, ...oppBonos]
    .sort((a, b) => (b.score ?? 62) - (a.score ?? 62))
    .slice(0, 4)
    .map(t => ({
      tk:     t.tk,
      nm:     t.nm,
      px:     t.px,
      v:      t.v ?? null,
      signal: t.signal,
      reason: t.reason,
      score:  t.score ?? null,
      isBono: t.cls === 'Bono',
      tir:    t.tir   ?? null,
    }));

  /* Finanzas personales */
  const finance = rSheet.status === 'fulfilled'
    ? rSheet.value
    : { series: [], categories: [], tx: [] };

  /* Noticias */
  const news = rNews.status === 'fulfilled' ? (rNews.value.news || []) : [];

  /* Top 5 real (BYMA + Yahoo) */
  const top5      = rBonos.status === 'fulfilled' ? (rBonos.value.top5   || []) : [];
  const bonosLive = rBonos.status === 'fulfilled' ? (rBonos.value.assets || []) : [];

  /* Pionex real */
  const pionex = {
    balances: rPionexBal.status === 'fulfilled' ? rPionexBal.value.balances : null,
    bots:     rPionexBots.status === 'fulfilled' ? rPionexBots.value.bots   : null,
    error:    rPionexBal.status === 'rejected'   ? rPionexBal.reason?.message : null,
  };

  const now = new Date();
  const updated = now.toLocaleDateString('es-AR', { day: '2-digit', month: 'short', year: 'numeric' })
    + ' · ' + String(now.getHours()).padStart(2,'0') + ':' + String(now.getMinutes()).padStart(2,'0');

  const errors = {
    fx:     rFx.status     === 'rejected' ? rFx.reason?.message    : null,
    macro:  (rBCRA27.status === 'rejected' && rBCRA1.status === 'rejected') ? rBCRA27.reason?.message : null,
    quotes: rQuotes.status === 'rejected' ? rQuotes.reason?.message : null,
    sheet:  rSheet.status  === 'rejected' ? rSheet.reason?.message  : null,
  };

  return { fx, brecha, macro, cedears, bonos: bonosLive.filter(a=>a.cls==='Bono').length ? bonosLive.filter(a=>a.cls==='Bono') : bonos, acciones, finance, opportunities, pionex, news, top5, updated, _priceMap: priceMap, _mepRate: mep, errors };
}

/* ============================================================
   Lock screen
   ============================================================ */
async function hashPin(pin) {
  const str = pin + 'fin-app-salt';
  if (window.crypto?.subtle) {
    const enc = new TextEncoder().encode(str);
    const buf = await crypto.subtle.digest('SHA-256', enc);
    return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2,'0')).join('');
  }
  // Fallback djb2 para HTTP local
  let h = 5381;
  for (let i = 0; i < str.length; i++) { h = (((h << 5) + h) ^ str.charCodeAt(i)) >>> 0; }
  return h.toString(16).padStart(8, '0');
}
window.FIN_HASH = hashPin;

function LockScreen({ onUnlock }) {
  const [pin,     setPin]     = useState('');
  const [confirm, setConfirm] = useState('');
  const [err,     setErr]     = useState('');
  const [busy,    setBusy]    = useState(false);
  const isSetup = !localStorage.getItem('fin_pin_hash');

  async function submit(e) {
    e.preventDefault();
    setBusy(true); setErr('');
    if (isSetup) {
      if (pin.length < 4) { setErr('Mínimo 4 caracteres'); setBusy(false); return; }
      if (pin !== confirm) { setErr('Los PINs no coinciden'); setBusy(false); return; }
      localStorage.setItem('fin_pin_hash', await hashPin(pin));
      onUnlock();
    } else {
      const h = await hashPin(pin);
      if (h === localStorage.getItem('fin_pin_hash')) { onUnlock(); }
      else { setErr('PIN incorrecto'); setBusy(false); }
    }
  }

  return (
    <div className="lock-screen">
      <div className="lock-card">
        <div className="brand-mark" style={{ margin: '0 auto 20px' }}>f</div>
        <div style={{ textAlign: 'center', marginBottom: 24 }}>
          <div style={{ fontSize: 17, fontWeight: 600, marginBottom: 4 }}>
            {isSetup ? 'Configurar acceso' : 'finanzas.mitempo.app'}
          </div>
          <div className="muted-note" style={{ fontSize: 12.5 }}>
            {isSetup ? 'Primera vez. Creá un PIN para proteger la app.' : 'Ingresá tu PIN para continuar'}
          </div>
        </div>
        <form onSubmit={submit}>
          <div className="field" style={{ marginBottom: 12 }}>
            <label className="field-label">{isSetup ? 'PIN nuevo' : 'PIN'}</label>
            <input className="inp" type="password" value={pin} onChange={e => setPin(e.target.value)}
              autoFocus placeholder="••••••" autoComplete="current-password" />
          </div>
          {isSetup && (
            <div className="field" style={{ marginBottom: 12 }}>
              <label className="field-label">Confirmar PIN</label>
              <input className="inp" type="password" value={confirm} onChange={e => setConfirm(e.target.value)}
                placeholder="••••••" autoComplete="new-password" />
            </div>
          )}
          {err && (
            <div className="alert warn" style={{ marginBottom: 12, padding: '8px 12px' }}>
              <Icon name="alertTri" size={15} className="a-ico" /><span className="a-body">{err}</span>
            </div>
          )}
          <button className="btn btn-primary" style={{ width: '100%', justifyContent: 'center' }} disabled={busy}>
            <Icon name="check" size={15} />{isSetup ? 'Crear PIN' : 'Entrar'}
          </button>
        </form>
      </div>
    </div>
  );
}

/* ============================================================
   Sidebar / MobileNav
   ============================================================ */
function Sidebar({ page, go }) {
  return (
    <aside className="sidebar">
      <div className="brand">
        <div className="brand-mark">f</div>
        <div className="brand-name"><b>finanzas</b><span>.mitempo</span></div>
      </div>
      <div className="nav-label">Principal</div>
      {NAV.map(n => (
        <div key={n.id} className={`nav-item ${page === n.id ? 'active' : ''}`} onClick={() => go(n.id)}>
          <Icon name={n.icon} size={17} className="nav-ico" />{n.label}
        </div>
      ))}
      <div className="nav-spacer" />
      <div className="sidebar-foot">
        <div className="avatar">MT</div>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600 }}>Mi Tempo</div>
          <div style={{ fontSize: 11.5, color: 'var(--text-3)' }}>plan personal</div>
        </div>
      </div>
    </aside>
  );
}
function MobileNav({ page, go }) {
  return (
    <nav className="mobile-nav">
      {NAV.map(n => (
        <div key={n.id} className={`mn-item ${page === n.id ? 'active' : ''}`} onClick={() => go(n.id)}>
          <Icon name={n.icon} size={20} />{n.label}
        </div>
      ))}
    </nav>
  );
}

/* ---- tweaks ---- */
const TWEAK_DEFAULTS = {
  accent: ['#00ff88', '#4488ff'], bgTone: 'Carbón',
  density: 'Cómoda', radius: 6, sans: 'IBM Plex Sans', mono: 'IBM Plex Mono',
};
const BG_TONES = {
  'Carbón':    { bg: '#1d1f23', bg2: '#191b1e', s: '#252830', s2: '#2c303a', s3: '#343945' },
  'Negro':     { bg: '#131417', bg2: '#0e0f11', s: '#1b1d22', s2: '#23262d', s3: '#2c2f37' },
  'Gris azul': { bg: '#21242b', bg2: '#1c1f25', s: '#2a2e37', s2: '#323742', s3: '#3a404c' },
};
const ACCENTS = {
  'Verde neón': ['#00ff88', '#4488ff'], 'Esmeralda': ['#34d399', '#60a5fa'],
  'Teal/Índigo': ['#2dd4bf', '#818cf8'], 'Lima': ['#a3e635', '#38bdf8'],
};
function applyTweaks(t) {
  const r = document.documentElement.style;
  r.setProperty('--pos', t.accent[0]); r.setProperty('--info', t.accent[1]);
  const tone = BG_TONES[t.bgTone] || BG_TONES['Carbón'];
  r.setProperty('--bg', tone.bg); r.setProperty('--bg-2', tone.bg2);
  r.setProperty('--surface', tone.s); r.setProperty('--surface-2', tone.s2); r.setProperty('--surface-3', tone.s3);
  const c = t.density === 'Compacta';
  r.setProperty('--row-pad-y', c ? '7px' : '11px'); r.setProperty('--cell-fs', c ? '12.5px' : '13.5px');
  r.setProperty('--radius', t.radius + 'px');
  r.setProperty('--radius-sm', Math.max(2, t.radius - 2) + 'px');
  r.setProperty('--radius-lg', (t.radius + 4) + 'px');
  r.setProperty('--sans', `'${t.sans}', system-ui, sans-serif`);
  r.setProperty('--mono', `'${t.mono}', ui-monospace, monospace`);
}
function FinTweaks({ t, setTweak }) {
  return (
    <TweaksPanel>
      <TweakSection label="Acentos" />
      <TweakColor label="Paleta" value={t.accent} options={Object.values(ACCENTS)} onChange={v => setTweak('accent', v)} />
      <TweakSection label="Superficie" />
      <TweakRadio label="Tono de fondo" value={t.bgTone} options={Object.keys(BG_TONES)} onChange={v => setTweak('bgTone', v)} />
      <TweakSlider label="Radio de esquinas" value={t.radius} min={0} max={14} unit="px" onChange={v => setTweak('radius', v)} />
      <TweakSection label="Tablas" />
      <TweakRadio label="Densidad" value={t.density} options={['Cómoda','Compacta']} onChange={v => setTweak('density', v)} />
      <TweakSection label="Tipografía" />
      <TweakSelect label="Texto" value={t.sans} options={['IBM Plex Sans','Hanken Grotesk','system-ui']} onChange={v => setTweak('sans', v)} />
      <TweakSelect label="Números" value={t.mono} options={['IBM Plex Mono','JetBrains Mono','Space Mono']} onChange={v => setTweak('mono', v)} />
    </TweaksPanel>
  );
}

/* ============================================================
   App
   ============================================================ */
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useEffect(() => { applyTweaks(t); }, [t]);

  const [unlocked, setUnlocked] = useState(false);

  const [page,    setPage]    = useState(() => localStorage.getItem('fin_page') || 'dashboard');
  const [data,    setData]    = useState(emptyData);
  const [loading, setLoading] = useState(true);
  const [errors,  setErrors]  = useState({});
  const [spinning,setSpinning]= useState(false);
  const [invPct,  setInvPct]  = useState(40);
  const [invSplit,setInvSplit]= useState(40);
  const [integ,   setInteg]   = useState({ sheet: false, balanz: true, cocos: true });
  const { onHover, node } = useTooltip();

  const fetchData = useCallback(async () => {
    setSpinning(true);
    try {
      const d = await loadAll();
      setData(d);
      setErrors(d.errors || {});
    } catch (e) {
      setErrors({ general: e.message });
    } finally {
      setLoading(false);
      setSpinning(false);
    }
  }, []);

  useEffect(() => { fetchData(); }, [fetchData]);

  const go = useCallback(p => {
    setPage(p);
    localStorage.setItem('fin_page', p);
    document.querySelector('.content')?.scrollTo(0, 0);
  }, []);

  const tt = TITLES[page];

  if (!unlocked) return <LockScreen onUnlock={() => setUnlocked(true)} />;

  return (
    <div className="app">
      {node}
      <Sidebar page={page} go={go} />
      <div className="main">
        <header className="topbar">
          <div className="mobile-topbrand">
            <div className="brand-mark" style={{ width: 26, height: 26, fontSize: 15 }}>f</div>
          </div>
          <div>
            <h1>{tt.h}</h1>
            <div className="sub">{tt.sub}</div>
          </div>
          <div className="topbar-right">
            {loading
              ? <span className="last-upd"><span className="dot-live" style={{ background: 'var(--warn)' }} /><span className="lu-text">Cargando…</span></span>
              : <span className="last-upd"><span className="dot-live" /><span className="lu-text">Actualizado {data.updated}</span></span>
            }
            <button className="btn btn-ghost btn-sm" onClick={fetchData} disabled={spinning}>
              <Icon name="refresh" size={14} className={spinning ? 'spin' : ''} />
              <span className="lu-text">Actualizar</span>
            </button>
          </div>
        </header>

        {page === 'mercado' && data.fx.length > 0 && (
          <div className="subbar">
            <span style={{ display:'flex', alignItems:'center', gap:8, color:'var(--text-3)' }}><Icon name="scale" size={14} />Brecha</span>
            <span className="num" style={{ fontWeight:600, color:'var(--warn)', fontSize:14 }}>{data.brecha != null ? fmtNum(data.brecha,1)+'%' : '—'}</span>
            <span style={{ color:'var(--border-strong)' }}>|</span>
            {data.fx.map(f => (
              <span key={f.name} style={{ display:'flex', alignItems:'center', gap:7 }}>
                <span style={{ color:'var(--text-3)', fontSize:11.5, textTransform:'uppercase', letterSpacing:'0.05em' }}>{f.name}</span>
                <span className="num" style={{ fontWeight:600 }}>${fmtNum(f.value,1)}</span>
              </span>
            ))}
          </div>
        )}

        <main className="content">
          {page === 'dashboard' && <Dashboard go={go} invPct={invPct} data={data} loading={loading} errors={errors} news={data.news} top5={data.top5} />}
          {page === 'gastos'    && <Gastos go={go} invPct={invPct} data={data} loading={loading} errors={errors} sheetConnected={integ.sheet} onConnectSheet={() => setInteg(s=>({...s,sheet:true}))} />}
          {page === 'mercado'   && <Mercado go={go} onHover={onHover} data={data} loading={loading} errors={errors} />}
          {page === 'config'    && <Config integ={integ} setInteg={setInteg} invPct={invPct} setInvPct={setInvPct} invSplit={invSplit} setInvSplit={setInvSplit} />}
        </main>
      </div>
      <MobileNav page={page} go={go} />
      <FinTweaks t={t} setTweak={setTweak} />
    </div>
  );
}

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