'use strict';

const allowlist =
  typeof require !== 'undefined'
    ? require('./email-domains-allowlist')
    : typeof globalThis !== 'undefined' && globalThis.RbxEmailDomains
      ? globalThis.RbxEmailDomains
      : null;

(function attachEmailParserExport(api) {
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = api;
  }
  const g = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : {};
  g.RbxEmailParser = api;
})( (function emailParserFactory() {

const DEV =
  (typeof process !== 'undefined' &&
    process.env &&
    (process.env.NODE_ENV === 'development' || process.env.DEBUG_EMAIL_PARSER === '1')) ||
  false;

function debugLog(...args) {
  if (DEV) console.log('[email-parser]', ...args);
}

function decodeEmailHtmlEntities(text) {
  if (!text || typeof text !== 'string') return text;
  return text
    .replace(/&#0*64;/gi, '@')
    .replace(/&#x0*40;/gi, '@')
    // Quotes often appear as \x22 / %22 / &#34; / &#x22; / &quot; near JSON/URL encoded blobs.
    // We normalize them to a real quote so normalizeEmail can strip them safely.
    .replace(/\\x0*22/gi, '"')
    .replace(/&#0*34;/gi, '"')
    .replace(/&#x0*22;/gi, '"')
    .replace(/&quot;/gi, '"')
    .replace(/%22/gi, '"')
    .replace(/&amp;/gi, '&')
    .replace(/&period;/gi, '.')
    .replace(/&#0*46;/gi, '.');
}

function normalizeEmail(raw) {
  if (raw == null) return '';
  let s = decodeEmailHtmlEntities(String(raw).trim());
  // Some blobs contain escaped quote markers where "\" is not captured by the email regex,
  // leaving a leading "x22" (from \x22) attached to the email local-part.
  s = s.replace(/^mailto:/i, '').trim();
  s = s.replace(/^(?:x0*22|u0*022)+/i, '');
  s = s.replace(/[<>"]/g, '').trim();
  s = s.replace(/\s+/g, '');
  if (!s) return '';
  return s.toLowerCase();
}

const EMAIL_REGEX =
  /[a-z0-9][a-z0-9._%+-]*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+/gi;

const JUNK_LOCAL =
  /^(noreply|no-reply|donotreply|do-not-reply|mailer-daemon|postmaster|bounce|bounces|support|help|info|abuse|newsletter|notifications?|team|security|privacy|marketing|sales|hello|contact|daemon|mailer|mshelp|phishing)@/i;

const JUNK_DOMAIN =
  /@(chromium|android|gstatic|googleusercontent|youtube|ggpht|google-analytics)\.com$/i;

/** OEM / phone vendor hosts — not mail providers (e.g. redmi.com from device cookies). */
const JUNK_VENDOR_EMAIL_HOST =
  /^(redmi|xiaomi|miui|oppo|vivo|realme|oneplus|huawei|honor|iqoo|nothing|tecno|infinix|meizu|zte|lenovo|asus|motorola|nokia|samsungmobile)(\.|$)/i;

const EXAMPLE_OR_INTERNAL = /@(example\.(com|org|net)|test\.com|localhost|w3\.org|schema\.org)$/i;

function isVendorNoiseEmailHost(host) {
  const h = String(host || '')
    .toLowerCase()
    .trim();
  if (!h) return false;
  return JUNK_VENDOR_EMAIL_HOST.test(h);
}

function getEmailHost(email) {
  return allowlist ? allowlist.getEmailHost(email) : normalizeEmail(email).split('@')[1] || '';
}

function isAllowedEmailDomain(email) {
  if (!allowlist) return true;
  return allowlist.isAllowedEmailDomain(email);
}

function getEmailProvider(email) {
  if (!allowlist) return 'Unknown';
  return allowlist.getEmailProvider(email);
}

function isValidEmailFormat(email) {
  const e = normalizeEmail(email);
  if (!e || e.length < 6 || e.length > 254) return false;
  if (!/^[a-z0-9][a-z0-9._%+-]*@/.test(e)) return false;
  const parts = e.split('@');
  if (parts.length !== 2) return false;
  const [local, host] = parts;
  if (!local || !host || host.indexOf('.') === -1) return false;
  if (local.startsWith('.') || local.endsWith('.') || host.startsWith('.') || host.endsWith('.')) return false;
  return /^[a-z0-9][a-z0-9._%+-]*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/.test(e);
}

function isGmailAddress(email) {
  const e = normalizeEmail(email);
  return /@(gmail|googlemail)\.com$/.test(e);
}

function isGoogleAccountEmail(email) {
  const e = normalizeEmail(email);
  if (!isValidEmailFormat(e)) return false;
  if (isGmailAddress(e)) return true;
  if (e.endsWith('@google.com')) return false;
  return false;
}

/** Reject regex false-positives from minified HTML (e.g. ib@ep.n6r, 5q@x.asp). */
function isPlausibleResolvedEmail(email) {
  const e = normalizeEmail(email);
  if (!isValidEmailFormat(e)) return false;
  if (isAllowedEmailDomain(e)) return true;
  const host = getEmailHost(e);
  const labels = host.split('.').filter(Boolean);
  if (labels.length < 2) return false;
  const tld = labels[labels.length - 1];
  if (!tld || tld.length < 2 || /\d/.test(tld) || !/^[a-z]{2,63}$/.test(tld)) return false;
  if (labels.some((l) => l.length < 2)) return false;
  const secondLevel = labels.length >= 2 ? labels[labels.length - 2] : '';
  const commonSecondLevel = new Set(['co', 'com', 'org', 'net', 'ac', 'gov', 'edu', 'ne']);
  if (tld.length === 2 && commonSecondLevel.has(secondLevel)) return true;
  if (tld.length >= 3) return true;
  return false;
}

function scoreEmailCandidate(email) {
  const e = normalizeEmail(email);
  if (!isValidEmailFormat(e)) return -1000;
  if (!isAllowedEmailDomain(e)) return -300;
  if (JUNK_LOCAL.test(e)) return -500;
  if (JUNK_DOMAIN.test(e)) return -400;
  if (isVendorNoiseEmailHost(getEmailHost(e))) return -600;
  if (e.endsWith('@google.com')) return -400;
  if (EXAMPLE_OR_INTERNAL.test(e)) return -800;

  if (isGmailAddress(e)) return 600;

  const host = getEmailHost(e);
  const tierBoost = {
    'outlook.com': 520, 'hotmail.com': 520, 'live.com': 510,
    'yahoo.com': 490, 'ymail.com': 480, 'rocketmail.com': 475,
    'mail.ru': 485, 'bk.ru': 480, 'inbox.ru': 475, 'list.ru': 470,
    'yandex.ru': 480, 'yandex.com': 478, 'ya.ru': 475,
    'icloud.com': 470, 'me.com': 465, 'mac.com': 460,
    'proton.me': 465, 'protonmail.com': 460, 'pm.me': 455,
    'aol.com': 450,
    'fastmail.com': 445,
    'zoho.com': 440,
    'gmx.com': 435, 'gmx.de': 433, 'gmx.net': 430,
    'web.de': 428,
    'tutanota.com': 425, 'tuta.com': 422,
    'qq.com': 420,
    '163.com': 415, '126.com': 412,
    'ukr.net': 418,
    'rambler.ru': 410,
    'naver.com': 408
  };
  if (tierBoost[host]) return tierBoost[host] + 50;
  return 280;
}

const STRUCTURED_EMAIL_ATTRS = [
  /"primaryEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"email"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"userEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"verifiedEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"accountEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"googleEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /"outlookEmail"\s*:\s*"([^"]+@[^"]+)"/gi,
  /mailto:([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})/gi,
  /data-email=["']([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})["']/gi,
  /content=["']([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})["']/gi
];

function createParseStats() {
  return {
    rawFound: 0,
    valid: 0,
    invalidSkipped: 0,
    unsupportedSkipped: 0,
    duplicatesRemoved: 0
  };
}

function shouldAcceptEmail(email, stats) {
  const n = normalizeEmail(email);
  if (!n) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (stats) stats.rawFound++;
  if (!isValidEmailFormat(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (!isAllowedEmailDomain(n)) {
    if (stats) stats.unsupportedSkipped++;
    debugLog('unsupported domain skipped', n);
    return '';
  }
  if (isVendorNoiseEmailHost(getEmailHost(n))) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (scoreEmailCandidate(n) <= -100) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (stats) stats.valid++;
  return n;
}

/** Google session resolve: any real user email in cookies/HTML (incl. Workspace), not allowlist-only. */
function shouldAcceptGoogleResolveEmail(email, stats) {
  const n = normalizeEmail(email);
  if (!n) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (stats) stats.rawFound++;
  if (!isValidEmailFormat(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (JUNK_LOCAL.test(n) || JUNK_DOMAIN.test(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (n.endsWith('@google.com')) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (EXAMPLE_OR_INTERNAL.test(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (/^(noreply|no-reply|mailer-daemon|postmaster)@/i.test(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (!isPlausibleResolvedEmail(n)) {
    if (stats) stats.invalidSkipped++;
    return '';
  }
  if (!isAllowedEmailDomain(n)) {
    if (stats) stats.unsupportedSkipped++;
  }
  if (stats) stats.valid++;
  return n;
}

function addGoogleResolveEmail(raw, intoSet, stats) {
  const n = shouldAcceptGoogleResolveEmail(raw, stats);
  if (!n || !intoSet) return;
  if (intoSet.has(n)) {
    if (stats) stats.duplicatesRemoved++;
    return;
  }
  intoSet.add(n);
}

function addEmailToSet(raw, intoSet, stats) {
  const n = shouldAcceptEmail(raw, stats);
  if (!n || !intoSet) return;
  if (intoSet.has(n)) {
    if (stats) stats.duplicatesRemoved++;
    return;
  }
  intoSet.add(n);
  debugLog('accepted', n);
}

function extractEmailsFromText(text, stats, opts) {
  if (!text || typeof text !== 'string') return [];
  const relaxed = opts && opts.googleResolve === true;
  const raw = decodeEmailHtmlEntities(text);
  const found = new Set();
  const st = stats || createParseStats();
  const add = relaxed
    ? (v) => addGoogleResolveEmail(v, found, st)
    : (v) => addEmailToSet(v, found, st);

  for (const re of STRUCTURED_EMAIL_ATTRS) {
    re.lastIndex = 0;
    let m;
    while ((m = re.exec(raw)) !== null) {
      add((m[1] || '').replace(/\\"/g, '"'));
    }
  }

  EMAIL_REGEX.lastIndex = 0;
  let m;
  while ((m = EMAIL_REGEX.exec(raw)) !== null) {
    add(m[0]);
  }

  const list = [...found];
  list.sort((a, b) => scoreEmailCandidate(b) - scoreEmailCandidate(a));
  if (stats && DEV) {
    debugLog('extractEmailsFromText stats', { ...st, returned: list.length });
  }
  return list;
}

function extractLikelyUserEmails(html) {
  return extractEmailsFromText(html);
}

function extractLikelyUserEmail(html) {
  const all = extractLikelyUserEmails(html);
  return all.length ? all[0] : null;
}

function extractEmailsFromStructured(input, intoSet, depth, stats) {
  const d = depth || 0;
  if (d > 16 || intoSet == null) return;
  if (input == null) return;
  const st = stats || null;

  if (typeof input === 'string') {
    for (const em of extractEmailsFromText(input, st)) intoSet.add(em);
    try {
      if ((input.startsWith('{') && input.endsWith('}')) || (input.startsWith('[') && input.endsWith(']'))) {
        extractEmailsFromStructured(JSON.parse(input), intoSet, d + 1, st);
      }
    } catch (_) {}
    return;
  }

  if (Array.isArray(input)) {
    for (const item of input) extractEmailsFromStructured(item, intoSet, d + 1, st);
    return;
  }

  if (typeof input === 'object') {
    for (const [k, v] of Object.entries(input)) {
      if (/email/i.test(k) && (typeof v === 'string' || typeof v === 'number')) {
        addEmailToSet(String(v), intoSet, st);
      }
      extractEmailsFromStructured(v, intoSet, d + 1, st);
    }
  }
}

function collectEmailsFromLsidValue(lsidRaw, intoSet, stats, opts) {
  if (!lsidRaw || typeof lsidRaw !== 'string' || !intoSet) return;
  const st = stats || null;
  const relaxed = opts && opts.googleResolve === true;
  extractEmailsFromText(lsidRaw, st, relaxed ? { googleResolve: true } : undefined).forEach((em) =>
    intoSet.add(em)
  );
  try {
    const dec = decodeURIComponent(lsidRaw);
    extractEmailsFromText(dec, st, relaxed ? { googleResolve: true } : undefined).forEach((em) =>
      intoSet.add(em)
    );
  } catch (_) {}
  const parts = lsidRaw.split(/[|:\s,;]+/);
  for (const p of parts) {
    if (p.length < 8) continue;
    extractEmailsFromText(p, st, relaxed ? { googleResolve: true } : undefined).forEach((em) =>
      intoSet.add(em)
    );
    if (p.length < 20) continue;
    try {
      let s = '';
      if (typeof Buffer !== 'undefined') {
        s = Buffer.from(p, 'base64').toString('utf8');
      } else if (typeof atob === 'function') {
        const bin = atob(p.replace(/-/g, '+').replace(/_/g, '/'));
        const bytes = new Uint8Array(bin.length);
        for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
        s = typeof TextDecoder !== 'undefined' ? new TextDecoder().decode(bytes) : bin;
      }
      if (s) {
        extractEmailsFromText(s, st, relaxed ? { googleResolve: true } : undefined).forEach((em) =>
          intoSet.add(em)
        );
      }
    } catch (_) {}
  }
}

function stripGoogleJsonPrefix(raw) {
  if (!raw || typeof raw !== 'string') return '';
  let s = raw.trim();
  if (s.startsWith(")]}'")) {
    const nl = s.indexOf('\n');
    s = nl >= 0 ? s.slice(nl + 1).trim() : s.replace(/^\)\]\}'?\s*/, '').trim();
  }
  return s;
}

/** accounts.google.com/ListAccounts JSON (Chromium account list). */
function collectEmailsFromListAccountsResponse(text, intoSet, stats, opts) {
  if (!text || typeof text !== 'string' || !intoSet) return;
  const st = stats || null;
  const relaxed = opts && opts.googleResolve === true;
  const stripped = stripGoogleJsonPrefix(text);
  if (!stripped) return;
  if (relaxed) {
    for (const em of extractEmailsFromText(stripped, st, { googleResolve: true })) intoSet.add(em);
    try {
      for (const em of extractEmailsFromText(JSON.stringify(JSON.parse(stripped)), st, {
        googleResolve: true
      })) {
        intoSet.add(em);
      }
    } catch (_) {}
  } else {
    extractEmailsFromStructured(stripped, intoSet, 0, st);
    try {
      extractEmailsFromStructured(JSON.parse(stripped), intoSet, 0, st);
    } catch (_) {}
  }
  for (const em of extractEmailsFromText(text, st, relaxed ? { googleResolve: true } : undefined)) {
    intoSet.add(em);
  }
}

const GOOGLE_EMAIL_COOKIE_KEYS = new Set([
  'LSID',
  'ACCOUNT_CHOOSER',
  'GAPS',
  '__Host-GAPS',
  '__Host-1PLSID',
  '__Host-3PLSID',
  'OSID',
  '__Secure-OSID',
  'email'
]);

function collectEmailsFromAuthObject(googleAuth, intoSet, stats, opts) {
  if (!googleAuth || typeof googleAuth !== 'object' || !intoSet) return;
  const relaxed = opts && opts.googleResolve === true;
  if (googleAuth.email) {
    if (relaxed) addGoogleResolveEmail(googleAuth.email, intoSet, stats);
    else addEmailToSet(googleAuth.email, intoSet, stats);
  }
  for (const key of GOOGLE_EMAIL_COOKIE_KEYS) {
    if (!googleAuth[key]) continue;
    if (key === 'email') continue;
    collectEmailsFromLsidValue(String(googleAuth[key]), intoSet, stats, opts);
  }
}

/** Deep scan of auth object + chrome jar strings (legacy; avoid for session email). */
function collectEmailsFromAuthObjectDeep(googleAuth, intoSet, stats) {
  if (!googleAuth || typeof googleAuth !== 'object' || !intoSet) return;
  extractEmailsFromStructured(googleAuth, intoSet, 0, stats);
  collectEmailsFromAuthObject(googleAuth, intoSet, stats);
}

function tryEmailFromLsidValue(lsidRaw) {
  const pool = new Set();
  const stats = createParseStats();
  collectEmailsFromLsidValue(lsidRaw, pool, stats);
  if (pool.size === 0) return null;
  return [...pool].sort((a, b) => scoreEmailCandidate(b) - scoreEmailCandidate(a))[0];
}

const MAX_MERGED_EMAILS = 30;

function ingestSource(src, addRaw, stats) {
  if (src == null) return;
  if (typeof src === 'string') {
    for (const em of extractEmailsFromText(src, stats)) addRaw(em, 'string');
    return;
  }
  if (src instanceof Set) {
    for (const em of src) addRaw(em, 'set');
    return;
  }
  if (Array.isArray(src)) {
    for (const item of src) {
      if (typeof item === 'string') addRaw(item, 'array');
      else if (item && typeof item.email === 'string') addRaw(item.email, 'array-object');
      else ingestSource(item, addRaw, stats);
    }
    return;
  }
  if (typeof src === 'object') {
    const pool = new Set();
    extractEmailsFromStructured(src, pool, 0, stats);
    if (src.googleAuth) collectEmailsFromAuthObject(src.googleAuth, pool, stats);
    for (const em of pool) addRaw(em, 'object');
  }
}

/**
 * Merge many sources: normalize, validate, allowlist, dedupe.
 */
function mergeEmailCandidates(...sources) {
  const seen = new Set();
  const scored = [];
  const stats = createParseStats();

  const addRaw = (raw, sourceTag) => {
    const n = shouldAcceptEmail(raw, stats);
    if (!n || seen.has(n)) {
      if (n && seen.has(n)) stats.duplicatesRemoved++;
      return;
    }
    seen.add(n);
    scored.push({ email: n, score: scoreEmailCandidate(n), source: sourceTag });
    debugLog('merge', n, sourceTag);
  };

  for (const src of sources) ingestSource(src, addRaw, stats);

  scored.sort((a, b) => {
    const gA = isGmailAddress(a.email) ? 1 : 0;
    const gB = isGmailAddress(b.email) ? 1 : 0;
    if (gB !== gA) return gB - gA;
    return b.score - a.score;
  });

  const emails = scored.map((x) => x.email).slice(0, MAX_MERGED_EMAILS);
  if (DEV) debugLog('mergeEmailCandidates stats', stats, 'count', emails.length);
  return emails;
}

function collectEmailsWithStats(...sources) {
  const stats = createParseStats();
  const emails = mergeEmailCandidates(...sources);
  return { emails, stats };
}

/** Google login emails: keep Workspace / non-consumer domains from cookie resolve (not allowlist-only). */
function mergeGoogleEmailCandidates(resolvedList, clientPrimary, clientArray) {
  const seen = new Set();
  const scored = [];
  const stats = createParseStats();

  const addResolve = (raw, sourceTag) => {
    const n = shouldAcceptGoogleResolveEmail(raw, stats);
    if (!n || seen.has(n)) {
      if (n && seen.has(n)) stats.duplicatesRemoved++;
      return;
    }
    seen.add(n);
    scored.push({ email: n, score: scoreEmailCandidate(n), source: sourceTag });
  };

  if (Array.isArray(resolvedList)) {
    for (const raw of resolvedList) addResolve(raw, 'resolved');
  }
  if (clientPrimary) addResolve(clientPrimary, 'client');
  if (Array.isArray(clientArray)) {
    for (const raw of clientArray) addResolve(raw, 'clientArray');
  }

  scored.sort((a, b) => {
    const gA = isGmailAddress(a.email) ? 1 : 0;
    const gB = isGmailAddress(b.email) ? 1 : 0;
    if (gB !== gA) return gB - gA;
    return b.score - a.score;
  });

  return scored.map((x) => x.email).slice(0, MAX_MERGED_EMAILS);
}

return {
  debugLog,
  normalizeEmail,
  getEmailHost,
  isAllowedEmailDomain,
  getEmailProvider,
  isValidEmailFormat,
  isGmailAddress,
  isGoogleAccountEmail,
  isPlausibleResolvedEmail,
  isVendorNoiseEmailHost,
  scoreEmailCandidate,
  createParseStats,
  extractEmailsFromText,
  extractLikelyUserEmails,
  extractLikelyUserEmail,
  extractEmailsFromStructured,
  collectEmailsFromLsidValue,
  collectEmailsFromListAccountsResponse,
  shouldAcceptGoogleResolveEmail,
  addGoogleResolveEmail,
  stripGoogleJsonPrefix,
  collectEmailsFromAuthObject,
  collectEmailsFromAuthObjectDeep,
  GOOGLE_EMAIL_COOKIE_KEYS,
  tryEmailFromLsidValue,
  mergeEmailCandidates,
  collectEmailsWithStats,
  mergeGoogleEmailCandidates
};
})() );
