import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { expect, test } from '@playwright/test';
import { createBrowserWorkspace } from './common';

const fixture = path.resolve(
  path.dirname(fileURLToPath(import.meta.url)),
  '../fixtures/interactive-source-project.zip',
);

test('R6 visual mode edits safe ranges while preserving raw LaTeX and exposes the semantic outline', async ({ page }) => {
  const workspaceName = `latex-visual-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  await page.getByLabel('Import Project ZIP').setInputFiles(fixture);
  await page.getByRole('link', { name: /^main\.tex$/ }).click();

  const source = page.getByLabel('LaTeX source editor');
  await source.fill([
    '\\documentclass{article}',
    '\\begin{document}',
    '\\section{Original heading}',
    '',
    'Plain editable paragraph.',
    '',
    '\\begin{customenv}',
    'KEEP-RAW-CONTENT',
    '\\end{customenv}',
    '',
    '\\end{document}',
    '',
  ].join('\n'));
  await source.press(process.platform === 'darwin' ? 'Meta+s' : 'Control+s');

  await page.getByRole('button', { name: 'Outline' }).click();
  const outline = page.getByTestId('interactive-project-outline');
  await expect(outline.getByRole('button', { name: /Original heading/ })).toBeVisible();

  await page.getByRole('button', { name: 'Visual' }).click();
  const visual = page.locator('.interactive-latex-visual-editor');
  await expect(visual).toBeVisible();
  await expect(visual.locator('.interactive-visual-raw')).toContainText('KEEP-RAW-CONTENT');

  const heading = visual.locator('.interactive-visual-heading').first();
  await heading.fill('Visual heading');
  await expect(heading).toHaveText('Visual heading');

  await page.getByRole('button', { name: 'Source' }).click();
  await expect(source).toContainText('\\section{Visual heading}');
  await expect(source).toContainText('\\begin{customenv}\nKEEP-RAW-CONTENT\n\\end{customenv}');
  await expect(source).toContainText('Plain editable paragraph.');
});

test('Visual fidelity firewall hides comments and protects preamble, unsupported LaTeX and standalone graphics', async ({ page }) => {
  const workspaceName = `latex-visual-firewall-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  await page.getByLabel('Import Project ZIP').setInputFiles(fixture);
  await page.getByRole('link', { name: /^main\.tex$/ }).click();

  const source = page.getByLabel('LaTeX source editor');
  const latex = [
    '\\documentclass{article}',
    '\\usepackage{graphicx}',
    '\\title{Protected setup}',
    '\\begin{document}',
    '% -------------------------------------------------------------------------',
    '\\section{First title}',
    '',
    'Editable text.',
    '',
    '{\\includegraphics[width=4cm]{missing-preview.pdf}}',
    '',
    '\\begin{customenv}',
    'CUSTOM-BODY',
    '\\end{customenv}',
    '',
    '\\bibliographystyle{plain}',
    '\\bibliography{refs}',
    '\\end{document}',
    '',
  ].join('\n');
  await source.fill(latex);
  await source.press(process.platform === 'darwin' ? 'Meta+s' : 'Control+s');
  await page.getByRole('button', { name: 'Visual' }).click();

  const visual = page.locator('.interactive-latex-visual-editor');
  await expect(visual).toBeVisible();
  await expect(visual).not.toContainText('-------------------------------------------------------------------------');
  await expect(visual.getByText('First title')).toBeVisible();
  await expect(visual.getByText('Document setup · Protected LaTeX')).toBeVisible();
  await expect(visual.getByText('Figure · Protected LaTeX')).toBeVisible();
  await expect(visual).toContainText('missing-preview.pdf');
  await expect(visual).toContainText('CUSTOM-BODY');
  await expect(visual.getByText('Bibliography · Protected LaTeX')).toBeVisible();

  await visual.getByText('CUSTOM-BODY').click();
  await expect(source).toBeVisible();
  await expect(source).toContainText('\\begin{customenv}\nCUSTOM-BODY\n\\end{customenv}');
});


