import type { Browser, BrowserContext, LaunchOptions } from 'playwright';
import type { BrowserConfig } from '../config.js';

const STEALTH_ARGS = ['--disable-blink-features=AutomationControlled'];

/**
 * Build Playwright launch options honoring config-supplied channel / executablePath
 * and adding stealth flags that help past automation-detecting login pages
 * (Google, in particular, is one of the strict ones).
 */
export function buildLaunchOptions(
  config: BrowserConfig | undefined,
  headless: boolean,
): LaunchOptions {
  const opts: LaunchOptions = { headless, args: STEALTH_ARGS };
  if (config?.executablePath) opts.executablePath = config.executablePath;
  else if (config?.channel && config.channel !== 'chromium') opts.channel = config.channel;
  return opts;
}

/**
 * Apply per-context stealth init script: hide `navigator.webdriver`. Cheap
 * insurance against simple automation checks. Won't fool everything (Google
 * uses many signals) but combined with `channel: 'chrome'` it gets through
 * most sites that block stock Playwright Chromium.
 */
export async function applyStealthInitScript(context: BrowserContext): Promise<void> {
  await context.addInitScript(() => {
    Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
  });
}

/**
 * Wrap `EventTarget.prototype.addEventListener` to tag any Element that
 * receives a click / mousedown / pointerdown listener with the
 * `data-ao-click` attribute.
 *
 * The BrowseWeb snapshot treats `[data-ao-click]` as an interactive element,
 * so this surfaces "naked clickable <div>" patterns common in jQuery /
 * vanilla-JS / Vue-compiled enterprise apps where event handlers are bound
 * at runtime instead of declared in markup.
 *
 * Limitations:
 *   - React's `onClick={...}` uses root-level event delegation, so individual
 *     elements never get a native listener. Those should be marked via
 *     semantic markup (role="button" / <button>) which the snapshot already
 *     detects.
 *   - Direct property assignment (`el.onclick = fn`) is not intercepted.
 *     The snapshot already detects elements with the [onclick] attribute.
 *   - The marker stays even after `removeEventListener`. Accept the false
 *     positive — a stale ref simply offers an extra click target.
 */
export async function applyAgentSnapshotHooks(context: BrowserContext): Promise<void> {
  await context.addInitScript(() => {
    const TARGET_TYPES = new Set(['click', 'mousedown', 'pointerdown']);
    const origAdd = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function (
      this: EventTarget,
      type: string,
      listener: EventListenerOrEventListenerObject | null,
      options?: boolean | AddEventListenerOptions,
    ) {
      try {
        if (
          typeof type === 'string' &&
          TARGET_TYPES.has(type) &&
          listener != null &&
          this instanceof Element
        ) {
          (this as Element).setAttribute('data-ao-click', '1');
        }
      } catch {
        /* never break the page */
      }
      return origAdd.call(this, type, listener as EventListener, options as never);
    };
  });
}

/** Convenience: launch + return a Browser with stealth applied at context level later. */
export async function launchWithStealth(
  chromium: { launch: (opts: LaunchOptions) => Promise<Browser> },
  config: BrowserConfig | undefined,
  headless: boolean,
): Promise<Browser> {
  return chromium.launch(buildLaunchOptions(config, headless));
}
