/* live.jsx — Pollable live event source. When configured with an endpoint URL,
   periodically GETs and merges parsed events into the in-memory feed.

   Expected response shape (your Discord bot must produce this):
   {
     "events": [
       {
         "id": "msg_123",          // unique per Discord message
         "ts": 1716302400000,      // ms epoch
         "analyst": "AURELIUS",    // Discord username
         "ticker": "NVDA",
         "action": "open|add|trim|cut|close|hold|watch|expire",
         "direction": "long|short",
         "contract": "6/18 120C",  // null for shares
         "price": 1.42,
         "confidence": "high|medium|low",
         "raw": "Bought $NVDA 6/18 120C @ $1.42 …",
         "channel": "#alerts-signals",
         "channelCategory": "signals|analysis|chat",
         "messageId": "1198451224561"
       }
     ],
     "cursor": "opaque-resume-token"  // optional
   }
*/

const LIVE_CONFIG_KEY = "tw:live:config";

function defaultEndpoint() {
  if (typeof window === "undefined") return "/api/events";
  const { hostname, origin } = window.location;
  if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "") {
    return "http://localhost:3001/api/events";
  }
  return origin + "/api/events";
}

function getLiveConfig() {
  const DEFAULTS = {
    endpoint: defaultEndpoint(),
    intervalSec: 5,
    authHeader: "",
    authValue: "",
    enabled: true,
  };
  try {
    const raw = localStorage.getItem(LIVE_CONFIG_KEY);
    const saved = raw ? JSON.parse(raw) : {};
    // Always resolve the endpoint from the CURRENT origin — never trust a stale
    // cached URL (e.g. a localhost endpoint saved in dev, then opened on the
    // deployed site, which is the usual cause of "endpoint error"). Auth is
    // cookie-based now, so ignore any stored header credentials too.
    return { ...DEFAULTS, ...saved, endpoint: defaultEndpoint(), authHeader: "", authValue: "" };
  } catch { return DEFAULTS; }
}
function setLiveConfig(cfg) {
  localStorage.setItem(LIVE_CONFIG_KEY, JSON.stringify(cfg));
}

async function pollLiveEndpoint(cfg, cursor, serverIds) {
  const params = [];
  if (cursor) params.push("cursor=" + encodeURIComponent(cursor));
  if (serverIds) params.push("server_ids=" + encodeURIComponent(serverIds));
  const url = cfg.endpoint + (params.length ? (cfg.endpoint.includes("?") ? "&" : "?") + params.join("&") : "");
  const headers = {};
  if (cfg.authHeader && cfg.authValue) headers[cfg.authHeader] = cfg.authValue;
  const ctrl = new AbortController();
  const to = setTimeout(() => ctrl.abort(), 8000);
  try {
    const r = await fetch(url, { headers, signal: ctrl.signal, credentials: "include" });
    clearTimeout(to);
    if (!r.ok) throw new Error("HTTP " + r.status);
    const j = await r.json();
    return { events: Array.isArray(j.events) ? j.events : [], cursor: j.cursor || null, status: "ok" };
  } catch (e) {
    return { events: [], cursor: null, status: "error", error: e.message };
  }
}

// Hook: returns { events, status, lastPolled, error, mergeLocal }
function useLiveSource({ enabled, cfg, serverIds, seedEvents }) {
  const [events, setEvents] = useState(enabled ? [] : (seedEvents || []));
  const [status, setStatus] = useState({ phase: enabled ? "polling" : "idle", lastPolled: null, error: null, count: 0 });
  const cursorRef = useRef(null);

  // Reset on enable toggle or server selection change
  useEffect(() => {
    if (!enabled) {
      setEvents(seedEvents || []);
      setStatus({ phase: "idle", lastPolled: null, error: null, count: 0 });
    } else {
      setEvents([]);
      cursorRef.current = null;
    }
  }, [enabled, serverIds]);

  useEffect(() => {
    if (!enabled || !cfg.endpoint) return;
    let cancelled = false;
    let timer = null;

    async function tick() {
      if (cancelled) return;
      const res = await pollLiveEndpoint(cfg, cursorRef.current, serverIds);
      if (cancelled) return;
      if (res.status === "ok") {
        cursorRef.current = res.cursor;
        if (res.events.length) {
          setEvents(prev => {
            const seen = new Set(prev.map(e => e.id));
            const incoming = res.events.filter(e => !seen.has(e.id));
            return [...incoming.map(e => ({ ...e, __fresh: true })), ...prev].slice(0, 2000);
          });
        }
        setStatus(s => ({ phase: "polling", lastPolled: Date.now(), error: null, count: s.count + res.events.length }));
      } else {
        setStatus(s => ({ ...s, phase: "error", error: res.error, lastPolled: Date.now() }));
      }
      timer = setTimeout(tick, Math.max(2, cfg.intervalSec) * 1000);
    }
    tick();
    return () => { cancelled = true; if (timer) clearTimeout(timer); };
  }, [enabled, cfg.endpoint, cfg.intervalSec, cfg.authHeader, cfg.authValue, serverIds]);

  function pushLocal(e) {
    setEvents(prev => [{ ...e, __fresh: true }, ...prev]);
  }

  return { events, status, setEvents, pushLocal };
}

Object.assign(window, { useLiveSource, getLiveConfig, setLiveConfig });