test('Visual round-trip keeps ordinary prose editable and protected figures/preamble lossless', async ({ page }) => {
  const workspaceName = `latex-visual-lossless-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  await page.getByLabel('Import Project ZIP').setInputFiles(fixture);
  await page.getByRole('link', { name: /^main\.tex$/ }).click();

  const source = page.getByLabel('LaTeX source editor');
  const complexFigure = [
    '\\begin{figure}[htb]',
    '\\begin{minipage}[b]{1.0\\linewidth}',
    '  \\centering',
    '  \\centerline{\\includegraphics[width=8.5cm]{image1}}',
    '  \\centerline{(a) Result 1}\\medskip',
    '\\end{minipage}',
    '\\begin{minipage}[b]{.48\\linewidth}',
    '  \\centering',
    '  \\centerline{\\includegraphics[width=4.0cm]{image3}}',
    '  \\centerline{(b) Results 3}\\medskip',
    '\\end{minipage}',
    '\\hfill',
    '\\begin{minipage}[b]{0.48\\linewidth}',
    '  \\centering',
    '  \\centerline{\\includegraphics[width=4.0cm]{image4}}',
    '  \\centerline{(c) Result 4}\\medskip',
    '\\end{minipage}',
    '\\caption{Example of placing a figure with experimental results.}',
    '\\label{fig:res}',
    '\\end{figure}',
  ].join('\n');
  const latex = [
    '% template comment must remain available inside protected setup',
    '\\documentclass{article}',
    '\\usepackage{graphicx}',
    '\\def\\x{{\\mathbf x}}',
    '\\title{Complex title}',
    '\\begin{document}',
    '\\maketitle',
    '',
    'First editable paragraph.',
    '',
    'Second editable paragraph.',
    '',
    '\\[',
    'E = mc^2',
    '\\]',
    '',
    '\\begin{itemize}',
    '\\item First list item',
    '\\item Second list item',
    '\\end{itemize}',
    '',
    '\\begin{tabular}{ll}',
    'A & B \\\\',
    'C & D',
    '\\end{tabular}',
    '',
    complexFigure,
    '',
    '\\bibliographystyle{plain}',
    '\\bibliography{refs}',
    '\\end{document}',
    '',
  ].join('\n');
  await source.fill(latex);
  await source.press(process.platform === 'darwin' ? 'Meta+s' : 'Control+s');
  await page.getByRole('button', { name: 'Visual' }).click();

  const visual = page.locator('.interactive-latex-visual-editor');
  await expect(visual.getByText('First editable paragraph.')).toBeVisible();
  await expect(visual.getByText('Second editable paragraph.')).toBeVisible();
  const setup = visual.locator('[data-visual-kind="preamble"]');
  await expect(setup).toContainText('% template comment must remain available inside protected setup');
  await expect(setup).toContainText('\\def\\x{{\\mathbf x}}');

  const figure = visual.locator('[data-visual-kind="figure"]').filter({ hasText: 'Complex figure · Protected LaTeX' }).first();
  await expect(figure).toContainText('Complete LaTeX');
  await expect(figure).toContainText('\\begin{minipage}[b]{1.0\\linewidth}');
  await expect(figure).toContainText('\\includegraphics[width=4.0cm]{image3}');
  await expect(figure).toContainText('\\caption{Example of placing a figure with experimental results.}');
  await expect(visual.locator('.interactive-visual-math-display')).toBeVisible();
  await expect(visual.locator('.interactive-visual-list-item')).toHaveCount(2);
  await expect(visual.locator('.interactive-visual-table')).toBeVisible();
  await expect(visual.getByText('Bibliography · Protected LaTeX')).toBeVisible();
  await expect(visual.getByText('Document end · Protected LaTeX')).toBeVisible();

  // Repeated mode switches must rebuild source-range ownership from the current
  // canonical source. This is the regression for equations/figures becoming
  // Protected LaTeX until a hard browser refresh.
  for (let cycle = 0; cycle < 3; cycle += 1) {
    await page.getByRole('button', { name: 'Source' }).click();
    await page.getByRole('button', { name: 'Visual' }).click();
    await expect(visual.getByText('First editable paragraph.')).toBeVisible();
    await expect(visual.getByText('Second editable paragraph.')).toBeVisible();
    await expect(visual.locator('.interactive-visual-math-display')).toBeVisible();
    await expect(visual.locator('.interactive-visual-list-item')).toHaveCount(2);
    await expect(visual.locator('.interactive-visual-table')).toBeVisible();
    await expect(visual.locator('[data-visual-kind="raw"]')).not.toContainText('First editable paragraph.');
    await expect(visual.locator('[data-visual-kind="raw"]')).not.toContainText('E = mc^2');
    await expect(visual.locator('[data-visual-kind="figure"]')).toContainText('image3');
    await expect(visual.getByText('Bibliography · Protected LaTeX')).toBeVisible();
  }
});

test('long scrolling Visual documents never flex-shrink protected LaTeX boxes into empty strips', async ({ page }) => {
  const workspaceName = `latex-visual-long-protected-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  await page.getByLabel('Import Project ZIP').setInputFiles(fixture);
  await page.getByRole('link', { name: /^main\.tex$/ }).click();

  await page.setViewportSize({ width: 1440, height: 820 });
  const source = page.getByLabel('LaTeX source editor');
  const filler = Array.from(
    { length: 70 },
    (_, index) => `Long editable paragraph ${index + 1}. This content forces the Visual manuscript to scroll without changing protected-node semantics.`,
  ).join('\n\n');
  const latex = [
    '% setup comment with \\vspace must stay inside source trivia/setup',
    '\\documentclass{article}',
    '\\usepackage{graphicx}',
    '\\title{Long document}',
    '\\begin{document}',
    '\\maketitle',
    filler,
    '% Below is an example using \\vspace and \\centerline{...}.',
    '\\begin{figure}[htb]',
    '\\begin{minipage}[b]{1.0\\linewidth}',
    '\\centerline{\\includegraphics[width=8.5cm]{image1}}',
    '\\end{minipage}',
    '\\begin{minipage}[b]{.48\\linewidth}',
    '\\centerline{\\includegraphics[width=4.0cm]{image3}}',
    '\\end{minipage}',
    '\\hfill',
    '\\begin{minipage}[b]{.48\\linewidth}',
    '\\centerline{\\includegraphics[width=4.0cm]{image4}}',
    '\\end{minipage}',
    '\\caption{Long-document figure}',
    '\\end{figure}',
    '% \\vfill\\pagebreak remains invisible source trivia',
    '\\section{References}',
    'Reference prose remains editable.',
    '\\bibliographystyle{plain}',
    '\\bibliography{refs}',
    '\\end{document}',
    '',
  ].join('\n');
  await source.fill(latex);
  await source.press(process.platform === 'darwin' ? 'Meta+s' : 'Control+s');
  await page.getByRole('button', { name: 'Visual' }).click();

  const visual = page.locator('.interactive-latex-visual-editor');
  const scrollHost = visual.locator('xpath=..');
  const protectedNodes = [
    visual.locator('[data-visual-kind="preamble"]'),
    visual.locator('[data-visual-kind="figure"]'),
    visual.locator('[data-visual-kind="bibliography"]'),
    visual.locator('[data-visual-kind="document-tail"]'),
  ];

  expect(await scrollHost.evaluate((node) => node.scrollHeight > node.clientHeight)).toBe(true);
  await expect(visual).not.toContainText('Below is an example using');
  await expect(visual).not.toContainText('vfill\\pagebreak remains invisible');

  for (const node of protectedNodes) {
    await expect(node).toHaveCount(1);
    await node.scrollIntoViewIfNeeded();
    await expect(node).toBeVisible();
    const metrics = await node.evaluate((element) => {
      const style = getComputedStyle(element);
      const source = element.querySelector<HTMLElement>('.interactive-visual-protected-source');
      const sourceStyle = source ? getComputedStyle(source) : undefined;
      return {
        height: element.getBoundingClientRect().height,
        flexShrink: style.flexShrink,
        visibility: style.visibility,
        contentVisibility: style.contentVisibility,
        sourceHeight: source?.getBoundingClientRect().height ?? 0,
        sourceDisplay: sourceStyle?.display ?? '',
        sourceTextLength: source?.textContent?.length ?? 0,
      };
    });
    expect(metrics.flexShrink).toBe('0');
    expect(metrics.visibility).toBe('visible');
    expect(metrics.contentVisibility).not.toBe('hidden');
    expect(metrics.height).toBeGreaterThan(40);
    expect(metrics.sourceDisplay).not.toBe('none');
    expect(metrics.sourceHeight).toBeGreaterThan(20);
    expect(metrics.sourceTextLength).toBeGreaterThan(0);
  }

  // A resize/zoom-equivalent viewport change must not decide whether protected
  // content exists. Width changes are enough to exercise responsive CSS in E2E
  // without relying on browser-specific page zoom APIs.
  await page.setViewportSize({ width: 1000, height: 820 });
  for (const node of protectedNodes) {
    await node.scrollIntoViewIfNeeded();
    await expect(node).toBeVisible();
    expect(await node.evaluate((element) => element.getBoundingClientRect().height)).toBeGreaterThan(40);
    expect(await node.locator('.interactive-visual-protected-source').textContent()).not.toBe('');
  }
});

