import { expect } from 'e2e';
import { chromium } from 'playwright-core';
import { test, WEB } from './t3.ts';

test('Floating preview stops resizing after focus interruption', WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project('Preview demo');
  const thread = await t3.thread({ project, title: 'Preview resize demonstration', messages: ['Show the preview demo'] });
  await t3.open(thread);
  await screen.getByRole('button', 'Toggle right panel').tap();
  await screen.getByRole('button', /^Browser/).tap();
  await screen.getByRole('textbox', 'Search or enter URL').fill('https://example.com');
  await browser.keyboard.press('Enter');
  await screen.getByRole('button', 'Float preview over chat').tap();
  const floating = browser.locator('[data-preview-mini-player]');
  await expect(floating).toBeVisible();
  const rect = () => browser.evaluate(() => {
    const r = document.querySelector('[data-preview-mini-player]')!.getBoundingClientRect();
    return { x:r.x, y:r.y, width:r.width, height:r.height };
  });
  const control = await chromium.connectOverCDP("http://127.0.0.1:19223");
  const context = control.contexts().find(c => c.pages().some(p => p.url().includes(app.baseUrl!)))!;
  const page = context.pages().find(p => p.url().includes(app.baseUrl!))!;
  await page.evaluate(() => { (window as any).__blurCount = 0; window.addEventListener("blur", () => (window as any).__blurCount++); });
  const cdp = await context.newCDPSession(page);
  await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: false });
  const resizeSource = async (width: number, height: number) => {
    await browser.evaluate(async ({ width, height }) => {
      const { commitBrowserViewportChange } = await import('/src/browser/browserViewportActions.ts');
      const { readThreadPreviewState } = await import('/src/previewStateStore.ts');
      const { previewRuntimeTabId } = await import('/src/browser/previewRuntimeTabId.ts');
      const [, environmentId, threadId] = location.pathname.split('/');
      const threadRef = { environmentId, threadId };
      const state = readThreadPreviewState(threadRef);
      const tabId = document.querySelector('[data-preview-mini-player]')!.getAttribute('data-preview-mini-player')!.slice(8);
      await commitBrowserViewportChange({ threadRef, tabId, runtimeTabId: previewRuntimeTabId(threadRef, state.serverEpoch, tabId) }, { _tag: 'freeform', width, height });
    }, { width, height });
  };
  await resizeSource(1000, 700);
  await page.waitForFunction(() => {
    const r = document.querySelector('[data-preview-mini-player]')!.getBoundingClientRect();
    const canvas = document.querySelector('[data-preview-mini-player] canvas') as HTMLCanvasElement;
    return Math.abs(r.width / r.height - 1000 / 700) < 0.01 && canvas && Math.abs(canvas.width / canvas.height - 1000 / 700) < 0.02;
  });
  await page.evaluate(() => {
    (window as any).__pointerMoves = [];
    document.addEventListener('pointermove', event => (window as any).__pointerMoves.push({ buttons: event.buttons, x: event.clientX, y: event.clientY }));
  });
  const r = await rect();
  const handle = await page.locator('[data-preview-mini-player-resize="northwest"]').boundingBox();
  if (!handle) throw new Error('Northwest resize handle missing');
  const pointerStart = { x: handle.x + handle.width / 2, y: handle.y + handle.height / 2 };
  await app.screenshot('before-resize');
  await browser.mouse.move(pointerStart.x, pointerStart.y);
  await browser.mouse.down();
  for (let i = 1; i <= 16; i++) await browser.mouse.move(pointerStart.x - i * 5, pointerStart.y - i * 45 / 16);
  await page.waitForFunction(({ width }) => document.querySelector('[data-preview-mini-player]')!.getBoundingClientRect().width > width + 10, { width: r.width });
  await app.screenshot('held-resize');
  const { targetInfo } = await cdp.send('Target.getTargetInfo');
  const appWindow = await cdp.send('Browser.getWindowForTarget', { targetId: targetInfo.targetId });
  const newPage = context.waitForEvent('page');
  await cdp.send('Target.createTarget', {
    url: 'data:text/html,<title>Other window</title><body>Release outside the app</body>',
    browserContextId: targetInfo.browserContextId,
    newWindow: true,
  });
  const other = await newPage;
  const otherCdp = await context.newCDPSession(other);
  const otherTarget = await otherCdp.send('Target.getTargetInfo');
  const otherWindow = await otherCdp.send('Browser.getWindowForTarget', { targetId: otherTarget.targetInfo.targetId });
  if (appWindow.windowId === otherWindow.windowId) throw new Error('Interruption requires a separate window');
  await other.bringToFront();
  await other.mouse.up();
  await page.bringToFront();
  await page.waitForFunction(() => (window as any).__blurCount > 0, { timeout: 3000 });
  if (!(await page.evaluate(() => (window as any).__blurCount))) throw new Error("Browser did not lose focus");
  const interrupted = await rect();
  await app.screenshot('returned-after-release');
  // No button is held when the pointer returns to the handle.
  await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: interrupted.x, y: interrupted.y, buttons: 0 });
  for (let i = 1; i <= 20; i++) await cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: interrupted.x - i * 5, y: interrupted.y - i * 3, buttons: 0 });
  const lastMove = await page.evaluate(() => (window as any).__pointerMoves.at(-1));
  if (lastMove?.buttons !== 0) throw new Error('Hover must have no button held');
  await app.screenshot('buttonless-hover');
  const after = await rect();
  await other.close();
  expect.soft(after).toEqual(interrupted);
  await browser.mouse.up();
  await resizeSource(1000, 700);
  await page.waitForFunction(() => {
    const r = document.querySelector('[data-preview-mini-player]')!.getBoundingClientRect();
    return Math.abs(r.width / r.height - 1000 / 700) < 0.01;
  });
  const start = await rect();
  await browser.mouse.move(start.x, start.y);
  await browser.mouse.down();
  for (let i = 1; i <= 15; i++) await browser.mouse.move(start.x - i * 2, start.y - i);
  const held = await rect();
  await app.screenshot('source-before-change');
  await resizeSource(700, 1000);
  // Wait for the preview's source stream to render the changed viewport before reading geometry.
  await page.waitForFunction(async () => {
    const { readThreadPreviewState } = await import('/src/previewStateStore.ts');
    const [, environmentId, threadId] = location.pathname.split('/');
    return Object.values(readThreadPreviewState({ environmentId, threadId }).sessions).some((s: any) => s.viewport?.width === 700 && s.viewport?.height === 1000);
  });
  await page.waitForFunction(() => {
    const canvas = document.querySelector('[data-preview-mini-player] canvas') as HTMLCanvasElement;
    return canvas && Math.abs(canvas.width / canvas.height - 700 / 1000) < 0.02;
  });
  await app.screenshot('source-changed-while-held');
  const changed = await rect();
  await browser.mouse.move(start.x - 45, start.y - 24);
  await app.screenshot('source-changed-resize-moved');
  const moved = await rect();
  await browser.mouse.up();
  await app.screenshot('source-change-released');
  expect.soft(changed).toEqual(held);
  expect.soft(Math.abs(moved.width / moved.height - held.width / held.height)).toBeLessThan(0.01);
  expect.soft(Math.abs((moved.y + moved.height) - (held.y + held.height))).toBeLessThan(2);

});
