import { expect, test } from '@playwright/test';

const boundary = '#polymodel-viewer-boundary';
const canvas = '#polymodel-viewer-canvas';
const preview = '#polymodel-viewer-preview';
const chestAssetPath = '/models/body_f_chest-v4.stl';
const forearmAssetPath = '/models/body_f_forearm_2x-v9.stl';

async function acquisitions(page) {
  return Number(await page.locator(boundary).getAttribute('data-context-acquisitions'));
}

async function expectViewerReady(page) {
  await expect(page.locator(canvas)).toHaveCount(1);
  await expect(page.locator(canvas)).toHaveAttribute('data-mounted', 'true');
  await expect(page.locator(boundary)).toHaveAttribute('data-renderer-kind', 'three-d');
  await expect(page.locator(canvas)).toHaveAttribute('data-renderer-kind', 'three-d');
  await expect(page.locator(preview)).toHaveCount(0);
}

test('the demo viewer route displays the committed STL asset inside the shell', async ({ page }) => {
  await page.goto('/viewer/body-f-chest-v4');

  await expect(page).toHaveURL(/\/viewer\/body-f-chest-v4$/);
  await expectViewerReady(page);
  // The product shell wraps the demo viewer route (reachable by direct URL only).
  await expect(page.getByRole('link', { name: 'Polymodel home' })).toBeVisible();
  // The selected model and ready status surface on the boundary's data attributes
  // and in the visible header chrome (status pill + active model control).
  await expect(page.locator(boundary)).toHaveAttribute('data-session-label', 'body-f-chest-v4#1');
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', chestAssetPath);
  await expect(page.locator(boundary)).toHaveAttribute('data-viewer-status', 'Viewer ready');
  await expect(page.getByText('Viewer ready')).toBeVisible();
  await expect(page.getByRole('button', { name: 'Body F chest v4' })).toBeVisible();
});

test('direct viewer routes select deterministic asset-backed models', async ({ page }) => {
  await page.goto('/viewer/body-f-chest-v4');
  await expectViewerReady(page);
  await expect(page.locator(boundary)).toHaveAttribute('data-session-label', 'body-f-chest-v4#1');
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', chestAssetPath);
  await expect(page.getByRole('button', { name: 'Body F chest v4' })).toBeVisible();

  await page.goto('/viewer/body-f-forearm-2x-v9');
  await expectViewerReady(page);
  await expect(page.locator(boundary)).toHaveAttribute('data-session-label', 'body-f-forearm-2x-v9#1');
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', forearmAssetPath);
  await expect(page.getByRole('button', { name: 'Body F forearm 2x v9' })).toBeVisible();
});

test('model switching keeps one WebGL context and updates renderer session metadata', async ({ page }) => {
  await page.goto('/viewer/body-f-chest-v4');
  await expectViewerReady(page);
  await expect(page.locator(canvas)).toHaveCount(1);
  // The WebGL context is acquired exactly once for the initial mount.
  await expect.poll(acquisitions.bind(null, page)).toBe(1);

  await page.getByRole('button', { name: 'Body F forearm 2x v9' }).click();
  await expectViewerReady(page);
  await expect(page.locator(canvas)).toHaveCount(1);
  await expect(page.locator(boundary)).toHaveAttribute('data-session-label', 'body-f-forearm-2x-v9#1');
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', forearmAssetPath);
  // Switching models must NOT acquire a second WebGL context (in-place replace).
  await expect.poll(acquisitions.bind(null, page)).toBe(1);

  await page.getByRole('button', { name: 'Body F chest v4' }).click();
  await expectViewerReady(page);
  await expect(page.locator(canvas)).toHaveCount(1);
  await expect(page.locator(boundary)).toHaveAttribute('data-session-label', 'body-f-chest-v4#1');
  await expect.poll(acquisitions.bind(null, page)).toBe(1);
});

test('rapid model switching through loading windows keeps a single WebGL context', async ({ page }) => {
  await page.goto('/viewer/body-f-chest-v4');
  await expectViewerReady(page);
  const chest = page.getByRole('button', { name: 'Body F chest v4' });
  const forearm = page.getByRole('button', { name: 'Body F forearm 2x v9' });

  // Switch rapidly without waiting for ready between clicks, exercising the
  // async Loading window and stale-fetch cancellation from use_resource.
  await forearm.click();
  await chest.click();
  await forearm.click();
  await chest.click();

  await expectViewerReady(page);
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', chestAssetPath);
  // The retained context is never recreated across the switches.
  await expect.poll(acquisitions.bind(null, page)).toBe(1);
});

test('resize callback remains single after model transitions', async ({ page }) => {
  await page.goto('/viewer/body-f-chest-v4');
  const currentResizeCount = async () => Number(await page.locator(boundary).getAttribute('data-resize-count'));
  await expectViewerReady(page);
  const before = await currentResizeCount();
  await page.setViewportSize({ width: 1100, height: 760 });
  await expect.poll(currentResizeCount).toBe(before + 1);

  await page.getByRole('button', { name: 'Body F forearm 2x v9' }).click();
  await expectViewerReady(page);
  await expect(page.locator(boundary)).toHaveAttribute('data-asset-path', forearmAssetPath);

  const afterTransition = await currentResizeCount();
  await page.setViewportSize({ width: 1000, height: 700 });
  await expect.poll(currentResizeCount).toBe(afterTransition + 1);
});
