// API client + React hooks. The bot's HTTP backend lives at /api/v1/*;
// this module exposes a thin fetch wrapper plus useAPI / useSSE hooks
// so each page can declare what it needs and get loading/error/refresh
// for free.
//
// Important: useSSE shares ONE underlying EventSource across every
// hook instance (managed by an internal singleton). The page used to
// open a separate connection per component which made it look like
// the bot was spamming HTTP — it wasn't, those were long-lived SSE
// streams, but two of them per dashboard render is still wasteful.
// Now there's exactly one /api/v1/stream connection per browser tab,
// fanning out to every subscriber who filters on `topics` locally.

window.API = (() => {
  // ─── fetch helpers ────────────────────────────────────────────────
  async function get(path, params) {
    let url = path;
    if (params && Object.keys(params).length) {
      const usp = new URLSearchParams();
      for (const [k, v] of Object.entries(params)) {
        if (v === undefined || v === null || v === '') continue;
        usp.set(k, String(v));
      }
      const qs = usp.toString();
      if (qs) url += '?' + qs;
    }
    const r = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
    if (!r.ok) {
      const err = new Error('http ' + r.status);
      err.status = r.status;
      try { err.body = await r.json(); } catch (_) {}
      throw err;
    }
    return r.json();
  }

  async function post(path, body) {
    const r = await fetch(path, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body || {}),
    });
    let j = {};
    try { j = await r.json(); } catch (_) {}
    if (!r.ok) {
      const err = new Error(j.error || 'http ' + r.status);
      err.status = r.status;
      err.body = j;
      throw err;
    }
    return j;
  }

  // ─── useAPI — Promise-returning fn → {data, error, loading, refresh, offline} ──
  function useAPI(fn, deps) {
    const [state, setState] = React.useState({ data: null, error: null, loading: true, offline: false });
    const fnRef = React.useRef(fn);
    fnRef.current = fn;

    React.useEffect(() => {
      let cancelled = false;
      setState(s => ({ ...s, loading: true, error: null }));
      Promise.resolve()
        .then(() => fnRef.current())
        .then((data) => {
          if (!cancelled) setState({ data, error: null, loading: false, offline: false });
        })
        .catch((error) => {
          if (cancelled) return;
          const offline = error.status === undefined;
          setState({ data: null, error, loading: false, offline });
        });
      return () => { cancelled = true; };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, deps);

    const refresh = React.useCallback(() => {
      setState(s => ({ ...s, loading: true }));
      Promise.resolve()
        .then(() => fnRef.current())
        .then((data) => setState({ data, error: null, loading: false, offline: false }))
        .catch((error) => {
          const offline = error.status === undefined;
          setState({ data: null, error, loading: false, offline });
        });
    }, []);

    return { ...state, refresh };
  }

  // ─── SSE singleton ────────────────────────────────────────────────
  // Lazy-initialised on first useSSE call. Reconnects automatically
  // on error (browsers do this for EventSource by default but we
  // also handle explicit close/recreate when the page is visible
  // again after a long sleep).
  const sseState = {
    es: null,
    connected: false,
    listeners: new Set(),    // (event) => void
    statusListeners: new Set(), // (connected) => void
  };

  function ensureSSE() {
    if (sseState.es) return;
    // No `topics=` filter — we receive every topic and let each hook
    // filter locally. The hub's drop-on-overflow + topic-based fan-out
    // already handles backpressure.
    let es;
    try { es = new EventSource('/api/v1/stream'); }
    catch (_) { return; }
    sseState.es = es;

    const setStatus = (v) => {
      if (sseState.connected === v) return;
      sseState.connected = v;
      sseState.statusListeners.forEach(fn => { try { fn(v); } catch (_) {} });
    };

    es.addEventListener('open', () => setStatus(true));
    es.addEventListener('error', () => setStatus(false));

    const dispatch = (e) => {
      let data = null;
      try { data = JSON.parse(e.data); } catch (_) { return; }
      const ev = { topic: e.type, data, at: Date.now() };
      sseState.listeners.forEach(fn => { try { fn(ev); } catch (_) {} });
    };
    // The server names events by topic; subscribe to every known one.
    ['ready', 'ticker', 'trade', 'event', 'equity', 'scout', 'message'].forEach(t => {
      es.addEventListener(t, dispatch);
    });
  }

  // useSSE — subscribe to the shared SSE stream with optional topic
  // filter. Returns {events, connected}. `events` is a tail of the
  // last `keep` matching events (rolling window).
  function useSSE(topics, keep = 100) {
    const [connected, setConnected] = React.useState(sseState.connected);
    const [events, setEvents] = React.useState([]);
    const topicsKey = topics ? topics.join(',') : '';

    React.useEffect(() => {
      ensureSSE();
      setConnected(sseState.connected);

      const wantedTopics = topicsKey ? new Set(topicsKey.split(',')) : null;
      const onEvent = (ev) => {
        if (wantedTopics && !wantedTopics.has(ev.topic)) return;
        setEvents(prev => {
          const next = prev.length >= keep ? prev.slice(prev.length - keep + 1) : prev.slice();
          next.push(ev);
          return next;
        });
      };
      const onStatus = (v) => setConnected(v);

      sseState.listeners.add(onEvent);
      sseState.statusListeners.add(onStatus);
      return () => {
        sseState.listeners.delete(onEvent);
        sseState.statusListeners.delete(onStatus);
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [topicsKey, keep]);

    return { connected, events };
  }

  return { get, post, useAPI, useSSE };
})();
