import { describe, expect, it } from 'vitest';
import {
  canEditLatexMathWithMathLive,
  createLatexMathContentEdit,
  createLatexMathInsertion,
  createLatexMathReplacementEdit,
  findLatexMathAtPosition,
  findLatexMathRegions,
  latexMathRegionToMathLiveValue,
  mathLiveValueToLatexMathContent,
} from './math';
import { applySourceTextEdit, computeMinimalSourceTextEdit } from './text-edit';

describe('latex math regions', () => {
  it('detects inline/display/environment math while ignoring comments and verbatim', () => {
    const source = String.raw`Text $a+b$ and \(c+d\).
% $ignored$
\[
  x^2
\]
\begin{equation}
 E = mc^2
\end{equation}
\begin{verbatim}
$not-math$
\end{verbatim}`;
    const regions = findLatexMathRegions(source);
    expect(regions.map((region) => [region.kind, region.content])).toEqual([
      ['inline', 'a+b'],
      ['inline', 'c+d'],
      ['display', 'x^2'],
      ['display', 'E = mc^2'],
    ]);
  });

  it('preserves delimiters and surrounding whitespace when editing content', () => {
    const source = '\\[\n   E = mc^2   \n\\]';
    const region = findLatexMathAtPosition(source, source.indexOf('mc'));
    expect(region).toBeDefined();
    const next = applySourceTextEdit(
      source,
      createLatexMathContentEdit(region!, 'E = \\gamma mc^2'),
    );
    expect(next).toBe('\\[\n   E = \\gamma mc^2   \n\\]');
  });

  it('inserts inline and display math without inventing a second source format', () => {
    const inline = createLatexMathInsertion({
      cursor: 4,
      sourceLength: 8,
      content: 'x^2',
      kind: 'inline',
      replaceTo: 6,
    });
    expect(inline.edit).toEqual({ from: 4, to: 6, text: '\\(x^2\\)' });

    const display = createLatexMathInsertion({
      cursor: 4,
      sourceLength: 8,
      content: 'E=mc^2',
      kind: 'display',
    });
    expect(display.edit.text).toContain('\\[\nE=mc^2\n\\]');
  });

  it('edits one align row and preserves the surrounding environment', () => {
    const source = String.raw`\begin{align}
a &= b \\
c &= d
\end{align}`;
    const region = findLatexMathAtPosition(source, source.indexOf('c &='));
    expect(region?.content).toBe('c &= d');
    expect(region?.rowInEnvironment).toBe(true);
    expect(latexMathRegionToMathLiveValue(region!)).toBe(
      String.raw`\begin{aligned}c &= d\end{aligned}`,
    );
    const mathLiveValue = String.raw`\begin{aligned}c &= e\end{aligned}`;
    const content = mathLiveValueToLatexMathContent(region!, mathLiveValue);
    const next = applySourceTextEdit(
      source,
      createLatexMathContentEdit(region!, content),
    );
    expect(next).toContain(String.raw`a &= b \\`);
    expect(next).toContain('c &= e');
  });

  it('rejects structural source directives from MathLive editing', () => {
    const source = String.raw`\begin{equation}
E=mc^2 \label{eq:energy}
\end{equation}`;
    const region = findLatexMathRegions(source)[0];
    expect(region).toBeDefined();
    expect(canEditLatexMathWithMathLive(region!)).toBe(false);
  });

  it('R14.18 detects Learnitive LaTeX delimiters and matrix-family environments', () => {
    const source = String.raw`$a$ \(b\) \[c\]
\begin{equation*}d\end{equation*}
\begin{align*}e &= f \\ g &= h\end{align*}
\begin{gather}i \\ j\end{gather}
\begin{cases}x & x>0 \\ -x & x<0\end{cases}
\begin{matrix}1 & 2 \\ 3 & 4\end{matrix}
\begin{pmatrix}1 & 0 \\ 0 & 1\end{pmatrix}
\begin{bmatrix}1 & 2\end{bmatrix}
\begin{Bmatrix}1 & 2\end{Bmatrix}
\begin{vmatrix}1 & 2\end{vmatrix}
\begin{Vmatrix}1 & 2\end{Vmatrix}`;
    const regions = findLatexMathRegions(source);
    expect(regions.map((region) => region.environment ?? region.delimiter)).toEqual([
      '$', '\\(', '\\[', 'equation*', 'align*', 'gather', 'cases',
      'matrix', 'pmatrix', 'bmatrix', 'Bmatrix', 'vmatrix', 'Vmatrix',
    ]);
  });

  it('R14.18 applies one exact align-row replacement while retaining delimiters and sibling row bytes', () => {
    const source = String.raw`\begin{align}
  a &= b \\
  c &= d
\end{align}`;
    const region = findLatexMathAtPosition(source, source.indexOf('c &='));
    expect(region?.rowInEnvironment).toBe(true);
    const edit = createLatexMathReplacementEdit(
      region!,
      String.raw`\begin{aligned}c \&= e\end{aligned}`,
    );
    expect(edit.from).toBe(region!.replaceFrom);
    expect(edit.to).toBe(region!.replaceTo);
    const next = applySourceTextEdit(source, edit);
    expect(next).toContain(String.raw`a &= b \\`);
    expect(next).toContain('  c &= e');
    expect(next.startsWith('\\begin{align}\n')).toBe(true);
    expect(next.endsWith('\n\\end{align}')).toBe(true);
  });

  it('R14.18 preserves existing environment whitespace when editing the whole equation', () => {
    const source = String.raw`\begin{equation}
   E=mc^2   
\end{equation}`;
    const region = findLatexMathRegions(source)[0]!;
    const next = applySourceTextEdit(
      source,
      createLatexMathReplacementEdit(region, String.raw`E=\gamma mc^2`),
    );
    expect(next).toBe(String.raw`\begin{equation}
   E=\gamma mc^2   
\end{equation}`);
  });

  it('R14.18 computes the smallest canonical splice instead of replacing the whole document', () => {
    const current = 'prefix MAIN-CONTENT suffix';
    const next = 'prefix VISUAL-CONTENT suffix';
    expect(computeMinimalSourceTextEdit(current, next)).toEqual({
      from: 'prefix '.length,
      to: 'prefix MAIN'.length,
      text: 'VISUAL',
    });
  });

  it('creates a real equation environment for the shared Equation command', () => {
    const insertion = createLatexMathInsertion({
      cursor: 0,
      sourceLength: 0,
      content: 'a=b',
      style: 'equation',
    });
    expect(insertion.edit.text).toBe('\\begin{equation}\na=b\n\\end{equation}\n');
    expect(insertion.edit.text.slice(insertion.contentFrom, insertion.contentTo)).toBe('a=b');
  });

});
