#!/usr/bin/env node
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { chromium } from 'playwright-core';

const baseUrl = (process.env.Q4_E2E_URL || process.argv[2] || 'http://127.0.0.1:3280').replace(/\/$/, '');
const chrome = process.env.Q4_CHROME || '/usr/bin/google-chrome';
const outDir = path.resolve('.selfcheck/shots');
await fs.mkdir(outDir, { recursive: true });

const PLAYER_MODELS = Object.freeze([
  { id: 'model_player_marine', label: 'Kane', team: 'Marine' },
  { id: 'model_player_kane_strogg', label: 'Stroggified Kane', team: 'Strogg' },
  { id: 'model_player_failed_transfer', label: 'Failed Transfer', team: null }
]);

const REPLAYS = Object.freeze([
  {
    id: 'dm-q4web-arena',
    name: 'Zapis meczu 01.q4sd',
    size: 184_320,
    modifiedAt: '2026-07-15T11:40:00.000Z',
    metadata: { map: 'mp/q4web_arena', gameType: 'DM', durationMs: 492_000 }
  },
  {
    id: 'ctf-heartless',
    name: 'Zapis meczu 02.q4sd',
    size: 291_840,
    modifiedAt: '2026-07-15T12:10:00.000Z',
    metadata: { map: 'mp/q4ctf1', gameType: 'CTF', durationMs: 724_000 }
  }
]);

function collectFailures(page) {
  const failures = [];
  page.on('pageerror', (error) => failures.push(`pageerror: ${error.stack || error}`));
  page.on('console', (message) => {
    if (message.type() === 'error') failures.push(`console: ${message.text()}`);
  });
  return failures;
}

async function installApiFixtures(context) {
  await context.route('**/api/status', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        status: 'ok',
        players: 2,
        connections: [
          { id: 'shell-e2e-1', port: 28004 },
          { id: 'shell-e2e-2', port: 28004 }
        ],
        udp: { defaultPort: 28004 },
        dedicated: { enabled: true, running: true, ready: true },
        catalog: { models: PLAYER_MODELS },
        match: { current: null, pending: null }
      })
    });
  });
  await context.route('**/api/replays', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ replays: REPLAYS })
    });
  });
  await context.route('**/api/replays/*/watch', async (route) => {
    if (route.request().method() === 'DELETE') {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ ok: true, released: true })
      });
      return;
    }
    await route.fulfill({
      status: 504,
      contentType: 'application/json',
      body: JSON.stringify({
        error: 'replay process did not become ready within 120000ms',
        code: 'REPLAY_START_TIMEOUT'
      })
    });
  });
}

async function seedPersistedModel(context) {
  await context.addInitScript((state) => {
    localStorage.setItem('q4_launcher_v1', JSON.stringify(state));
  }, {
    activeView: 'campaign',
    difficulty: 1,
    match: {
      map: 'mp/q4web_arena',
      mode: 'DM',
      botCount: 4,
      fragLimit: 10,
      timeLimit: 0
    },
    profile: {
      nick: 'Gracz',
      team: 'auto',
      model: 'model_player_marine'
    }
  });
}

async function assertCanvasWebGLReady(page) {
  const sample = await page.locator('#gameCanvas').evaluate((canvas) => {
    const context = canvas.getContext('webgl2');
    return {
      context: Boolean(context),
      version: context?.getParameter(context.VERSION) || null,
      rejects2d: canvas.getContext('2d') === null,
      width: canvas.width,
      height: canvas.height
    };
  });
  assert.equal(sample.context, true, 'game canvas must expose WebGL2');
  assert.match(sample.version || '', /WebGL 2/i, `unexpected graphics context: ${JSON.stringify(sample)}`);
  assert.equal(sample.rejects2d, true, 'game canvas must not switch from WebGL2 to 2D');
  assert.ok(sample.width >= 300 && sample.height >= 200, `canvas is unexpectedly small: ${JSON.stringify(sample)}`);
}

async function assertNoViewportOverflow(page) {
  const result = await page.evaluate(() => ({
    documentWidth: document.documentElement.scrollWidth,
    viewportWidth: innerWidth,
    documentHeight: document.documentElement.scrollHeight,
    viewportHeight: innerHeight,
    clippedText: [...document.querySelectorAll('button, strong, h1, h2, h3, p, span')]
      .filter((element) => {
        const style = getComputedStyle(element);
        const rect = element.getBoundingClientRect();
        if (element.classList.contains('sr-only') || style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0) return false;
        if (style.textOverflow === 'ellipsis') return false;
        return element.scrollWidth > element.clientWidth + 2 || element.scrollHeight > element.clientHeight + 2;
      })
      .map((element) => `${element.tagName.toLowerCase()}#${element.id}.${element.className}`)
      .slice(0, 20)
  }));
  assert.ok(result.documentWidth <= result.viewportWidth + 1, `horizontal overflow: ${JSON.stringify(result)}`);
  assert.ok(result.documentHeight <= result.viewportHeight + 1, `vertical overflow: ${JSON.stringify(result)}`);
  assert.deepEqual(result.clippedText, [], `visible text is clipped: ${JSON.stringify(result.clippedText)}`);
}

