import { expect, test, type Page } from '@playwright/test';
import { createBrowserWorkspace } from './common';

const SAVE_KEY = process.platform === 'darwin' ? 'Meta+s' : 'Control+s';

function watchFatalRuntimeFailures(page: Page) {
  const failures: string[] = [];
  page.on('pageerror', (error) => failures.push(`pageerror: ${error.message}`));
  page.on('console', (message) => {
    if (message.type() !== 'error') return;
    const text = message.text();
    if (/\b(?:Uncaught|TypeError|ReferenceError|RangeError|SyntaxError)\b|Failed to fetch dynamically imported module/i.test(text)) {
      failures.push(`console: ${text}`);
    }
  });
  return failures;
}

async function compileCurrentProject(page: Page) {
  await page.getByRole('button', { name: 'Compiler settings' }).click();
  await expect(page.getByText('Browser compiler runtime')).toBeVisible();
  await expect(page.getByText('Browser compiler runtime')).toBeVisible();
  await page.keyboard.press('Escape');
  await page.getByRole('button', { name: 'Compile', exact: true }).click();
  await expect(page.getByTestId('interactive-pdf-preview').locator('canvas')).toBeVisible({ timeout: 120_000 });
  await page.keyboard.press('Escape');
  await expect(page.getByTestId('interactive-pdf-preview').locator('canvas')).toBeVisible({
    timeout: 30_000,
  });
}

