// apps/client/test/e2e/cli-08.e2e.test.ts
// [int->REQ-CLI-08] [int->REQ-CLI-07]
// Plan 06-08 Task 2 — CLI-08 hard-milestone two-client smoke. The MVP merge
// gate. Was committed RED on plan 06-01 (test.skip(true, ...)); plan 06-08
// flips the skip and turns the assertions GREEN by:
//   - LoginScene DOM form (plan 06-04 / forms/login.html)
//   - GameScene WS join + state apply + render + chat HUD (plan 06-07)
//   - Authoritative server (Phase 4) + deployed staging (Phase 5)
//
// Two contexts = two independent users (cookies, storage, WS). The test
// asserts:
//   1. Both clients reach the GameScene canvas (data-game-ready=true)
//   2. Each client sees the OTHER nameplate (DOM-mirror per remote player)
//   3. A presses 'd' for 1s; B sees A's data-x-coord increase (movement
//      round-trip via authoritative server broadcast)
//   4. A types in chat; B sees the chat-line in its DOM mirror
//   5. A force-closes the WS; Colyseus auto-reconnect within grace returns
//      A to data-game-ready=true (Phase 4 D-12 reconnection grace)

import { test, expect, loginAs, waitForGameReady } from './fixtures.js';

test('CLI-08 hard milestone — two clients see each other move + chat round-trip + reconnect grace', async ({
  browser,
  accountA,
  accountB,
  inviteSuffix,
}) => {
  const ctxA = await browser.newContext();
  const ctxB = await browser.newContext();
  const a = await ctxA.newPage();
  const b = await ctxB.newPage();

  try {
    // (1) Both clients log in and reach GameScene.
    await loginAs(a, accountA, inviteSuffix);
    await loginAs(b, accountB, inviteSuffix);
    await waitForGameReady(a);
    await waitForGameReady(b);

    // (1a) D-30 room-id assertion: window.__rebno.roomId must be 'mvp-room'
    // (set by 06-14 Task 3 __rebno hook). [int->REQ-CLI-07]
    const roomIdA = await a.evaluate(
      () => (window as unknown as { __rebno?: { roomId?: string } }).__rebno?.roomId,
    );
    expect(roomIdA).toBe('mvp-room');
    await expect(a.locator('canvas[data-room-id="mvp-room"]')).toBeAttached({ timeout: 5_000 });

    // (2) Each client sees the OTHER's nameplate (DOM-mirror per remote
    //     player; plan 06-07 Nameplate.ts emits `[data-nameplate=<username>]`).
    //     The mirror is intentionally hidden (display:none) per UI-SPEC §
    //     Nameplate — the visible label lives in the Phaser canvas. Assert
    //     attached-to-DOM instead of visibility.
    await expect(
      a.locator(`[data-nameplate="${accountB.username}"]`),
    ).toBeAttached({ timeout: 10_000 });
    await expect(
      b.locator(`[data-nameplate="${accountA.username}"]`),
    ).toBeAttached({ timeout: 10_000 });

    // (3) A presses 'd' for 1s; B observes A's data-x-coord advance.
    const beforeXStr = await b
      .locator(`[data-nameplate="${accountA.username}"]`)
      .getAttribute('data-x-coord');
    const beforeX = Number(beforeXStr ?? '0');
    expect(Number.isFinite(beforeX)).toBe(true);

    // Focus A's canvas so keypress reaches the Phaser input layer.
    await a.locator('canvas[data-game-ready="true"]').click();
    await a.keyboard.down('d');
    await a.waitForTimeout(1_000);
    await a.keyboard.up('d');
    // Allow ~3 server ticks (50ms each at 20Hz) for B to receive the
    // authoritative broadcast.
    await b.waitForTimeout(500);

    const afterXStr = await b
      .locator(`[data-nameplate="${accountA.username}"]`)
      .getAttribute('data-x-coord');
    const afterX = Number(afterXStr ?? '0');
    expect(afterX).toBeGreaterThan(beforeX + 30); // ≥30 px movement; conservative.

    // (4) Chat round-trip. A presses Enter to focus chat, types, Enter to send.
    //     B sees a `.chat-line` matching `<sender>: <text>` within 5s.
    await a.keyboard.press('Enter');
    await a.locator('[data-chat-input]').waitFor({ timeout: 5_000 });
    await a.fill('[data-chat-input]', 'hello from A');
    await a.locator('[data-chat-input]').press('Enter');
    await expect(
      b
        .locator('.chat-line')
        .filter({ hasText: `${accountA.username}: hello from A` }),
    ).toBeVisible({ timeout: 5_000 });

    // (5) Reconnect grace — kill A's WS, wait, expect data-game-ready=true again.
    //     Colyseus auto-reconnects via the cached reconnectionToken (Phase 4 D-12).
    await a.evaluate(() => {
      const w = window as unknown as {
        __rebno?: { room?: { connection?: { close?: () => void } } };
      };
      w.__rebno?.room?.connection?.close?.();
    });
    // Briefly drop ready; auto-reconnect re-applies state and re-asserts ready.
    await a.waitForTimeout(2_000);
    await expect(
      a.locator('canvas[data-game-ready="true"]'),
    ).toBeVisible({ timeout: 15_000 });
  } finally {
    await ctxA.close();
    await ctxB.close();
  }
});