async function assertLauncherLayout(page, view) {
  const result = await page.evaluate((activeView) => {
    const rect = (element) => {
      const value = element.getBoundingClientRect();
      return {
        left: value.left,
        top: value.top,
        right: value.right,
        bottom: value.bottom,
        width: value.width,
        height: value.height
      };
    };
    const overlapArea = (left, right) => Math.max(0, Math.min(left.right, right.right) - Math.max(left.left, right.left)) *
      Math.max(0, Math.min(left.bottom, right.bottom) - Math.max(left.top, right.top));
    const root = document.querySelector('#launcher');
    const layout = document.querySelector('.launcher-layout');
    const sidebar = document.querySelector('.launcher-sidebar');
    const main = document.querySelector('.launcher-main');
    const panel = document.querySelector(`[data-launcher-panel="${activeView}"]`);
    const topbar = document.querySelector('#topbar');
    const header = panel.querySelector('.launcher-view-head');
    const cta = panel.querySelector('.launcher-cta');
    const navButtons = [...document.querySelectorAll('.launcher-nav [data-launcher-view]')].map(rect);
    const navOverlaps = [];
    for (let left = 0; left < navButtons.length; left += 1) {
      for (let right = left + 1; right < navButtons.length; right += 1) {
        const area = overlapArea(navButtons[left], navButtons[right]);
        if (area > 1) navOverlaps.push({ left, right, area });
      }
    }
    const rootRect = rect(root);
    const sidebarRect = rect(sidebar);
    const mainRect = rect(main);
    return {
      viewport: { width: innerWidth, height: innerHeight },
      root: rootRect,
      layout: rect(layout),
      sidebar: sidebarRect,
      main: mainRect,
      panel: rect(panel),
      topbar: rect(topbar),
      header: rect(header),
      cta: cta ? rect(cta) : null,
      navButtons,
      navOverlaps,
      sidebarMainOverlap: overlapArea(sidebarRect, mainRect),
      headerCtaOverlap: cta ? overlapArea(rect(header), rect(cta)) : 0
    };
  }, view);

  assert.ok(result.root.left >= -1 && result.root.right <= result.viewport.width + 1, JSON.stringify(result));
  assert.ok(result.root.top >= result.topbar.bottom - 1, `launcher overlaps topbar: ${JSON.stringify(result)}`);
  assert.ok(result.root.bottom <= result.viewport.height + 1, JSON.stringify(result));
  assert.ok(result.layout.width > 0 && result.layout.height > 0, JSON.stringify(result));
  assert.ok(result.panel.width > 0 && result.panel.height > 0, JSON.stringify(result));
  assert.equal(result.navButtons.length, 4, JSON.stringify(result));
  assert.ok(result.navButtons.every((button) => button.width >= 40 && button.height >= 40), `launcher navigation has undersized targets: ${JSON.stringify(result)}`);
  assert.deepEqual(result.navOverlaps, [], `launcher navigation overlaps: ${JSON.stringify(result)}`);
  assert.ok(result.sidebarMainOverlap <= 1, `launcher columns overlap: ${JSON.stringify(result)}`);
  assert.ok(result.headerCtaOverlap <= 1, `launcher header and CTA overlap: ${JSON.stringify(result)}`);
  if (result.cta) {
    assert.ok(result.cta.height >= 46 && result.cta.bottom <= result.root.bottom + 1, `launcher CTA is outside the viewport: ${JSON.stringify(result)}`);
  }
}

async function assertLauncherView(page, view) {
  const navigation = page.locator(`.launcher-nav [data-launcher-view="${view}"]`);
  await navigation.click();
  const panel = page.locator(`[data-launcher-panel="${view}"]`);
  await panel.waitFor({ state: 'visible' });
  assert.equal(await navigation.getAttribute('aria-current'), 'page');
  const visibleViews = await page.locator('[data-launcher-panel]').evaluateAll((panels) => panels
    .filter((entry) => !entry.hidden)
    .map((entry) => entry.dataset.launcherPanel));
  assert.deepEqual(visibleViews, [view], `unexpected visible launcher panels: ${JSON.stringify(visibleViews)}`);
  await assertNoViewportOverflow(page);
  await assertLauncherLayout(page, view);
}

async function openLauncherPage(context) {
  await installApiFixtures(context);
  const page = await context.newPage();
  const failures = collectFailures(page);
  const response = await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'networkidle' });
  assert.equal(response?.status(), 200);
  await page.locator('#launcher').waitFor({ state: 'visible' });
  await page.waitForFunction(() => Boolean(window.Q4App?.launcher?.getState));
  await page.locator('#multiplayerModel option[value="model_player_marine"]').waitFor({ state: 'attached' });
  assert.equal(await page.locator('#launcher').getAttribute('aria-hidden'), 'false');
  assert.equal(await page.locator('#runtimePanel').isHidden(), true, 'runtime status panel must yield to the ready launcher');
  assert.equal(await page.evaluate(() => crossOriginIsolated), true, 'COOP/COEP isolation must be active');
  await assertCanvasWebGLReady(page);
  return { page, failures };
}

