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

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

async function createTextFile(
  page: import('@playwright/test').Page,
  fileName: string,
  content: string,
) {
  const explorer = page.getByTestId('interactive-file-explorer');
  await explorer.getByRole('button', { name: 'New File' }).click();
  const dialog = page.getByRole('dialog', { name: 'Create Project File' });
  await dialog.getByLabel('File name').fill(fileName);
  await dialog.getByRole('button', { name: 'Create' }).click();
  const source = page.getByLabel('LaTeX source editor');
  await expect(source).toBeVisible();
  await source.fill(content);
  await source.press(saveShortcut);
  await expect(page.getByText('Saved', { exact: true })).toBeVisible();
}

async function selectCompiler(
  page: import('@playwright/test').Page,
  label: 'pdfLaTeX' | 'XeLaTeX' | 'LuaLaTeX',
) {
  const engine = page.getByRole('combobox', { name: 'Compiler engine' });
  await engine.click();
  const option = page.getByRole('option', { name: new RegExp(`^${label}`) });
  await expect(option).toBeEnabled();
  await option.click();
}

async function compileAndExpectSuccess(page: import('@playwright/test').Page) {
  await page.getByRole('button', { name: 'Compile', exact: true }).click();
  await expect(page.getByTestId('interactive-pdf-preview').locator('canvas')).toBeVisible({
    timeout: 180_000,
  });
}