test('R14.10 integrated Source → Visual → MathLive → Compile → PDF → SyncTeX → reload → ZIP journey', async ({
  page,
}) => {
  test.setTimeout(420_000);
  const fatalRuntimeFailures = watchFatalRuntimeFailures(page);
  const workspaceName = `integrated-r14-10-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  await page.getByRole('link', { name: /^main\.tex$/ }).click();

  const source = page.getByLabel('LaTeX source editor');
  const initialSource = [
    '\\documentclass{article}',
    '\\usepackage{amsmath}',
    '\\begin{document}',
    '\\section{Integrated release candidate}',
    'Visual prose has a \\textbf{editable} marker and equation $E=mc^2$.',
    'R14.10 searchable PDF marker for SyncTeX.',
    '\\end{document}',
    '',
  ].join('\n');
  await source.fill(initialSource);
  await source.press(SAVE_KEY);

  // Source and Visual must be two persistent views of one canonical document.
  await source.evaluate((element) => {
    element.setAttribute('data-r14-10-source-instance', 'persistent');
  });
  await page.getByRole('button', { name: 'Visual' }).click();
  const visual = page.locator('.interactive-latex-visual-editor');
  await expect(visual).toBeVisible();
  await visual.locator('.interactive-visual-inline-bold').fill('verified');

  await page.getByRole('button', { name: 'Edit equation' }).first().click();
  const mathEditor = page.getByTestId('mathlive-equation-editor');
  await mathEditor.locator('math-field').evaluate((element) => {
    const field = element as HTMLElement & { value: string };
    field.value = 'E^2=p^2c^2+m^2c^4';
    field.dispatchEvent(new Event('input', { bubbles: true }));
  });
  await mathEditor.getByRole('button', { name: 'Apply' }).click();

  await page.getByRole('button', { name: 'Source' }).click();
  await expect(source).toHaveAttribute('data-r14-10-source-instance', 'persistent');
  await expect(source).toContainText('\\textbf{verified}');
  await expect(source).toContainText('$E^2=p^2c^2+m^2c^4$');

  for (let index = 0; index < 5; index += 1) {
    await page.getByRole('button', { name: 'Visual' }).click();
    await expect(visual).toBeVisible();
    await page.getByRole('button', { name: 'Source' }).click();
    await expect(source).toHaveAttribute('data-r14-10-source-instance', 'persistent');
  }

  await compileCurrentProject(page);

  // PDF text selection and search are real PDF.js text-layer interactions.
  const pdfPreview = page.getByTestId('interactive-pdf-preview');
  const textLayer = pdfPreview.getByTestId('pdf-text-layer');
  await expect(textLayer).toBeVisible();
  await page.getByRole('button', { name: 'Search PDF' }).click();
  await page.getByLabel('Search compiled PDF').fill('R14.10 searchable PDF marker');
  await expect(page.getByTestId('pdf-search-toolbar')).toContainText(/1 \/ 1/);
  await expect(textLayer.locator('.interactive-pdf-search-active')).toBeVisible();

  const selectionSpan = textLayer.locator('span').filter({ hasText: 'R14.10' }).first();
  await expect(selectionSpan).toBeVisible();
  const selectionBox = await selectionSpan.boundingBox();
  if (!selectionBox || selectionBox.width < 4 || selectionBox.height < 2) {
    throw new Error('Expected the PDF text span to have real overlay geometry for mouse selection');
  }
  const selectionY = selectionBox.y + selectionBox.height / 2;
  await page.mouse.move(selectionBox.x + 1, selectionY);
  await page.mouse.down();
  await page.mouse.move(selectionBox.x + Math.max(3, selectionBox.width - 1), selectionY, { steps: 8 });
  await page.mouse.up();
  await expect(pdfPreview.getByText(/chars selected/)).toBeVisible();

  // Source → PDF SyncTeX, then the R14.4 non-destructive reverse gesture.
  await page.getByRole('button', { name: 'Source' }).click();
  await source.press(process.platform === 'darwin' ? 'Meta+Home' : 'Control+Home');
  for (let index = 0; index < 5; index += 1) await source.press('ArrowDown');
  await expect(page.getByTestId('latex-cursor-location')).toContainText('Ln 6');
  await page.getByLabel('Locate source in PDF').click();
  const highlight = page.getByTestId('synctex-source-highlight').first();
  await expect(highlight).toBeVisible();

  const sourceFollowsPdf = page.getByLabel('Source follows PDF');
  if (!(await sourceFollowsPdf.isChecked())) await sourceFollowsPdf.check();
  await source.press(process.platform === 'darwin' ? 'Meta+Home' : 'Control+Home');
  await expect(page.getByTestId('latex-cursor-location')).toContainText('Ln 1');
  const layerBox = await textLayer.boundingBox();
  const highlightBox = await highlight.boundingBox();
  if (!layerBox || !highlightBox) throw new Error('Missing SyncTeX integration bounds');
  await textLayer.dblclick({
    modifiers: ['Alt'],
    position: {
      x: highlightBox.x - layerBox.x + highlightBox.width / 2,
      y: highlightBox.y - layerBox.y + highlightBox.height / 2,
    },
  });
  await expect(page.getByTestId('latex-cursor-location')).toContainText('Ln 6');

  // Reload must recover the canonical project, then compile again from a fresh
  // in-memory PDF/compiler UI session.
  await page.reload({ waitUntil: 'domcontentloaded' });
  const reloadedSource = page.getByLabel('LaTeX source editor');
  await expect(reloadedSource).toBeVisible();
  await expect(reloadedSource).toContainText('\\textbf{verified}');
  await expect(reloadedSource).toContainText('$E^2=p^2c^2+m^2c^4$');
  await page.getByRole('button', { name: 'Visual' }).click();
  await expect(page.locator('.interactive-latex-visual-editor')).toBeVisible();
  await page.getByRole('button', { name: 'Source' }).click();
  await compileCurrentProject(page);

  // Whole-project portability must work with a freshly bootstrapped target;
  // R14.10 treats untouched main.tex as scaffolding, never as user content.
  await page.getByRole('link', { name: 'Home', exact: true }).click();
  const downloadPromise = page.waitForEvent('download');
  await page.getByRole('button', { name: 'Project menu' }).click();
  await page.getByRole('menuitem', { name: 'Download project' }).click();
  const download = await downloadPromise;
  const archivePath = await download.path();
  if (!archivePath) throw new Error('Project ZIP export did not produce a file');

  const importedWorkspace = `integrated-import-r14-10-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName: importedWorkspace });
  await page.getByRole('link', { name: 'Home', exact: true }).click();
  await page.getByLabel('Import Project ZIP').setInputFiles(archivePath);
  await expect(page.getByText('Project imported', { exact: true })).toBeVisible();
  await page.getByRole('link', { name: /^main\.tex$/ }).click();
  const importedSource = page.getByLabel('LaTeX source editor');
  await expect(importedSource).toContainText('\\textbf{verified}');
  await expect(importedSource).toContainText('$E^2=p^2c^2+m^2c^4$');
  await compileCurrentProject(page);

  expect(fatalRuntimeFailures, fatalRuntimeFailures.join('\n')).toEqual([]);
});