async function bootReadinessRaceSmoke(browser) {
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'pl-PL' });
  await installApiFixtures(context);

  let releaseEngineManifest;
  const engineManifestGate = new Promise((resolve) => { releaseEngineManifest = resolve; });
  let observeEngineManifest;
  const engineManifestObserved = new Promise((resolve) => { observeEngineManifest = resolve; });
  await context.route('**/engine/engine-manifest.json', async (route) => {
    observeEngineManifest();
    await engineManifestGate;
    await route.continue();
  });

  let matchRequests = 0;
  await context.route('**/api/match', async (route) => {
    matchRequests += 1;
    await route.fulfill({
      status: 500,
      contentType: 'application/json',
      body: JSON.stringify({ code: 'UNEXPECTED_MATCH_REQUEST', error: 'Preflight danych nie został zakończony.' })
    });
  });

  const engineArtifactRequests = [];
  const page = await context.newPage();
  const failures = collectFailures(page);
  page.on('request', (request) => {
    if (/\/engine\/[^/?]+\.(?:mjs|wasm)(?:\?|$)/.test(request.url())) engineArtifactRequests.push(request.url());
  });

  const response = await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'domcontentloaded' });
  assert.equal(response?.status(), 200);
  await engineManifestObserved;
  await page.waitForFunction(() => Boolean(window.Q4App?.launcher?.getState));

  await page.locator('#multiplayerButton').click();
  await page.locator('[data-launcher-panel="multiplayer"]').waitFor({ state: 'visible' });
  await page.locator('#multiplayerModel option[value="model_player_marine"]').waitFor({ state: 'attached' });
  await page.waitForFunction(() => !document.querySelector('#multiplayerPlayButton')?.disabled);

  await page.evaluate(() => {
    const importer = window.Q4App.importer;
    const hadOwnMethod = Object.hasOwn(importer, 'ensureServerRetail');
    const originalMethod = importer.ensureServerRetail;
    window.__q4BootRace = {
      started: false,
      reject: null,
      restore() {
        if (hadOwnMethod) importer.ensureServerRetail = originalMethod;
        else delete importer.ensureServerRetail;
      }
    };
    importer.ensureServerRetail = () => new Promise((_resolve, reject) => {
      window.__q4BootRace.started = true;
      window.__q4BootRace.reject = () => reject(new Error('Kontrolowane przerwanie preflightu danych gry.'));
    });
  });

  await page.locator('#multiplayerPlayButton').click();
  await page.waitForFunction(() => window.__q4BootRace?.started && typeof window.__q4BootRace.reject === 'function');
  assert.equal(await page.locator('#launcher').getAttribute('aria-busy'), 'true');

  const manifestResponsePromise = page.waitForResponse((candidate) =>
    new URL(candidate.url()).pathname === '/engine/engine-manifest.json');
  releaseEngineManifest();
  const manifestResponse = await manifestResponsePromise;
  await manifestResponse.finished();
  await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 0)));

  assert.equal(await page.locator('#launcher').getAttribute('aria-busy'), 'true',
    'late boot readiness must not clear an active multiplayer preflight');
  for (const selector of ['#campaignPlayButton', '#multiplayerPlayButton', '#replayPlayButton']) {
    assert.equal(await page.locator(selector).isDisabled(), true,
      `late boot readiness unexpectedly enabled ${selector}`);
  }
  assert.equal(matchRequests, 0, 'match configuration must wait for the retail preflight');
  assert.deepEqual(engineArtifactRequests, [], 'the boot-race scenario must not start a WASM artifact');

  const reloaded = page.waitForEvent('framenavigated');
  await page.keyboard.press('Escape');
  await reloaded;
  await page.locator('#launcher').waitFor({ state: 'visible' });
  assert.equal(await page.evaluate(() => window.__q4BootRace), undefined,
    'Escape during preflight must destroy the pending launch document');
  assert.equal(matchRequests, 0, 'an Escape-cancelled retail preflight must not configure a match');
  assert.deepEqual(engineArtifactRequests, [], 'an Escape-cancelled retail preflight must not start WASM');
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

async function replayPreflightOrderSmoke(browser, settlement) {
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'pl-PL' });
  await installApiFixtures(context);
  const page = await context.newPage();
  const failures = collectFailures(page);
  let watchRequests = 0;
  let cancelRequests = 0;
  const replayClaimTokens = [];
  page.on('request', (request) => {
    if (!/\/api\/replays\/[^/]+\/watch$/.test(new URL(request.url()).pathname)) return;
    const claimToken = request.headers()['x-q4-replay-claim'];
    replayClaimTokens.push(claimToken);
    if (request.method() === 'POST') watchRequests += 1;
    if (request.method() === 'DELETE') cancelRequests += 1;
  });

  const response = await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'networkidle' });
  assert.equal(response?.status(), 200);
  await page.locator('#launcher').waitFor({ state: 'visible' });
  await page.locator('.launcher-nav [data-launcher-view="replays"]').click();
  await page.locator('[data-replay-id="ctf-heartless"]').click();

  await page.evaluate(() => {
    const importer = window.Q4App.importer;
    const hadOwnMethod = Object.hasOwn(importer, 'ensureServerRetail');
    const originalMethod = importer.ensureServerRetail;
    window.__q4ReplayPreflight = {
      started: false,
      resolve: null,
      reject: null,
      restore() {
        if (hadOwnMethod) importer.ensureServerRetail = originalMethod;
        else delete importer.ensureServerRetail;
      }
    };
    importer.ensureServerRetail = () => new Promise((resolve, reject) => {
      window.__q4ReplayPreflight.started = true;
      window.__q4ReplayPreflight.resolve = resolve;
      window.__q4ReplayPreflight.reject = reject;
    });
  });

  await page.locator('#replayPlayButton').click();
  await page.waitForFunction(() => window.__q4ReplayPreflight?.started);
  await page.waitForTimeout(100);
  assert.equal(watchRequests, 0, `replay /watch escaped the pending retail preflight (${settlement})`);

  await page.evaluate((outcome) => {
    if (outcome === 'resolve') window.__q4ReplayPreflight.resolve();
    else window.__q4ReplayPreflight.reject(new Error('Kontrolowane przerwanie preflightu powtórki.'));
  }, settlement);

  if (settlement === 'resolve') {
    await page.waitForFunction(() => document.querySelector('#launcherAnnouncement')?.dataset.level === 'error');
    assert.equal(watchRequests, 1, 'resolved replay retail preflight must be followed by exactly one /watch request');
    await page.waitForFunction(() => performance.getEntriesByType('resource') !== null);
    for (let attempt = 0; attempt < 20 && cancelRequests < 1; attempt += 1) {
      await page.waitForTimeout(25);
    }
    assert.equal(cancelRequests, 1, 'failed replay allocation must release its server-side claim');
    assert.equal(replayClaimTokens.length, 2);
    assert.match(replayClaimTokens[0] || '', /^[a-f0-9]{64}$/);
    assert.equal(replayClaimTokens[1], replayClaimTokens[0], 'POST and keepalive DELETE must use the same claim');
    for (let index = failures.length - 1; index >= 0; index -= 1) {
      if (/^console: Failed to load resource: .*status of 504/.test(failures[index])) failures.splice(index, 1);
    }
  } else {
    await page.waitForFunction(() => document.querySelector('#launcher')?.getAttribute('aria-busy') === 'false');
    assert.equal(watchRequests, 0, 'rejected replay retail preflight must not allocate a replay process');
    assert.equal(cancelRequests, 0, 'rejected retail preflight has no replay claim to cancel');
    assert.match(await page.locator('#launcherAnnouncement').textContent(), /kontrolowane przerwanie preflightu powtórki/i);
  }

  await page.evaluate(() => {
    window.__q4ReplayPreflight.restore();
    delete window.__q4ReplayPreflight;
  });
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

