/**
 * OmniFlow - MAIN World JS Overrides
 * This file is loaded as a Web Accessible Resource.
 *
 * Minimal page-world patches: timezone, language, hardware, webdriver. These
 * reduce casual fingerprinting; they do NOT make the browser undetectable.
 * Known residual signals are documented inline (see the timezone section) and
 * are better addressed at the CDP/emulation layer than here.
 */
(function() {
  const config = globalThis.__OMNI_FLOW_STEALTH_CONFIG__ || {};
  try {
    delete globalThis.__OMNI_FLOW_STEALTH_CONFIG__;
  } catch (e) {
    globalThis.__OMNI_FLOW_STEALTH_CONFIG__ = undefined;
  }

  const fakeTimezone = config.timezoneEnabled && config.spoofTimezone
    ? String(config.spoofTimezone)
    : "";
  const enableLang = config.languagesEnabled === true;
  const enableHardware = config.hardwareMaskingEnabled === true;
  const enableAntiBot = config.antiBotEnabled === true;

  if (!fakeTimezone && !enableLang && !enableHardware && !enableAntiBot) {
    return;
  }

  // ==========================================
  // Stealth Native Disguise Helper
  // ==========================================
  // A patched function must look native to BOTH `fn.toString()` AND
  // `Function.prototype.toString.call(fn)`. The old approach defined an OWN
  // `toString` on each patched fn; that was detectable two ways:
  //   1. `Function.prototype.toString.call(fn)` bypasses the own property and
  //      returns the real source (the classic stealth-bypass detectors use).
  //   2. A genuine native function has NO own `toString` property, so
  //      `fn.hasOwnProperty('toString') === true` was itself a tell.
  // Instead, take over `Function.prototype.toString` once with a Proxy and keep
  // the reported source for each patched fn in a side WeakMap. Patched fns then
  // carry no own properties beyond what a native fn would, and both call paths
  // route through the proxy.
  const nativeSourceByFn = new WeakMap();
  const originalFunctionToString = Function.prototype.toString;
  const proxiedToString = new Proxy(originalFunctionToString, {
    apply(target, thisArg, argList) {
      if (typeof thisArg === 'function' && nativeSourceByFn.has(thisArg)) {
        return nativeSourceByFn.get(thisArg);
      }
      return Reflect.apply(target, thisArg, argList);
    }
  });
  // The takeover itself must report as native.
  nativeSourceByFn.set(proxiedToString, 'function toString() { [native code] }');
  Object.defineProperty(Function.prototype, 'toString', {
    value: proxiedToString,
    configurable: true,
    writable: true
  });

  // Mark a patched function so it reports `[native code]` with the correct
  // source string and `.name`. Accessor getters report as `get <name>` for both
  // their source and their `.name`, matching how Chrome exposes real getters.
  function makeNative(fn, name, isGetter = false) {
    const label = isGetter ? `get ${name}` : name;
    nativeSourceByFn.set(fn, `function ${label}() { [native code] }`);
    try {
      Object.defineProperty(fn, 'name', { value: label, configurable: true });
    } catch (e) {}
  }

  // ==========================================
  // 1. Timezone Spoofing (Intl resolvedOptions + Date getTimezoneOffset only)
  // ==========================================
  // KNOWN LIMITATION: this only patches getTimezoneOffset() and
  // resolvedOptions().timeZone. Date.prototype.toString/toTimeString and
  // Intl.DateTimeFormat().format() still render in the real system zone, so a
  // detector comparing the spoofed offset against the offset embedded in
  // `new Date().toString()` will see a mismatch. Doing this consistently in JS
  // is fragile (must match V8's exact format); the correct fix is engine-level
  // CDP `Emulation.setTimezoneOverride` applied by the worker before navigation.
  if (fakeTimezone) {
    const originalResolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions;
    const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;

    // Helper to calculate offset
    function calculateOffset(timezone) {
      try {
        const dateInstance = new Date();
        const tzString = dateInstance.toLocaleString('en-US', { timeZone: timezone });
        const localDate = new Date(tzString);
        const utcString = dateInstance.toLocaleString('en-US', { timeZone: 'UTC' });
        const utcDate = new Date(utcString);
        return Math.round((utcDate - localDate) / 60000);
      } catch (e) {
        return originalGetTimezoneOffset.call(new Date());
      }
    }

    const spoofedOffset = calculateOffset(fakeTimezone);

    // 1. Intl resolvedOptions Override
    const resolvedOptionsOverride = function() {
      const options = originalResolvedOptions.call(this);
      options.timeZone = fakeTimezone;
      return options;
    };
    makeNative(resolvedOptionsOverride, 'resolvedOptions');
    Intl.DateTimeFormat.prototype.resolvedOptions = resolvedOptionsOverride;

    // 2. Date getTimezoneOffset Override
    const getTimezoneOffsetOverride = function() {
      return spoofedOffset;
    };
    makeNative(getTimezoneOffsetOverride, 'getTimezoneOffset');
    Date.prototype.getTimezoneOffset = getTimezoneOffsetOverride;
  }

  // ==========================================
  // 2. Language Spoofing — always English (en-US)
  // ==========================================
  // Fallback for standalone use (no worker). In dp-worker mode the locale is
  // already forced to en-US at the engine level via CDP Emulation.setLocaleOverride,
  // so navigator.language === 'en-US' here and this block no-ops — keeping the two
  // paths from fighting over Navigator.prototype.
  if (enableLang) {
    const targetLang = 'en-US';
    const targetLanguages = ['en-US', 'en'];

    // Only override if current language is different to minimize prototype mutation
    if (navigator.language !== targetLang) {
      try {
        const languageGetter = function() { return targetLang; };
        makeNative(languageGetter, 'language', true);
        Object.defineProperty(Navigator.prototype, 'language', {
          get: languageGetter,
          configurable: true,
          enumerable: true
        });

        const languagesGetter = function() { return targetLanguages; };
        makeNative(languagesGetter, 'languages', true);
        Object.defineProperty(Navigator.prototype, 'languages', {
          get: languagesGetter,
          configurable: true,
          enumerable: true
        });
      } catch(e) {}
    }
  }

  // ==========================================
  // 3. Hardware Fingerprints Standardization (Only if different from target specs)
  // ==========================================
  if (enableHardware) {
    if (navigator.hardwareConcurrency !== 8 || navigator.deviceMemory !== 8) {
      try {
        const hardwareGetter = function() { return 8; };
        makeNative(hardwareGetter, 'hardwareConcurrency', true);
        Object.defineProperty(Navigator.prototype, 'hardwareConcurrency', {
          get: hardwareGetter,
          configurable: true,
          enumerable: true
        });

        const memoryGetter = function() { return 8; };
        makeNative(memoryGetter, 'deviceMemory', true);
        Object.defineProperty(Navigator.prototype, 'deviceMemory', {
          get: memoryGetter,
          configurable: true,
          enumerable: true
        });
      } catch(e) {}
    }
  }

  // ==========================================
  // 4. Webdriver Protection (Only if browser is actually automated)
  // ==========================================
  if (enableAntiBot) {
    if (navigator.webdriver === true) {
      try {
        const webdriverGetter = function() { return false; };
        makeNative(webdriverGetter, 'webdriver', true);
        Object.defineProperty(Navigator.prototype, 'webdriver', {
          get: webdriverGetter,
          configurable: true,
          enumerable: true
        });
      } catch(e) {}
    }
  }

})();