test('R14.14 switches project files without refresh and keeps route content authoritative', async ({ page }) => {
  const workspaceName = `core-r14-14-files-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });

  const source = page.getByLabel('LaTeX source editor');
  await expect(source).toBeVisible();
  await source.fill('\\documentclass{article}\n\\begin{document}\nMAIN-CONTENT\n\\end{document}\n');
  await source.press(saveShortcut);

  await createTextFile(page, 'chapter.tex', 'CHAPTER-CONTENT\n');
  await createTextFile(page, 'refs.bib', '@article{switchtest, title={BIB-CONTENT}, year={2026}}\n');
  await createTextFile(page, 'custom.sty', '\\ProvidesPackage{custom}\n% STYLE-CONTENT\n');

  const main = page.getByRole('link', { name: /^main\.tex$/ });
  const chapter = page.getByRole('link', { name: /^chapter\.tex$/ });
  const bib = page.getByRole('link', { name: /^refs\.bib$/ });
  const sty = page.getByRole('link', { name: /^custom\.sty$/ });

  for (let index = 0; index < 20; index += 1) {
    await main.click();
    await expect(page.getByLabel('LaTeX source editor')).toContainText('MAIN-CONTENT');
    await chapter.click();
    await expect(page.getByLabel('LaTeX source editor')).toContainText('CHAPTER-CONTENT');
  }

  await bib.click();
  await expect(page.getByLabel('LaTeX source editor')).toContainText('BIB-CONTENT');
  await sty.click();
  await expect(page.getByLabel('LaTeX source editor')).toContainText('STYLE-CONTENT');
  await main.click();
  await expect(page.getByLabel('LaTeX source editor')).toContainText('MAIN-CONTENT');

  // Source-only files must force Source visible even if the previous .tex was
  // left in Visual mode; otherwise the new file appears as a blank editor.
  await page.getByRole('button', { name: 'Visual' }).click();
  await expect(page.locator('.interactive-latex-visual-editor')).toBeVisible();
  await sty.click();
  await expect(page.getByLabel('LaTeX source editor')).toBeVisible();
  await expect(page.getByLabel('LaTeX source editor')).toContainText('STYLE-CONTENT');
});

test('R14.14 Visual prose, native slash and MathLive recovery stay inside the editor surface', async ({ page }) => {
  const workspaceName = `core-r14-14-visual-${Date.now()}`;
  const pageErrors: string[] = [];
  page.on('pageerror', (error) => pageErrors.push(error.message));
  await createBrowserWorkspace(page, { workspaceName });

  const source = page.getByLabel('LaTeX source editor');
  await source.fill([
    '\\documentclass{article}',
    '\\begin{document}',
    'Hello Visual Editor.',
    '',
    '\\end{document}',
    '',
  ].join('\n'));
  await source.press(saveShortcut);

  await page.getByRole('button', { name: 'Visual' }).click();
  const visual = page.locator('.interactive-latex-visual-editor');
  await expect(visual).toBeVisible();
  const paragraph = visual.locator('.interactive-visual-paragraph').filter({ hasText: 'Hello Visual Editor.' });
  await paragraph.fill('Hello Visual Editor. Updated');

  // Typed slash is owned by the Learnitive suggestion plugin; Section is a pure
  // structural command and therefore proves the slash engine independently of MathLive.
  await paragraph.click();
  await paragraph.press('/');
  await page.keyboard.type('sec');
  const slash = page.getByTestId('latex-visual-slash-menu');
  await expect(slash).toBeVisible();
  await expect(slash.getByText('Section', { exact: true })).toBeVisible();
  await page.keyboard.press('Enter');
  await expect(visual.locator('.interactive-visual-heading').filter({ hasText: 'Section title' })).toBeVisible();

  // Equation command must open a local MathLive edit session without ever
  // reaching the app-level ErrorBoundary. A fallback textarea is acceptable
  // if MathLive itself cannot initialize in this browser session.
  const editable = visual.locator('.interactive-visual-paragraph').filter({ hasText: 'Hello Visual Editor. Updated' });
  await editable.click();
  await editable.press('/');
  await page.keyboard.type('equ');
  await expect(slash.getByText('Equation', { exact: true })).toBeVisible();
  await page.keyboard.press('Enter');
  const equation = page.getByTestId('mathlive-equation-editor');
  await expect(equation).toBeVisible();
  await expect(page.getByText(/Mathfield not mounted/i)).toHaveCount(0);
  await equation.getByRole('button', { name: 'Cancel' }).click();

  await page.getByRole('button', { name: 'Source' }).click();
  await expect(source).toContainText('Hello Visual Editor. Updated');
  await expect(source).toContainText('\\section{Section title}');
  expect(pageErrors.filter((message) => /Mathfield not mounted/i.test(message))).toEqual([]);
});

test('R14.14 compiles the real root directly and full profile certifies pdfLaTeX, XeLaTeX and LuaLaTeX', async ({ page }) => {
  const workspaceName = `core-r14-14-compile-${Date.now()}`;
  await createBrowserWorkspace(page, { workspaceName });
  const source = page.getByLabel('LaTeX source editor');
  await source.fill([
    '\\documentclass{article}',
    '\\begin{document}',
    'R14.14 compiler recovery.',
    '\\end{document}',
    '',
  ].join('\n'));
  await source.press(saveShortcut);

  await compileAndExpectSuccess(page);
  const log = page.locator('details').filter({ has: page.locator('summary') }).last();
  if (await log.count()) {
    await log.locator('summary').click();
    await expect(log).not.toContainText('__learnitive_synctex__.tex');
    await expect(log).not.toContainText('\\synctex=1');
  }

  const status = await page.evaluate(async () => {
    const response = await fetch('/compiler/status.json', { cache: 'no-store' });
    return response.json() as Promise<{
      compilerProfile?: string;
      engines: Record<'pdflatex' | 'xelatex' | 'lualatex', { available: boolean }>;
      runtimeAssets?: {
        busytex?: {
          version: string | null;
          fingerprint: string | null;
          requiredFiles: string[];
          engineRequiredFiles: Partial<
            Record<'pdflatex' | 'xelatex' | 'lualatex', string[]>
          >;
        };
      };
    }>;
  });

  if (process.env.LEARNITIVE_RELEASE_QUALITY === '1' || status.compilerProfile === 'full') {
    expect(status.engines.pdflatex.available).toBe(true);
    expect(status.engines.xelatex.available).toBe(true);
    expect(status.engines.lualatex.available).toBe(true);
    expect(status.runtimeAssets?.busytex?.version).toBeTruthy();
    expect(status.runtimeAssets?.busytex?.fingerprint).toMatch(/^[a-f0-9]{64}$/);
    const deployedBusytexAssets = [
      ...(status.runtimeAssets?.busytex?.requiredFiles ?? []),
      ...(status.runtimeAssets?.busytex?.engineRequiredFiles.xelatex ?? []),
      ...(status.runtimeAssets?.busytex?.engineRequiredFiles.lualatex ?? []),
    ];
    expect(deployedBusytexAssets.length).toBeGreaterThan(0);
    for (const asset of new Set(deployedBusytexAssets)) {
      const response = await page.request.head(asset);
      expect(response.ok(), `${asset} must be served by the deployed origin`).toBe(
        true,
      );
    }

    await selectCompiler(page, 'XeLaTeX');
    await compileAndExpectSuccess(page);
    await selectCompiler(page, 'LuaLaTeX');
    await compileAndExpectSuccess(page);
    await selectCompiler(page, 'pdfLaTeX');
  }
});