async function replayEscapeClaimSmoke(browser) {
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'pl-PL' });
  await installApiFixtures(context);
  await context.unroute('**/api/replays/*/watch');
  const postTokens = [];
  const cancellationTokens = [];
  await context.route('**/api/replays/*/watch', async (route) => {
    const claimToken = route.request().headers()['x-q4-replay-claim'] || null;
    if (route.request().method() === 'DELETE') {
      cancellationTokens.push(claimToken);
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({ ok: true, released: true })
      });
      return;
    }
    postTokens.push(claimToken);
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        port: 28040,
        connectUrl: '/game/28040',
        replay: REPLAYS[0].name,
        replayId: REPLAYS[0].id,
        map: REPLAYS[0].metadata.map,
        reused: false
      })
    });
  });

  const page = await context.newPage();
  const failures = collectFailures(page);
  const response = await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'networkidle' });
  assert.equal(response?.status(), 200);
  await page.locator('#launcher').waitFor({ state: 'visible' });
  await page.locator('.launcher-nav [data-launcher-view="replays"]').click();
  await page.locator(`[data-replay-id="${REPLAYS[0].id}"]`).click();
  await page.evaluate(() => {
    const importer = window.Q4App.importer;
    const engineLoader = window.Q4App.engineLoader;
    importer.ensureServerRetail = async () => ({});
    engineLoader.launch = ({ launch }) => new Promise(() => {
      window.__q4ReplayClaimLaunch = launch;
    });
  });

  await page.locator('#replayPlayButton').click();
  await page.waitForFunction(() => Boolean(window.__q4ReplayClaimLaunch));
  assert.equal(postTokens.length, 1);
  assert.match(postTokens[0] || '', /^[a-f0-9]{64}$/);
  assert.equal(await page.evaluate(() => window.__q4ReplayClaimLaunch.replayClaimToken), postTokens[0]);

  const reloaded = page.waitForEvent('framenavigated');
  await page.keyboard.press('Escape');
  await reloaded;
  await page.locator('#launcher').waitFor({ state: 'visible' });
  for (let attempt = 0; attempt < 50 && cancellationTokens.length === 0; attempt += 1) {
    await page.waitForTimeout(10);
  }
  assert.deepEqual(cancellationTokens, [postTokens[0]],
    'Escape during replay engine initialization must release exactly its own claim');
  assert.equal(await page.evaluate(() => window.__q4ReplayClaimLaunch), undefined,
    'Escape must destroy the replay initialization document');
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

