// Runs in the page context (eldorado.gg): periodically polls the notifications API,
// parses boost requests and sends them to the service worker.
// The request is sent from here because SameSite cookies + Cloudflare cf_clearance
// are only sent correctly from the page's own (same-site) origin.
(function () {
  "use strict";

  const CURSOR =
    "9999-99-99 99:99:99.999999999999999-9999-9999-9999-999999999999";
  const PATH = "/api/notifications/me";
  const DEFAULT_INTERVAL = 8000;
  const MIN_INTERVAL = 5000;
  const LOG = "[BoostWatcherCS]";

  // Status re-check: the notifications feed never reports taken/cancelled, so we
  // poll each active request's detail endpoint and flip it when state != Active.
  const DETAIL_PATH = "/api/boostingOffers/boostingRequests/";
  const STATUS_INTERVAL = 15000;
  const STATUS_BATCH = 6;
  const STATUS_POOL = 60;

  let intervalMs = DEFAULT_INTERVAL;
  let timer = null;
  let statusTimer = null;
  let consecutiveErrors = 0;
  let running = false;
  const lastCheckedMap = Object.create(null); // id -> ts (in-memory, fair rotation)

  function readCookie(name) {
    const m = document.cookie.match(
      new RegExp("(?:^|; )" + name.replace(/([.$?*|{}()\[\]\\\/+^])/g, "\\$1") + "=([^;]*)")
    );
    return m ? decodeURIComponent(m[1]) : null;
  }

  function buildUrl() {
    const params = new URLSearchParams();
    params.set("cursorValue", CURSOR);
    params.set("pageDirection", "Next");
    params.set("pageSize", "20");
    // URLSearchParams can't set the same key twice; append manually
    let qs = params.toString();
    qs +=
      "&notificationReadStatuses=IsUnread&notificationReadStatuses=IsRead";
    return location.origin + PATH + "?" + qs;
  }

  function sendToSW(message) {
    try {
      chrome.runtime.sendMessage(message, () => void chrome.runtime.lastError);
    } catch (e) {
      // Context may be invalidated if the extension reloaded; ignore silently.
    }
  }

  async function poll() {
    const xsrf = readCookie("__Host-XSRF-TOKEN");
    const headers = { accept: "application/json, text/plain, */*" };
    if (xsrf) headers["x-xsrf-token"] = xsrf;

    let res;
    try {
      res = await fetch(buildUrl(), {
        method: "GET",
        credentials: "include",
        headers,
        cache: "no-store",
      });
    } catch (e) {
      consecutiveErrors++;
      sendToSW({ type: "status", status: "error", detail: String(e), ts: Date.now() });
      return;
    }

    if (res.status === 401 || res.status === 403) {
      consecutiveErrors++;
      sendToSW({ type: "status", status: "auth", httpStatus: res.status, ts: Date.now() });
      return;
    }
    if (!res.ok) {
      consecutiveErrors++;
      sendToSW({ type: "status", status: "error", httpStatus: res.status, ts: Date.now() });
      return;
    }

    let json;
    try {
      json = await res.json();
    } catch (e) {
      consecutiveErrors++;
      sendToSW({ type: "status", status: "error", detail: "json", ts: Date.now() });
      return;
    }

    consecutiveErrors = 0;
    const items = window.EldoradoParse.parseNotificationsResponse(json);
    sendToSW({ type: "boostRequests", items, ts: Date.now() });
  }

  function schedule() {
    if (timer) clearTimeout(timer);
    // Gentle backoff as errors accumulate (up to ~1 min)
    const backoff = Math.min(consecutiveErrors, 7) * 5000;
    timer = setTimeout(runOnce, intervalMs + backoff);
  }

  async function runOnce() {
    if (document.visibilityState === "hidden" && consecutiveErrors > 3) {
      // Tab hidden for a while and erroring: slow down further
    }
    await poll();
    schedule();
  }

  function applyInterval(ms) {
    intervalMs = Math.max(MIN_INTERVAL, Number(ms) || DEFAULT_INTERVAL);
  }

  // ---- status re-check (taken / cancelled / expired detection) ----
  function buildHeaders() {
    const xsrf = readCookie("__Host-XSRF-TOKEN");
    const headers = { accept: "application/json, text/plain, */*" };
    if (xsrf) headers["x-xsrf-token"] = xsrf;
    return headers;
  }

  function classifyState(state, httpStatus) {
    if (httpStatus === 404 || httpStatus === 410) return "cancelled";
    const s = String(state || "").toLowerCase();
    if (!s) return "active";
    if (s === "active") return "active";
    if (s.indexOf("cancel") >= 0) return "cancelled";
    if (s.indexOf("expir") >= 0) return "expired";
    return "taken";
  }

  async function checkOneStatus(req) {
    let res;
    try {
      res = await fetch(location.origin + DETAIL_PATH + encodeURIComponent(req.detailsId), {
        method: "GET", credentials: "include", headers: buildHeaders(), cache: "no-store",
      });
    } catch (e) { return null; }
    if (res.status === 404 || res.status === 410) return { id: req.id, status: "cancelled", state: "NotFound" };
    if (!res.ok) return null; // transient — leave as active
    let j;
    try { j = await res.json(); } catch (e) { return null; }
    return { id: req.id, status: classifyState(j.state, res.status), state: j.state || null, expireDate: j.expireDate || null };
  }

  async function checkStatuses() {
    let data;
    try { data = await new Promise((r) => chrome.storage.local.get("requests", r)); }
    catch (e) { return; }
    const list = (data && data.requests) || [];
    const pool = list
      .filter((r) => r.detailsId && (r.status || "active") === "active")
      .sort((a, b) => (b.firstSeenTs || 0) - (a.firstSeenTs || 0))
      .slice(0, STATUS_POOL)
      .sort((a, b) => (lastCheckedMap[a.id] || 0) - (lastCheckedMap[b.id] || 0))
      .slice(0, STATUS_BATCH);
    if (!pool.length) return;
    const updates = [];
    for (const req of pool) {
      const u = await checkOneStatus(req);
      lastCheckedMap[req.id] = Date.now();
      if (u && u.status !== "active") updates.push(u);
    }
    if (updates.length) sendToSW({ type: "statusUpdates", updates, ts: Date.now() });
  }

  function scheduleStatus() {
    if (statusTimer) clearTimeout(statusTimer);
    statusTimer = setTimeout(async () => {
      try { await checkStatuses(); } catch (e) {}
      scheduleStatus();
    }, STATUS_INTERVAL);
  }

  function start() {
    if (running) return;
    running = true;
    chrome.storage.local.get("settings", (data) => {
      const s = (data && data.settings) || {};
      applyInterval(s.pollInterval);
      console.log(LOG, "started, interval:", intervalMs, "ms");
      runOnce();
      scheduleStatus();
    });
  }

  // Update poll interval when settings change
  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === "local" && changes.settings) {
      const s = changes.settings.newValue || {};
      applyInterval(s.pollInterval);
    }
  });

  // Poll immediately when the tab becomes visible again (freshness)
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "visible") runOnce();
  });

  // Generic authenticated relay: lets the service worker run same-site,
  // credentialed API calls (offers, conversations) through the page origin.
  // SameSite cookies + Cloudflare cf_clearance only go from here.
  chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
    if (!msg || msg.type !== "apiFetch") return; // not for us
    (async () => {
      try {
        const xsrf = readCookie("__Host-XSRF-TOKEN");
        const headers = { accept: "application/json, text/plain, */*" };
        if (xsrf) headers["x-xsrf-token"] = xsrf;
        const init = {
          method: msg.method || "GET",
          credentials: "include",
          headers,
          cache: "no-store",
        };
        if (msg.body !== undefined && msg.body !== null) {
          headers["content-type"] = "application/json";
          init.body = typeof msg.body === "string" ? msg.body : JSON.stringify(msg.body);
        }
        const res = await fetch(location.origin + msg.path, init);
        let json = null;
        try { json = await res.json(); } catch (e) {}
        sendResponse({ ok: res.ok, status: res.status, json });
      } catch (e) {
        sendResponse({ ok: false, status: 0, error: String(e) });
      }
    })();
    return true; // async response
  });

  start();
})();
