// Top-level app — auth + routing + global state.
const { Sidebar, StatusSpine, NotificationsPanel, LoginScreen } = window.Shell;

// /api/v1/me handshake. The hook returns one of three states:
//   { checking: true }                           — splash visible
//   { authed: true, authEnabled: bool, user }    — render the app
//   { authed: false, authEnabled: bool }         — render LoginScreen
async function meCheck() {
  try {
    const r = await fetch('/api/v1/me', { credentials: 'same-origin', cache: 'no-store' });
    if (r.status === 200) return await r.json();
    if (r.status === 401) {
      const j = await r.json().catch(() => ({}));
      return { authed: false, auth_enabled: j.auth_enabled !== false };
    }
    return { authed: false, auth_enabled: true };
  } catch (_) {
    return { authed: false, auth_enabled: true, offline: true };
  }
}

async function loginRequest(user, password) {
  const r = await fetch('/api/v1/login', {
    method: 'POST',
    credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ user, password }),
  });
  const body = await r.json().catch(() => ({}));
  if (!r.ok) {
    const err = new Error(body.error || `login failed (${r.status})`);
    err.status = r.status;
    throw err;
  }
  return body;
}

async function logoutRequest() {
  try {
    await fetch('/api/v1/logout', { method: 'POST', credentials: 'same-origin' });
  } catch (_) { /* ignore */ }
}