async function matchReservationHeartbeatSmoke(browser) {
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'pl-PL' });
  await installApiFixtures(context);
  const matchRequests = [];
  const reservationTokens = [];
  const cancellationTokens = [];
  let acceptedMatch = null;
  await context.route('**/api/match/reservation', async (route) => {
    cancellationTokens.push(route.request().headers()['x-q4-match-reservation'] || null);
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ ok: true, released: true })
    });
  });
  await context.route('**/api/match', async (route) => {
    const match = route.request().postDataJSON();
    matchRequests.push(match);
    reservationTokens.push(route.request().headers()['x-q4-match-reservation'] || null);
    if (matchRequests.length === 3) {
      await route.fulfill({
        status: 503,
        contentType: 'application/json',
        body: JSON.stringify({ code: 'MATCH_HEARTBEAT_TEST_FAILURE', error: 'Kontrolowany błąd odnowienia.' })
      });
      return;
    }
    if (matchRequests.length === 1) acceptedMatch = { ...match, fragLimit: Number(match.fragLimit) + 1 };
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        ok: true,
        changed: matchRequests.length === 1,
        port: 28004,
        match: matchRequests.length === 1 ? acceptedMatch : match
      })
    });
  });

  const page = await context.newPage();
  const failures = collectFailures(page);
  const warnings = [];
  page.on('console', (message) => {
    if (message.type() === 'warning') warnings.push(message.text());
  });
  const response = await page.goto(`${baseUrl}/?nodirect=1`, { waitUntil: 'networkidle' });
  assert.equal(response?.status(), 200);
  await page.locator('#launcher').waitFor({ state: 'visible' });
  await page.waitForFunction(() => Boolean(window.Q4App?.launcher?.getState));
  await page.locator('#multiplayerModel option[value="model_player_marine"]').waitFor({ state: 'attached' });

  await page.evaluate(() => {
    const importer = window.Q4App.importer;
    const engineLoader = window.Q4App.engineLoader;
    const hadOwnEnsure = Object.hasOwn(importer, 'ensureServerRetail');
    const originalEnsure = importer.ensureServerRetail;
    const originalLaunch = engineLoader.launch;
    const originalSetInterval = window.setInterval;
    const originalClearInterval = window.clearInterval;
    const heartbeatTimerId = 987_654;
    window.__q4MatchHeartbeat = {
      timers: [],
      cleared: [],
      launchStarted: false,
      emitProgress: null,
      resolveLaunch: null,
      restore() {
        window.setInterval = originalSetInterval;
        window.clearInterval = originalClearInterval;
        engineLoader.launch = originalLaunch;
        if (hadOwnEnsure) importer.ensureServerRetail = originalEnsure;
        else delete importer.ensureServerRetail;
      }
    };
    window.setInterval = (callback, delay, ...args) => {
      if (delay === 120_000) {
        window.__q4MatchHeartbeat.timers.push({ callback, delay, args, id: heartbeatTimerId });
        return heartbeatTimerId;
      }
      return originalSetInterval(callback, delay, ...args);
    };
    window.clearInterval = (id) => {
      if (id === heartbeatTimerId) {
        window.__q4MatchHeartbeat.cleared.push(id);
        return;
      }
      originalClearInterval(id);
    };
    importer.ensureServerRetail = async () => ({});
    engineLoader.launch = ({ onReady, onStatus }) => new Promise((resolve) => {
      window.__q4MatchHeartbeat.launchStarted = true;
      window.__q4MatchHeartbeat.emitProgress = (step) => {
        if (step === 'runtime-ready') onStatus({
          stage: 'runtime',
          message: 'Dane runtime OpenQ4 są gotowe.',
          completedBytes: 10,
          totalBytes: 10
        });
        if (step === 'gamelib-start') onStatus({
          stage: 'gamelib',
          message: 'Pobieranie game-mp_wasm.wasm…',
          completedBytes: 0,
          totalBytes: 10
        });
        if (step === 'gamelib-half') onStatus({
          stage: 'gamelib',
          message: 'Pobieranie game-mp_wasm.wasm…',
          completedBytes: 5,
          totalBytes: 10
        });
      };
      window.__q4MatchHeartbeat.resolveLaunch = () => {
        onReady({});
        resolve({});
      };
    });
  });

  await page.locator('#multiplayerButton').click();
  await page.locator('[data-launcher-panel="multiplayer"]').waitFor({ state: 'visible' });
  await page.waitForFunction(() => !document.querySelector('#multiplayerPlayButton')?.disabled);
  await page.locator('#multiplayerPlayButton').click();
  await page.waitForFunction(() => window.__q4MatchHeartbeat?.launchStarted && window.__q4MatchHeartbeat.timers.length === 1);
  assert.equal(matchRequests.length, 1, 'launch must configure the selected match exactly once before the heartbeat');
  assert.match(reservationTokens[0] || '', /^[a-f0-9]{64}$/,
    'the first match request must carry a cryptographic per-tab reservation capability');
  assert.equal(await page.evaluate(() => window.__q4MatchHeartbeat.timers[0].delay), 120_000);
  await page.evaluate(() => window.__q4MatchHeartbeat.emitProgress('runtime-ready'));
  const runtimeReadyProgress = await page.locator('#runtimeProgress').evaluate((progress) => progress.value);
  await page.evaluate(() => window.__q4MatchHeartbeat.emitProgress('gamelib-start'));
  const gameLibStartProgress = await page.locator('#runtimeProgress').evaluate((progress) => progress.value);
  await page.evaluate(() => window.__q4MatchHeartbeat.emitProgress('gamelib-half'));
  const gameLibHalfProgress = await page.locator('#runtimeProgress').evaluate((progress) => progress.value);
  assert.equal(runtimeReadyProgress, 0.70);
  assert.equal(gameLibStartProgress, 0.70,
    'the overall progress bar must not move backwards between runtime and GameLib');
  assert.equal(gameLibHalfProgress, 0.76);
  assert.match(await page.locator('#runtimeProgressText').textContent(), /^76%/);
  assert.match(await page.locator('#runtimeProgressStage').textContent(), /Moduł zasad gry/);
  assert.match(await page.locator('#runtimeProgress').getAttribute('aria-valuetext'), /Moduł zasad gry: 76%/);

  await page.evaluate(() => window.__q4MatchHeartbeat.timers[0].callback());
  assert.equal(matchRequests.length, 2, 'the heartbeat must refresh the accepted match reservation');
  assert.notDeepEqual(matchRequests[1], matchRequests[0], 'the fixture must distinguish requested and accepted match data');
  assert.deepEqual(matchRequests[1], acceptedMatch, 'the heartbeat must use the normalized accepted match');
  assert.equal(reservationTokens[1], reservationTokens[0], 'the heartbeat must reuse the same reservation capability');

  await page.evaluate(() => window.__q4MatchHeartbeat.timers[0].callback());
  assert.equal(matchRequests.length, 3, 'a failed heartbeat must still settle its own request');
  assert.equal(reservationTokens[2], reservationTokens[0]);
  assert.ok(warnings.some((entry) => /match reservation heartbeat failed/i.test(entry)),
    `the heartbeat failure was not reported: ${JSON.stringify(warnings)}`);
  assert.equal(await page.evaluate(() => window.__q4MatchHeartbeat.cleared.length), 0,
    'a heartbeat failure must not cancel the in-flight engine launch');

  await page.evaluate(() => window.__q4MatchHeartbeat.resolveLaunch());
  await page.waitForFunction(() => window.__q4MatchHeartbeat.cleared.length === 1);
  assert.equal(await page.locator('body').evaluate((body) => body.classList.contains('engine-active')), true);
  await page.evaluate(() => window.__q4MatchHeartbeat.timers[0].callback());
  await page.waitForTimeout(50);
  assert.equal(matchRequests.length, 3, 'a settled launch must stop future reservation refreshes');

  const reloaded = page.waitForEvent('framenavigated');
  await page.keyboard.press('Escape');
  await reloaded;
  await page.locator('#launcher').waitFor({ state: 'visible' });
  assert.equal(await page.locator('body').evaluate((body) => body.classList.contains('engine-active')), false);
  assert.equal(await page.locator('#launcher').getAttribute('aria-hidden'), 'false');
  assert.equal(await page.evaluate(() => window.__q4MatchHeartbeat), undefined,
    'Escape must destroy the old engine document instead of suspending it');
  for (let attempt = 0; attempt < 50 && cancellationTokens.length === 0; attempt += 1) {
    await page.waitForTimeout(10);
  }
  assert.deepEqual(cancellationTokens, [reservationTokens[0]],
    'Escape reload must issue one owner-authenticated keepalive cancellation');

  for (let index = failures.length - 1; index >= 0; index -= 1) {
    if (/^console: Failed to load resource: .*status of 503/.test(failures[index])) failures.splice(index, 1);
  }
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

