// CAC WebSocket client — subscribes to a conversationId and dispatches
// pipeline + assistant events to a callback. Single connection per page;
// re-subscribes on convoId change. Auto-reconnects with backoff. Silently
// disables when VITE_WS_URL is empty so the REST polling path stays the
// fallback transport during local dev / preview environments.
(function () {
  const { useEffect, useRef } = React;

  function meta(name) {
    const el = document.querySelector(`meta[name="${name}"]`);
    const v = el?.getAttribute('content') || '';
    // Vite leaves the literal "%VITE_X%" placeholder when the var is missing
    // in .env — treat that as empty rather than as a URL.
    return v.startsWith('%VITE_') ? '' : v;
  }

  const WS_URL = meta('cac-ws-url');

  function getIdToken() {
    try {
      const raw = localStorage.getItem('cac.tokens');
      return raw ? (JSON.parse(raw).idToken || null) : null;
    } catch {
      return null;
    }
  }

  /**
   * useChatSocket — opens one WebSocket per (convoId, idToken) pair and
   * dispatches every received frame to onEvent(payload). Returns nothing;
   * the caller's onEvent handler is the only side-effect surface.
   *
   * No-op when WS_URL is empty (no env config) or no idToken (signed out).
   */
  function useChatSocket(convoId, onEvent) {
    const handlerRef = useRef(onEvent);
    handlerRef.current = onEvent;

    useEffect(() => {
      if (!WS_URL || !convoId) return undefined;
      const token = getIdToken();
      if (!token) return undefined;

      let ws = null;
      let closedByUs = false;
      let reconnectAttempts = 0;
      let reconnectTimer = null;

      function connect() {
        // Token in query because browsers don't allow custom headers on
        // the WS handshake. The ws-authoriser Lambda reads it from there.
        const url = `${WS_URL}?token=${encodeURIComponent(token)}`;
        ws = new WebSocket(url);

        ws.addEventListener('open', () => {
          reconnectAttempts = 0;
          try {
            ws.send(JSON.stringify({ action: 'subscribe', convoId }));
          } catch (err) {
            console.warn('[cac-ws] subscribe send failed', err);
          }
        });

        ws.addEventListener('message', (ev) => {
          let parsed;
          try {
            parsed = JSON.parse(ev.data);
          } catch {
            return; // ignore non-JSON frames (none expected from our broadcaster)
          }
          try {
            handlerRef.current?.(parsed);
          } catch (err) {
            console.warn('[cac-ws] handler threw', err);
          }
        });

        ws.addEventListener('close', () => {
          if (closedByUs) return;
          // Backoff: 0.5s, 1s, 2s, 4s, then cap at 5s. Stop after 8 retries
          // so a hard auth failure doesn't spin forever.
          reconnectAttempts += 1;
          if (reconnectAttempts > 8) return;
          const delay = Math.min(5000, 500 * 2 ** (reconnectAttempts - 1));
          reconnectTimer = setTimeout(connect, delay);
        });

        ws.addEventListener('error', () => {
          // 'close' fires after 'error' — backoff lives there.
        });
      }

      connect();

      return () => {
        closedByUs = true;
        if (reconnectTimer) clearTimeout(reconnectTimer);
        if (ws && ws.readyState <= 1) {
          try { ws.close(); } catch { /* noop */ }
        }
      };
    }, [convoId]);
  }

  window.CAC_WS = {
    useChatSocket,
    enabled: () => Boolean(WS_URL),
  };
})();
