// apps/client/test/e2e/fixtures.ts
// [int->REQ-CLI-08]
// [unit->REQ-CLI-06]
// Plan 06-08 Task 2 — full Playwright two-client harness fixtures.
// Plan 06-10 Task 2 — adds computeContrastInPage helper for WCAG AA checks.
// Drives the LoginScene DOM form (D-01 DOM-overlay) seeded by plan 06-04
// (`apps/client/public/forms/login.html`) and waits for the GameScene
// `<canvas data-game-ready="true">` anchor emitted by plan 06-07 GameScene.

import { test as base, expect, type Page } from '@playwright/test';

export interface Account {
  username: string;
  password: string;
}

export interface AppFixtures {
  /**
   * The `?invite=...` URL suffix appended to every navigation. Empty when
   * STAGING_INVITE_TOKEN is unset (local server with STAGING_MODE=0).
   */
  inviteSuffix: string;
  /**
   * Two seeded accounts. Defaults are local-dev fallbacks; staging CI sets
   * UAT_ACCOUNT_A/B + UAT_PASSWORD_A/B.
   */
  accountA: Account;
  accountB: Account;
}

export const test = base.extend<AppFixtures>({
  inviteSuffix: async ({}, use) => {
    const tok = process.env.STAGING_INVITE_TOKEN;
    await use(tok ? `?invite=${encodeURIComponent(tok)}` : '');
  },
  accountA: async ({}, use) => {
    await use({
      username: process.env.UAT_ACCOUNT_A ?? 'alice',
      password: process.env.UAT_PASSWORD_A ?? 'alicepass1234',
    });
  },
  accountB: async ({}, use) => {
    await use({
      username: process.env.UAT_ACCOUNT_B ?? 'bob',
      password: process.env.UAT_PASSWORD_B ?? 'bobpass1234',
    });
  },
});

export { expect };

/**
 * WCAG-AA contrast helper — evaluable string for page.evaluate().
 *
 * Usage:
 *   const result = await page.evaluate(
 *     new Function('selector', `return (${computeContrastInPage})(selector)`) as
 *       (sel: string) => { fg: string; bg: string; ratio: number },
 *     'form#login-form h1',
 *   );
 *   expect(result.ratio).toBeGreaterThanOrEqual(4.5);
 *
 * The helper:
 *   1. Queries the element by CSS selector — throws if not found (guards
 *      against false-green caused by a missing element being silently skipped).
 *   2. Reads getComputedStyle(el).color as the foreground RGB triple.
 *   3. Walks the ancestor chain until it finds the first element whose
 *      getComputedStyle().backgroundColor is NOT transparent / rgba(0,0,0,0).
 *      Falls back to document.body if no opaque ancestor is found.
 *   4. Parses both rgb() / rgba() strings into R, G, B [0..255] components.
 *   5. Computes WCAG relative luminance and contrast ratio per the formula at:
 *      https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
 *   6. Returns { fg: "<computed color string>", bg: "<computed bg string>",
 *                ratio: <number> } for use in expect() assertions.
 *
 * [unit->REQ-CLI-06]
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const computeContrastInPage: string = /* javascript */ `
function computeContrastInPage(selector) {
  var el = document.querySelector(selector);
  if (!el) throw new Error('computeContrastInPage: element not found: ' + selector);

  // --- Parse "rgb(r, g, b)" or "rgba(r, g, b, a)" → [r, g, b] ---
  function parseRGB(str) {
    var m = str.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);
    if (!m) throw new Error('Cannot parse color: ' + str);
    return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
  }

  function isTransparent(str) {
    if (str === 'transparent') return true;
    var m = str.match(/rgba\\(\\d+,\\s*\\d+,\\s*\\d+,\\s*([\\d.]+)\\)/);
    if (m && parseFloat(m[1]) === 0) return true;
    return false;
  }

  // --- Compute WCAG relative luminance for an [r,g,b] triple ---
  function luminance(r, g, b) {
    var c = [r / 255, g / 255, b / 255].map(function (v) {
      return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
  }

  // --- Foreground color ---
  var fgStr = window.getComputedStyle(el).color;

  // --- Walk ancestors for first opaque background ---
  var bgStr = null;
  var node = el;
  while (node && node !== document.documentElement) {
    var bg = window.getComputedStyle(node).backgroundColor;
    if (bg && !isTransparent(bg)) {
      bgStr = bg;
      break;
    }
    node = node.parentElement;
  }
  if (!bgStr) {
    bgStr = window.getComputedStyle(document.body).backgroundColor;
  }
  if (!bgStr || isTransparent(bgStr)) {
    // Ultimate fallback: treat as white (safest for reporting purposes)
    bgStr = 'rgb(255, 255, 255)';
  }

  var fg = parseRGB(fgStr);
  var bg = parseRGB(bgStr);
  var L1 = luminance(fg[0], fg[1], fg[2]);
  var L2 = luminance(bg[0], bg[1], bg[2]);
  var ratio = (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);

  return { fg: fgStr, bg: bgStr, ratio: Math.round(ratio * 100) / 100 };
}
`;

/**
 * Drive the LoginScene DOM form for a single page.
 *
 * Selectors are the canonical surface emitted by `apps/client/public/forms/login.html`
 * (loaded by LoginScene via `this.load.html('login-form', ...)`):
 *   - `#username` / `[name=username]` (autocomplete=username)
 *   - `#password` / `[name=password]` (autocomplete=current-password)
 *   - `button[type=submit]`
 *
 * Navigates to `/${inviteSuffix}` first; the LoginScene mounts the form on
 * scene start. After submit, GameScene transition is signalled by the
 * `<canvas data-game-ready="true">` attribute (set on first state snapshot).
 */
export async function loginAs(
  page: Page,
  account: Account,
  inviteSuffix: string,
): Promise<void> {
  await page.goto(`/${inviteSuffix}`);
  // Login form is part of the Phaser DOMElement scene — wait for the input
  // node to render before attempting to fill.
  await page.waitForSelector('#username', { timeout: 10_000 });
  await page.fill('#username', account.username);
  await page.fill('#password', account.password);
  await page.click('button[type=submit]');
}

/**
 * Wait for GameScene to be ready (canvas data-game-ready="true").
 * Per plan 06-07, GameScene sets this attribute on the first state snapshot
 * (server's onCreate broadcast); seeing it confirms WS connect + room join
 * + initial state apply succeeded end-to-end.
 */
export async function waitForGameReady(page: Page): Promise<void> {
  await expect(page.locator('canvas[data-game-ready="true"]')).toBeVisible({
    timeout: 15_000,
  });
}