async function assertCtfSelection(page) {
  await page.locator('[data-mode="CTF"]').click();
  await page.waitForFunction(() => window.Q4App.launcher.getState().match.mode === 'CTF');
  const state = await page.evaluate(() => window.Q4App.launcher.getState());
  assert.equal(state.match.mode, 'CTF');
  assert.notEqual(state.profile.team, 'auto', `team mode must resolve a concrete team: ${JSON.stringify(state.profile)}`);
  assert.equal(await page.locator('#multiplayerTeams [data-team="auto"]').count(), 0,
    'team modes must not expose a non-functional automatic-team option');
  assert.match(state.match.map, /^mp\/(?:q4ctf[1-5]|q4xctf6)$/, `CTF must choose a compatible map: ${JSON.stringify(state.match)}`);
  assert.ok(state.match.fragLimit >= 1, `CTF capture limit must be positive: ${JSON.stringify(state.match)}`);
  if (state.profile.model !== 'auto') {
    const model = PLAYER_MODELS.find((entry) => entry.id === state.profile.model);
    assert.ok(model?.team, `team mode exposed an unassigned model: ${JSON.stringify(state.profile)}`);
    assert.equal(state.profile.team, model.team, `team mode did not align the selected character: ${JSON.stringify(state.profile)}`);
  }
  assert.equal(await page.locator(`[data-map="${state.match.map}"]`).getAttribute('aria-checked'), 'true');
  const visibleMaps = await page.locator('#multiplayerMaps [data-map]').evaluateAll((maps) => maps.map((entry) => entry.dataset.map));
  assert.ok(visibleMaps.length >= 1 && visibleMaps.every((map) => /^mp\/(?:q4ctf[1-5]|q4xctf6)$/.test(map)), `non-CTF map remained visible: ${JSON.stringify(visibleMaps)}`);
  assert.equal(await page.locator('#multiplayerFragLimit').getAttribute('min'), '1');
  assert.equal(await page.locator('#multiplayerFragLimit').getAttribute('step'), '1');
  assert.equal(await page.locator('#multiplayerFragLimit').locator('xpath=ancestor::label[1]').locator('b').textContent(), 'Limit przejęć');
  assert.equal(await page.locator('#multiplayerBots').isDisabled(), true, 'unqualified CTF bots must stay disabled');
  assert.equal(await page.locator('#multiplayerModel option[value="model_player_failed_transfer"]').count(), 0,
    'team modes must not expose a model without a retail team assignment');
}

async function assertLegalDialog(page) {
  await page.locator('#menuButton').click();
  await page.locator('#aboutButton').click();
  await page.locator('#aboutDialog').waitFor({ state: 'visible' });
  const legalLinks = await page.locator('#aboutDialog a[href^="/legal/"], #aboutDialog a[href^="/source/"]').evaluateAll(
    (links) => links.map((link) => link.getAttribute('href'))
  );
  assert.ok(legalLinks.includes('/legal/GPL-3.0.txt'));
  assert.ok(legalLinks.includes('/legal/QUAKE4-SDK-EULA.rtf'));
  assert.ok(legalLinks.includes('/source/openq4-web-source.tar.xz'));
  await page.locator('#aboutDialog button[value="close"]').click();
  await page.locator('#aboutDialog').waitFor({ state: 'hidden' });
}

