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

// The redesigned move history: a ruled matrix with one column per seat (no player names, no
// running total), opened by a swipe-down on the zoom-out board. The gesture is touch-only and
// the desktop projects have no touch input, so the swipe is dispatched as PointerEvents with
// pointerType:'touch'; a phone viewport so the board fills the width.
test.use({ viewport: { width: 390, height: 844 } });

async function openGame(page: Page): Promise<void> {
  await page.goto('/');
  await page.getByRole('button', { name: /guest/i }).click();
  await page.getByRole('button', { name: /Ann/ }).click(); // the seeded active game G1 (your turn)
  await expect(page.locator('[data-cell]').first()).toBeVisible();
  await expect(page.locator('.pane')).toHaveCount(1); // let the screen-slide settle
}

/** touchSwipeDown dispatches a single-finger vertical touch drag on the board wrapper. */
async function touchSwipeDown(page: Page, fromY: number, toY: number): Promise<void> {
  const box = (await page.locator('.boardwrap').boundingBox())!;
  const x = box.x + box.width / 2;
  await page.locator('.boardwrap').evaluate(
    (el, { x, fromY, toY }) => {
      const fire = (type: string, y: number) =>
        el.dispatchEvent(
          new PointerEvent(type, { pointerType: 'touch', clientX: x, clientY: y, bubbles: true, cancelable: true }),
        );
      fire('pointerdown', fromY);
      for (let i = 1; i <= 8; i++) fire('pointermove', fromY + ((toY - fromY) * i) / 8);
      fire('pointerup', toY);
    },
    { x, fromY, toY },
  );
}

test('a swipe-down on the zoom-out board opens the history', async ({ page }) => {
  await openGame(page);
  await page.locator('.stage').evaluate((el) => (el.scrollTop = 0)); // the pull only opens at the top
  const box = (await page.locator('.boardwrap').boundingBox())!;

  await touchSwipeDown(page, box.y + 20, box.y + 180);

  await expect(page.locator('.history')).toBeVisible();
  await expect(page.locator('.boardwrap.slid')).toBeVisible();
});

test('a recall drag onto the rack does not break the next board swipe', async ({ page }) => {
  await openGame(page);
  // Place a tile, then drag it back onto the rack — the recall's pointerup lands off the
  // boardwrap, which once left a stale pointer there so the next swipe read as multi-touch.
  await page.locator('.rack .tile').first().click();
  await page.locator('[data-cell]:not(.filled)').nth(30).click();
  const pending = page.locator('[data-cell].pending');
  await expect(pending).toHaveCount(1);
  const fb = (await pending.first().boundingBox())!;
  const slot = (await page.locator('[data-rack] .tile').nth(2).boundingBox())!;
  await page.mouse.move(fb.x + fb.width / 2, fb.y + fb.height / 2);
  await page.mouse.down();
  await page.mouse.move(slot.x + 2, slot.y + slot.height / 2, { steps: 10 });
  await page.mouse.up();
  await expect(pending).toHaveCount(0);

  // The swipe-down still opens the history (the recall pointer was reconciled).
  await page.locator('.stage').evaluate((el) => (el.scrollTop = 0));
  const box = (await page.locator('.boardwrap').boundingBox())!;
  await touchSwipeDown(page, box.y + 20, box.y + 180);
  await expect(page.locator('.history')).toBeVisible();
});

test('the history is a per-seat grid with move scores but no names or running total', async ({ page }) => {
  await openGame(page);
  await page.locator('.scoreboard').click(); // open the history (gesture is covered separately)

  const grid = page.locator('.hgrid');
  await expect(grid).toBeVisible();
  // G1: You [HELLO 16, RAT 3] vs Ann [WORLD 9, AND 4] — a 2×2 matrix, no empty/thinking cells.
  await expect(page.locator('.hcell')).toHaveCount(4);
  await expect(grid).toContainText('HELLO');
  await expect(grid).toContainText('(3)'); // RAT's per-move score
  // The running total (RAT's was 19) and the seat names no longer appear in the table.
  await expect(grid).not.toContainText('19');
  await expect(grid).not.toContainText('Ann');
});

test('a two-finger pinch does not open the history (pinch-zoom vs swipe-down)', async ({ page }) => {
  await openGame(page);
  await page.locator('.stage').evaluate((el) => (el.scrollTop = 0));
  const box = (await page.locator('.boardwrap').boundingBox())!;
  const cx = box.x + box.width / 2;
  const y0 = box.y + 40;

  // Two fingers land, then spread apart while their centroid drifts down — a pinch-out whose
  // downward component used to also slide the history open. With the single-pointer guard the
  // second finger disarms the pull, so the history stays closed.
  await page.locator('.boardwrap').evaluate(
    (el, { cx, y0 }) => {
      const fire = (type: string, id: number, x: number, y: number) =>
        el.dispatchEvent(
          new PointerEvent(type, {
            pointerType: 'touch',
            pointerId: id,
            isPrimary: id === 1,
            clientX: x,
            clientY: y,
            bubbles: true,
            cancelable: true,
          }),
        );
      fire('pointerdown', 1, cx - 20, y0);
      fire('pointerdown', 2, cx + 20, y0);
      for (let i = 1; i <= 8; i++) {
        const d = i * 18;
        fire('pointermove', 1, cx - 20 - d, y0 + d);
        fire('pointermove', 2, cx + 20 + d, y0 + d);
      }
      fire('pointerup', 1, cx - 164, y0 + 144);
      fire('pointerup', 2, cx + 164, y0 + 144);
    },
    { cx, y0 },
  );

  await expect(page.locator('.history')).toHaveCount(0);
});

/** tallHistoryRows stretches the move-table cells so the seeded four-move game overflows the
 *  table's scroller — the condition under which "opens at the newest move" is observable at all
 *  (with no overflow the assertion would hold vacuously). Injected at document start, since the
 *  table is pinned as soon as it is shown. */
async function tallHistoryRows(page: Page): Promise<void> {
  await page.addInitScript(() => {
    document.addEventListener('DOMContentLoaded', () => {
      const style = document.createElement('style');
      style.textContent = '.hcell { min-height: 200px !important; }';
      document.head.append(style);
    });
  });
}

test('the history opens scrolled to the newest move', async ({ page }) => {
  await tallHistoryRows(page);
  await openGame(page);
  await page.locator('.scoreboard').click(); // open the history

  const m = await page
    .locator('.hgridwrap')
    .evaluate((el) => ({ top: el.scrollTop, scroll: el.scrollHeight, client: el.clientHeight }));
  expect(m.scroll).toBeGreaterThan(m.client); // the table really overflows — no vacuous pass
  expect(m.top).toBeGreaterThanOrEqual(m.scroll - m.client - 1); // pinned to the last row
});

test('a swipe-down on the zoomed-in board does not open the history (native scroll wins)', async ({ page }) => {
  await openGame(page);
  // Double-tap an empty cell to zoom in (two synchronous clicks = a double-tap).
  await page
    .locator('[data-cell]:not(.filled)')
    .nth(20)
    .evaluate((el: HTMLElement) => {
      el.click();
      el.click();
    });
  await expect(page.locator('.viewport.zoomed')).toBeVisible();
  const box = (await page.locator('.boardwrap').boundingBox())!;

  await touchSwipeDown(page, box.y + 20, box.y + 180);

  await expect(page.locator('.history')).toHaveCount(0);
});