function App() {
  const [boot, setBoot] = React.useState({ checking: true, authed: false, authEnabled: true });
  const [user, setUser] = React.useState('operator');
  const [route, setRoute] = React.useState('dashboard');
  const [notifOpen, setNotifOpen] = React.useState(false);

  const [app, setApp] = React.useState({
    user: 'operator',
    account: '',         // hydrated from /api/v1/accounts
    accounts: [],        // populated after the boot fetch
    modeOverride: null,
    halted: null,        // null = running; string reason = halted
    tickAge: 1,
    scoutRate: 0,
    now: Date.now(),
    unreadAlerts: 0,     // bumped from /api/v1/dashboard alerts in M11.2
    accountsLoaded: false,
  });

  // Boot: check session via /api/v1/me, then load accounts from API.
  React.useEffect(() => {
    let cancelled = false;
    (async () => {
      const me = await meCheck();
      if (cancelled) return;
      if (me.authed) {
        setUser(me.user || 'operator');
        setApp(s => ({ ...s, user: me.user || s.user }));
        setBoot({ checking: false, authed: true, authEnabled: !!me.auth_enabled });
      } else if (me.offline && localStorage.getItem('pb-auth') === '1') {
        // Designer preview / file:// — keep the SPA usable with mock data.
        setBoot({ checking: false, authed: true, authEnabled: false });
      } else {
        setBoot({ checking: false, authed: false, authEnabled: !!me.auth_enabled });
      }
    })();
    return () => { cancelled = true; };
  }, []);

  // Once authed, pull the live account list. The bot's app.Wire upserts
  // one row per configured account on every boot, so this is the single
  // source of truth for "which accounts exist" — no mock fallback.
  // accountsLoaded gates the page render so we never paint a stale
  // selection while the request is in flight.
  React.useEffect(() => {
    if (!boot.authed) return;
    let cancelled = false;
    (async () => {
      try {
        const raw = await window.API.get('/api/v1/accounts');
        if (cancelled) return;
        const accounts = (Array.isArray(raw) ? raw : []).map(a => ({
          id: a.label,
          accountID: a.id,
          exchange: a.exchange,
          mode: a.mode,
          bridge: a.bridge,
          coins: Array.isArray(a.coins) ? a.coins : [],
          current_coin: a.current_coin || '',
        }));
        setApp(s => ({
          ...s,
          accounts,
          account: accounts[0] ? accounts[0].id : '',
          accountsLoaded: true,
        }));
      } catch (_) {
        // Network blip / unauthorized — leave the list empty so the
        // page renders an explicit 'no account configured' state
        // instead of silently inventing one.
        setApp(s => ({ ...s, accountsLoaded: true }));
      }
    })();
    return () => { cancelled = true; };
  }, [boot.authed]);

  // Poll the kill-switch status so the StatusSpine and the Control page
  // both reflect the real state without each rendering a separate fetch.
  React.useEffect(() => {
    if (!boot.authed) return;
    let cancelled = false;
    const tick = async () => {
      try {
        const s = await window.API.get('/api/v1/control');
        if (cancelled) return;
        setApp(prev => ({ ...prev, halted: s.halted ? (s.reason || 'halted') : null }));
      } catch (_) { /* keep last known state on transient error */ }
    };
    tick();
    const id = setInterval(tick, 5000);
    return () => { cancelled = true; clearInterval(id); };
  }, [boot.authed]);

  // Wall-clock tick + tickAge derivation from the latest ticker SSE
  // event + scoutRate from the latest scout SSE event.
  //
  // Bug history: this used to depend on `events.length` so the effect
  // re-ran on every new event. But useSSE caps the buffer at `keep`,
  // so once length hit the cap it stopped changing — the effect went
  // stale and tickAge crept upward forever even though events were
  // still arriving. Fix: depend on the events ARRAY identity (every
  // setEvents returns a fresh array reference) so the effect always
  // sees the latest tail.
  const { events: tickerEvents } = window.API.useSSE(['ticker'], 4);
  const { events: scoutEvents }  = window.API.useSSE(['scout'], 4);
  const lastTickRef = React.useRef(0);
  const scoutRateRef = React.useRef(0);

  React.useEffect(() => {
    if (tickerEvents.length === 0) return;
    lastTickRef.current = tickerEvents[tickerEvents.length - 1].at;
  }, [tickerEvents]);

  React.useEffect(() => {
    if (scoutEvents.length === 0) return;
    const last = scoutEvents[scoutEvents.length - 1];
    scoutRateRef.current = Number(last?.data?.iter_per_sec) || 0;
  }, [scoutEvents]);

  // Wall-clock + status-spine update — reads from refs, no dependency
  // on the SSE arrays so the interval keeps a single stable closure.
  React.useEffect(() => {
    if (!boot.authed) return;
    const id = setInterval(() => {
      const now = Date.now();
      // tickAge semantics: null = "never seen a ticker" (badge hidden),
      // any number ≥ 0 = "this many seconds since the last frame".
      // Ticker frames on mainnet often arrive sub-second, so 0 is a
      // valid display value ("just now"); we used to hide the badge
      // when ageS===0 which made it flash on/off as soon as data
      // started flowing. Now we render <1s.
      const tickAge = lastTickRef.current
        ? Math.max(0, Math.floor((now - lastTickRef.current) / 1000))
        : null;
      setApp(s => ({ ...s, now, tickAge, scoutRate: scoutRateRef.current }));
    }, 1000);
    return () => clearInterval(id);
  }, [boot.authed]);

  // Allow child components to navigate by setting app._navTo. Used by
  // the Status Spine's halted pill (clicks → Control page) and the
  // Onboarding splash (clicks → Dashboard).
  React.useEffect(() => {
    if (app._navTo && app._navTo !== route) {
      setRoute(app._navTo);
      setApp(s => ({ ...s, _navTo: null, _navAt: null }));
    }
  }, [app._navTo, app._navAt]);

  // keyboard shortcuts: 1/D/P/T/C/O/L/R/H
  React.useEffect(() => {
    const map = { '1': 'strategy101', d: 'dashboard', p: 'pairs', t: 'trades', c: 'config', o: 'control', l: 'logs', r: 'replay', h: 'onboarding' };
    const h = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
      if (e.metaKey || e.ctrlKey || e.altKey) return;
      const r = map[e.key.toLowerCase()];
      if (r) setRoute(r);
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, []);

  const handleLogin = async (u, p) => {
    const body = await loginRequest(u, p);
    const username = body.user || u;
    setUser(username);
    setApp(s => ({ ...s, user: username }));
    setBoot({ checking: false, authed: true, authEnabled: true });
    localStorage.setItem('pb-auth', '1');
    return body;
  };

  const handleLogout = async () => {
    await logoutRequest();
    localStorage.removeItem('pb-auth');
    setBoot({ checking: false, authed: false, authEnabled: true });
  };

  if (boot.checking) {
    return (
      <div className="min-h-screen grid place-items-center bg-bg-0 text-ink-3 mono text-[12px]">
        <div className="flex items-center gap-3">
          <span className="pulse-dot text-pos-0"></span>
          <span>checking session…</span>
        </div>
      </div>
    );
  }
  if (!boot.authed) return <LoginScreen onLogin={handleLogin} authEnabled={boot.authEnabled} />;

  const openNotif = () => { setNotifOpen(true); setApp(s => ({ ...s, unreadAlerts: 0 })); };

  let Page = null;
  switch (route) {
    case 'dashboard': Page = <window.PageDashboard app={app} setApp={setApp}/>; break;
    case 'pairs':     Page = <window.PagePairs app={app}/>; break;
    case 'trades':    Page = <window.PageTrades app={app}/>; break;
    case 'config':    Page = <window.PageConfig/>; break;
    case 'control':   Page = <window.PageControl app={app} setApp={setApp}/>; break;
    case 'logs':      Page = <window.PageLogs app={app}/>; break;
    case 'replay':    Page = <window.PageReplay app={app}/>; break;
    case 'onboarding': Page = <window.PageOnboarding app={app} setApp={setApp}/>; break;
    case 'strategy101': Page = <window.PageStrategy101 app={app} setApp={setApp}/>; break;
    default:          Page = <div className="p-8 text-ink-3 mono">404</div>;
  }

  return (
    <div className="min-h-screen flex flex-col">
      <StatusSpine app={app} setApp={setApp} openNotif={openNotif} onLogout={handleLogout}/>
      <div className="flex flex-1 min-h-0">
        <Sidebar route={route} setRoute={setRoute} app={app}/>
        <main className="flex-1 overflow-auto scrollbar bg-bg-0">
          {Page}
        </main>
      </div>
      <NotificationsPanel open={notifOpen} onClose={() => setNotifOpen(false)} alerts={app.alerts || []}/>
    </div>
  );
}

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