async function desktopSmoke(browser) {
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'pl-PL' });
  await seedPersistedModel(context);
  const { page, failures } = await openLauncherPage(context);

  assert.equal(await page.locator('#multiplayerModel').inputValue(), 'model_player_marine', 'the first dynamic catalog must restore the persisted model');
  assert.equal((await page.evaluate(() => window.Q4App.launcher.getState())).profile.model, 'model_player_marine');

  await page.evaluate(() => window.Q4App.launcher.setBusy(true, 'Test przygotowania meczu'));
  await page.locator('#campaignButton').click();
  assert.equal(await page.locator('#launcher').getAttribute('aria-busy'), 'true',
    'topbar navigation must not clear an in-flight launch');
  assert.equal(await page.locator('#campaignPlayButton').isDisabled(), true);
  assert.equal(await page.locator('#multiplayerPlayButton').isDisabled(), true);
  assert.equal(await page.locator('#replayPlayButton').isDisabled(), true);
  await page.evaluate(() => window.Q4App.launcher.setBusy(false));

  await assertLauncherView(page, 'campaign');
  await page.locator('[data-difficulty="3"]').click();
  assert.equal((await page.evaluate(() => window.Q4App.launcher.getState())).difficulty, 3);

  await assertLauncherView(page, 'multiplayer');
  assert.equal(await page.locator('#multiplayerModel option').count(), PLAYER_MODELS.length + 1);
  await page.locator('#multiplayerModel').selectOption('model_player_kane_strogg');
  assert.equal((await page.evaluate(() => window.Q4App.launcher.getState())).profile.model, 'model_player_kane_strogg');
  await page.evaluate(() => window.Q4App.refreshLauncherStatus());
  assert.equal(await page.locator('#multiplayerModel').inputValue(), 'model_player_kane_strogg', 'a status refresh must not overwrite the user model selection');
  assert.equal((await page.evaluate(() => window.Q4App.launcher.getState())).profile.model, 'model_player_kane_strogg');
  await assertCtfSelection(page);
  await assertNoViewportOverflow(page);
  await assertLauncherLayout(page, 'multiplayer');

  await assertLauncherView(page, 'replays');
  await page.locator('#launcherReplayCount').waitFor({ state: 'visible' });
  await page.waitForFunction(() => document.querySelector('#launcherReplayCount')?.textContent === '2');
  await page.locator('[data-replay-id="ctf-heartless"]').click();
  assert.equal(await page.locator('#replayPlayButton').isEnabled(), true);
  assert.match(await page.locator('[data-replay-id="ctf-heartless"]').textContent(), /12:04/);
  assert.match(await page.locator('#launcherReplayDetails').textContent(), /mp\/q4ctf1/);
  await page.evaluate(() => {
    window.Q4App.importer.ensureServerRetail = async () => ({});
  });
  await page.locator('#replayPlayButton').click();
  await page.waitForFunction(() => document.querySelector('#launcherAnnouncement')?.dataset.level === 'error');
  const replayError = await page.locator('#launcherAnnouncement').textContent();
  assert.match(replayError, /nie zakończył ładowania/i);
  assert.doesNotMatch(replayError, /^\s*\{/,
    'the replay launcher must not expose a raw JSON error to the player');
  for (let index = failures.length - 1; index >= 0; index -= 1) {
    if (/^console: Failed to load resource: .*status of 504/.test(failures[index])) failures.splice(index, 1);
  }
  await page.locator('#replaySearch').fill('mp/q4web_arena');
  assert.equal(await page.locator('#launcherReplayCount').textContent(), '1');
  await page.locator('#replaySearch').fill('');

  await assertLauncherView(page, 'settings');
  await page.locator('#settingFov').fill('105');
  await page.locator('#settingFov').dispatchEvent('input');
  assert.equal((await page.evaluate(() => window.Q4App.launcher.getState())).settings.fov, 105);

  await assertLauncherView(page, 'multiplayer');
  await assertCtfSelection(page);
  await assertLegalDialog(page);
  await page.screenshot({ path: path.join(outDir, 'q4-launcher-desktop.png'), fullPage: true });
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

async function assertTouchControls(page) {
  const inputEvidence = await page.evaluate(() => {
    const events = [];
    const router = window.Q4App.input;
    router.sink = {
      action: (name, down, source) => events.push({ kind: 'action', name, down, source }),
      move: (x, y, source) => events.push({ kind: 'move', x, y, source }),
      look: (x, y, source) => events.push({ kind: 'look', x, y, source }),
      weapon: (direction, source) => events.push({ kind: 'weapon', direction, source })
    };
    document.body.classList.add('engine-active');
    const overlay = document.querySelector('#touchOverlay');
    overlay.setAttribute('aria-hidden', 'false');

    const dispatch = (element, type, id, x, y) => element.dispatchEvent(new PointerEvent(type, {
      pointerId: id,
      pointerType: 'touch',
      clientX: x,
      clientY: y,
      bubbles: true,
      cancelable: true,
      isPrimary: id === 41
    }));
    const move = document.querySelector('#moveZone');
    move.setPointerCapture = () => {};
    const moveRect = move.getBoundingClientRect();
    dispatch(move, 'pointerdown', 41, moveRect.left + moveRect.width * 0.5, moveRect.top + moveRect.height * 0.5);
    dispatch(move, 'pointermove', 41, moveRect.left + moveRect.width * 0.8, moveRect.top + moveRect.height * 0.2);

    const fire = document.querySelector('[data-action="fire"]');
    fire.setPointerCapture = () => {};
    const fireRect = fire.getBoundingClientRect();
    dispatch(fire, 'pointerdown', 42, fireRect.left + fireRect.width * 0.5, fireRect.top + fireRect.height * 0.5);
    dispatch(fire, 'pointerup', 42, fireRect.left + fireRect.width * 0.5, fireRect.top + fireRect.height * 0.5);

    const ptt = document.querySelector('[data-action="ptt"]');
    ptt.setPointerCapture = () => {};
    const pttRect = ptt.getBoundingClientRect();
    dispatch(ptt, 'pointerdown', 43, pttRect.left + pttRect.width * 0.5, pttRect.top + pttRect.height * 0.5);
    dispatch(ptt, 'pointerup', 43, pttRect.left + pttRect.width * 0.5, pttRect.top + pttRect.height * 0.5);
    dispatch(move, 'pointerup', 41, moveRect.left + moveRect.width * 0.8, moveRect.top + moveRect.height * 0.2);

    const controls = [...overlay.querySelectorAll('.touch-button, .stick-base')]
      .map((element) => ({ label: element.getAttribute('aria-label') || element.className, rect: element.getBoundingClientRect().toJSON() }))
      .filter((entry) => entry.rect.width > 0 && entry.rect.height > 0);
    const overlaps = [];
    for (let left = 0; left < controls.length; left += 1) {
      for (let right = left + 1; right < controls.length; right += 1) {
        const a = controls[left].rect;
        const b = controls[right].rect;
        const width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left));
        const height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
        if (width * height > 4) overlaps.push([controls[left].label, controls[right].label, width * height]);
      }
    }
    return { events, overlaps };
  });
  assert.ok(inputEvidence.events.some((event) => event.kind === 'move' && Math.abs(event.x) > 0.2), JSON.stringify(inputEvidence));
  assert.ok(inputEvidence.events.some((event) => event.kind === 'action' && event.name === 'fire' && event.down), JSON.stringify(inputEvidence));
  assert.ok(inputEvidence.events.some((event) => event.kind === 'action' && event.name === 'ptt' && event.down), JSON.stringify(inputEvidence));
  assert.deepEqual(inputEvidence.overlaps, [], `touch controls overlap: ${JSON.stringify(inputEvidence.overlaps)}`);
}

