/**
 * E2E tests for buildFrameChain / captureFrameChain in browser.ts.
 *
 * Drives a real Playwright Chromium instance against in-memory data: URLs that
 * carry nested iframes, then asserts the captured FrameChainEntry[] is the
 * expected shape for both attribute-unique iframes and positional fallbacks.
 *
 * Gated on SKIP_PLAYWRIGHT_E2E=1.
 */
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { chromium, type Browser, type Page } from 'playwright';
import { captureFrameChain } from './browser.js';

const skipPlaywright = process.env['SKIP_PLAYWRIGHT_E2E'] === '1';

const TEST_TIMEOUT = 30_000;

describe.skipIf(skipPlaywright)('buildFrameChain (E2E)', () => {
  let browser: Browser;
  let page: Page;

  beforeAll(async () => {
    browser = await chromium.launch({ headless: true });
    page = await browser.newPage();
  }, TEST_TIMEOUT);

  afterAll(async () => {
    await page?.close().catch(() => {});
    await browser?.close().catch(() => {});
  }, TEST_TIMEOUT);

  it('returns [] for an element in the main frame', async () => {
    await page.setContent('<!doctype html><button id="b">x</button>');
    const chain = await captureFrameChain(page.mainFrame());
    expect(chain).toEqual([]);
  }, TEST_TIMEOUT);

  it('captures a single iframe with a unique `name` attribute', async () => {
    await page.setContent(`
      <!doctype html>
      <iframe name="cart" srcdoc='<button id="b">x</button>'></iframe>
    `);
    // Wait for iframe to be ready
    await page.locator('iframe[name="cart"]').waitFor({ state: 'attached' });
    const frames = page.frames();
    const cartFrame = frames.find(f => f.name() === 'cart');
    expect(cartFrame).toBeDefined();
    const chain = await captureFrameChain(cartFrame!);
    expect(chain).toEqual([{ selector: 'iframe[name="cart"]' }]);
  }, TEST_TIMEOUT);

  it('captures a single iframe with a unique `id` attribute when no name is set', async () => {
    await page.setContent(`
      <!doctype html>
      <iframe id="checkout-frame" srcdoc='<button>x</button>'></iframe>
    `);
    await page.locator('iframe#checkout-frame').waitFor({ state: 'attached' });
    const inner = page.mainFrame().childFrames()[0];
    expect(inner).toBeDefined();
    const chain = await captureFrameChain(inner!);
    expect(chain).toEqual([{ selector: 'iframe[id="checkout-frame"]' }]);
  }, TEST_TIMEOUT);

  it('falls back to positional index when no stable attribute is present', async () => {
    await page.setContent(`
      <!doctype html>
      <iframe srcdoc='<button>a</button>'></iframe>
      <iframe srcdoc='<button>b</button>'></iframe>
    `);
    await page.waitForFunction(() => document.querySelectorAll('iframe').length === 2);
    const children = page.mainFrame().childFrames();
    expect(children).toHaveLength(2);
    const chain0 = await captureFrameChain(children[0]!);
    const chain1 = await captureFrameChain(children[1]!);
    expect(chain0).toEqual([{ selector: 'iframe', index: 0 }]);
    expect(chain1).toEqual([{ selector: 'iframe', index: 1 }]);
  }, TEST_TIMEOUT);

  it('captures a 2-level nested chain with mixed strategies', async () => {
    // Outer has name="outer"; inner has no stable attr → positional.
    await page.setContent(`
      <!doctype html>
      <iframe name="outer" srcdoc='<iframe srcdoc="<button>x</button>"></iframe>'></iframe>
    `);
    await page.locator('iframe[name="outer"]').waitFor({ state: 'attached' });
    // Wait for the nested iframe to attach inside `outer`
    await page.waitForFunction(() => {
      const outer = document.querySelector('iframe[name="outer"]') as HTMLIFrameElement | null;
      return !!outer?.contentDocument?.querySelector('iframe');
    });

    const outer = page.frames().find(f => f.name() === 'outer');
    expect(outer).toBeDefined();
    const inner = outer!.childFrames()[0];
    expect(inner).toBeDefined();

    const chain = await captureFrameChain(inner!);
    expect(chain).toEqual([
      { selector: 'iframe[name="outer"]' },
      { selector: 'iframe', index: 0 },
    ]);
  }, TEST_TIMEOUT);

  it('escapes double-quotes and backslashes in attribute values', async () => {
    // Edge case: name contains a quote that we must escape in the selector.
    await page.setContent(`
      <!doctype html>
      <iframe name='frame"with"quotes' srcdoc='<button>x</button>'></iframe>
    `);
    await page.waitForFunction(() => document.querySelector('iframe') !== null);
    const inner = page.mainFrame().childFrames()[0];
    expect(inner).toBeDefined();
    const chain = await captureFrameChain(inner!);
    // Selector should escape the quotes so it remains a valid CSS attribute selector.
    expect(chain).toHaveLength(1);
    expect(chain[0].selector).toBe('iframe[name="frame\\"with\\"quotes"]');
  }, TEST_TIMEOUT);
});
