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

// The in-game composition UX after the under-board status strip was removed:
//  - whose turn (or the final result) shows in a thin strip above the score plaques;
//  - the tiles left in the bag ride the exchange control as a badge and repeat at the foot of the
//    move table;
//  - staging a play tints its tiles on the board and shows the orange move-score badge, and the
//    confirm control sits in the rack's fixed 7th slot.
// Mock transport only: the mock has no dictionary (Game.svelte skips the local evaluator under the
// mock mode) and its network evaluator accepts any placement, so a staged tile always reads as legal.

test('in-game UX: turn strip, bag badge + table footer, staged-play highlight and score badge', async ({ page }) => {
  await page.goto('/');
  await page.getByRole('button', { name: /guest/i }).click();
  await page.getByRole('button', { name: /🎲/ }).click();
  await page.getByRole('button', { name: 'Random player' }).click();
  await page.locator('.variant').first().click();
  await page.getByRole('button', { name: /Start game/i }).click();
  // Attach the opponent deterministically, then it is the player's turn.
  await page.evaluate(() => (window as unknown as { __mock: { joinOpponent(): void } }).__mock.joinOpponent());
  await expect(page.getByText('Robo')).toBeVisible();

  // The old under-board status line is gone; a thin turn strip sits above the plaques instead.
  await expect(page.locator('.turnstrip')).toBeVisible();
  await expect(page.locator('.status')).toHaveCount(0);

  // The bag count rides the exchange control (the first tab) as a badge, and repeats at the foot of
  // the move table, out of the scrolling grid.
  await expect(page.locator('.tab').first().locator('.badge')).toBeVisible();
  await page.locator('.scoreboard').click(); // open the history drawer
  await expect(page.locator('.hbagfoot')).toContainText(/bag/i);
  await page.locator('.scoreboard').click(); // close it again

  // Stage a play: the pending tile reads as legal (green), the orange move-score badge shows on the
  // board, and the confirm control takes the rack's 7th slot.
  await page.locator('.rack .tile').first().click();
  await page.locator('[data-cell]:not(.filled)').nth(112).click(); // centre (row 7, col 7)
  await expect(page.locator('[data-cell].pending.legal')).toHaveCount(1);
  await expect(page.locator('.scorebadge')).toBeVisible();
  await expect(page.locator('.make')).toBeVisible();
});

test('online game offline: banner shows, the tray and comms entry freeze, and it thaws on recovery', async ({ page }) => {
  await page.goto('/');
  await page.getByRole('button', { name: /guest/i }).click();
  await page.getByRole('button', { name: /🎲/ }).click();
  await page.getByRole('button', { name: 'Random player' }).click();
  await page.locator('.variant').first().click();
  await page.getByRole('button', { name: /Start game/i }).click();
  await page.evaluate(() => (window as unknown as { __mock: { joinOpponent(): void } }).__mock.joinOpponent());
  await expect(page.getByText('Robo')).toBeVisible();

  // Baseline online: no banner, the tray is interactive, the comms (chat/dictionary) entry is enabled.
  await expect(page.locator('.offline-banner')).toHaveCount(0);
  await expect(page.locator('.rack .tile').first()).toBeEnabled();

  // The connection drops (the net hook rides past the hysteresis straight to offline).
  await page.evaluate(() => (window as unknown as { __net: { offline(): void } }).__net.offline());

  // The explicit "connection lost" banner appears and every rack tile freezes (cannot compose a move).
  await expect(page.locator('.offline-banner')).toBeVisible({ timeout: 15000 });
  const tiles = page.locator('.rack .tile');
  const n = await tiles.count();
  expect(n).toBeGreaterThan(0);
  for (let i = 0; i < n; i++) await expect(tiles.nth(i)).toBeDisabled();

  // The comms entry (💬) and the resign control are HIDDEN offline (like the frozen social controls);
  // both live in the history drawer, so open it and assert no action icons remain in its header.
  await page.locator('.scoreboard').click();
  await expect(page.locator('.hhead button.hicon')).toHaveCount(0);

  // Recovery: the network returns, the banner clears, the tray is interactive and the comms entry returns.
  await page.evaluate(() => (window as unknown as { __net: { online(): void } }).__net.online());
  await expect(page.locator('.offline-banner')).toHaveCount(0, { timeout: 15000 });
  await expect(page.locator('.rack .tile').first()).toBeEnabled();
  await expect(page.locator('button:has(.chat-ico)')).toBeVisible();
});

test('online game offline: the dictionary still accepts input and checks (variant seeded from cache)', async ({ page }) => {
  await page.goto('/');
  await page.getByRole('button', { name: /guest/i }).click();
  // A Russian (erudit_ru — first in the mock's variant preferences) game, so the offline dictionary is
  // exercised with a non-Latin alphabet that differs from the panel's default 'scrabble_en'. The panel
  // seeds its variant + pinned version from the cached game; the mock resolves gameState even offline
  // (no kill switch), so on the real backend — where that fetch fails offline — the seed is what keeps
  // the input and the dawg fallback working. This guards the offline dictionary flow end-to-end.
  await page.getByRole('button', { name: /🎲/ }).click();
  await page.getByRole('button', { name: 'Random player' }).click();
  await page.locator('.variant').first().click();
  await page.getByRole('button', { name: /Start game/i }).click();
  await page.evaluate(() => (window as unknown as { __mock: { joinOpponent(): void } }).__mock.joinOpponent());
  await expect(page.getByText('Robo')).toBeVisible();

  // Go offline, then open the dictionary directly (its in-game entry is hidden offline) so CheckScreen
  // mounts with no network and must seed the variant/version from the cached game, not a failed fetch.
  await page.evaluate(() => (window as unknown as { __net: { offline(): void } }).__net.offline());
  await expect(page.locator('.offline-banner')).toBeVisible({ timeout: 15000 });
  await page.evaluate(() => {
    const route = location.hash.slice(1); // /game/<id>
    (window as unknown as { __router: { navigate(p: string): void } }).__router.navigate(route + '/check');
  });

  // The check input keeps the Cyrillic word (the variant is known) and the Check button is usable —
  // the outcome the cache seed guarantees on the real backend where the offline gameState fetch fails.
  const input = page.locator('.check input');
  await expect(input).toBeVisible();
  await input.fill('СЛОВО');
  await expect(input).toHaveValue('СЛОВО');
  await expect(page.locator('.check button')).toBeEnabled();

  // Running the check resolves to a verdict (or the neutral offline note), proving the fallback path
  // executes rather than hanging.
  await page.locator('.check button').click();
  await expect(page.locator('.verdict')).toBeVisible();
});