async function mobileSmoke(browser, { name, width, height, touchEvidence = false }) {
  const context = await browser.newContext({
    viewport: { width, height },
    screen: { width, height },
    hasTouch: true,
    isMobile: true,
    deviceScaleFactor: 2,
    locale: 'pl-PL'
  });
  const { page, failures } = await openLauncherPage(context);

  for (const view of ['campaign', 'multiplayer', 'replays', 'settings']) {
    await assertLauncherView(page, view);
  }
  await assertLauncherView(page, 'multiplayer');
  await assertCtfSelection(page);
  await assertNoViewportOverflow(page);
  await assertLauncherLayout(page, 'multiplayer');

  const manifest = await page.evaluate(async () => (await fetch('/manifest.webmanifest')).json());
  assert.equal(manifest.orientation, 'landscape');
  await page.evaluate(async () => navigator.serviceWorker?.ready);
  await page.screenshot({ path: path.join(outDir, `q4-launcher-mobile-${name}.png`), fullPage: true });

  if (touchEvidence) await assertTouchControls(page);
  assert.deepEqual(failures, [], failures.join('\n'));
  await context.close();
}

for (const pathname of ['/healthz', '/legal/NOTICE.txt', '/legal/GPL-3.0.txt', '/legal/QUAKE4-SDK-EULA.rtf']) {
  const response = await fetch(`${baseUrl}${pathname}`);
  assert.equal(response.status, 200, `${pathname} must be public`);
  assert.ok(Number(response.headers.get('content-length')) > 0, `${pathname} must not be empty`);
}

const workerResponse = await fetch(`${baseUrl}/sw.js`);
assert.equal(workerResponse.status, 200, '/sw.js must be public');
const worker = await workerResponse.text();
assert.match(worker, /const CACHE_VERSION = 'q4-shell-v6';/);
assert.match(worker, /'\/js\/menu\.js'/);
assert.match(worker, /'\/js\/menu-config\.js'/);

const browser = await chromium.launch({
  executablePath: chrome,
  headless: true,
  args: ['--enable-unsafe-swiftshader', '--use-angle=swiftshader', '--disable-dev-shm-usage', '--no-sandbox']
});
try {
  await bootReadinessRaceSmoke(browser);
  await replayPreflightOrderSmoke(browser, 'resolve');
  await replayPreflightOrderSmoke(browser, 'reject');
  await replayEscapeClaimSmoke(browser);
  await matchReservationHeartbeatSmoke(browser);
  await desktopSmoke(browser);
  await mobileSmoke(browser, { name: 'portrait', width: 390, height: 844 });
  await mobileSmoke(browser, { name: 'landscape', width: 844, height: 390, touchEvidence: true });
  process.stdout.write(`Q4 launcher shell E2E passed at ${baseUrl}; screenshots: ${outDir}\n`);
} finally {
  await browser.close();
}
