import type { SyncTexFileMapping } from '@learnitive/latex-synctex';
import { describe, expect, it } from 'vitest';
import {
  decideLiveInstantParagraph,
  liveInstantOriginEligible,
  selectLiveInstantTarget,
} from '../project-live-instant';

const base = String.raw`\documentclass{article}
\begin{document}
First ordinary paragraph for the exact baseline.

Second paragraph remains unchanged.
\end{document}
`;

describe('Live PDF R6 conservative classifier', () => {
  it('accepts one isolated plain-prose paragraph edit', () => {
    const current = base.replace('exact baseline', 'instant preview baseline');
    const decision = decideLiveInstantParagraph(base, current);
    expect(decision).toMatchObject({ kind: 'patch' });
    if (decision.kind !== 'patch') throw new Error('expected patch');
    expect(decision.paragraph.text).toContain('instant preview baseline');
    expect(decision.paragraph.startLine).toBe(3);
  });

  it('fails closed for preamble, structure, math and multiple-paragraph edits', () => {
    expect(decideLiveInstantParagraph(base, base.replace('{article}', '{report}'))).toMatchObject({ kind: 'skip' });
    expect(decideLiveInstantParagraph(base, base.replace('First ordinary', String.raw`\textbf{First} ordinary`))).toMatchObject({ kind: 'skip' });
    expect(decideLiveInstantParagraph(base, base.replace('First ordinary', '$x$ First ordinary'))).toMatchObject({ kind: 'skip' });
    expect(decideLiveInstantParagraph(
      base,
      base.replace('exact baseline', 'changed').replace('remains unchanged', 'also changed'),
    )).toMatchObject({ kind: 'skip' });
  });

  it('limits R6 origins to direct Source and non-structural Visual prose', () => {
    expect(liveInstantOriginEligible('source')).toBe(true);
    expect(liveInstantOriginEligible('visual')).toBe(true);
    expect(liveInstantOriginEligible('visual-structure')).toBe(false);
    expect(liveInstantOriginEligible('mathlive')).toBe(false);
    expect(liveInstantOriginEligible('collaboration')).toBe(false);
    expect(liveInstantOriginEligible('ai')).toBe(false);
  });

  it('anchors only a single exact SyncTeX page and infers the matching column width', () => {
    const mappings: SyncTexFileMapping[] = [
      { file: 'main.tex', line: 3, page: 1, rects: [{ x: 72, y: 100, width: 190, height: 12 }] },
      { file: 'main.tex', line: 4, page: 1, rects: [{ x: 72, y: 114, width: 235, height: 12 }] },
      { file: 'main.tex', line: 8, page: 1, rects: [{ x: 72, y: 250, width: 240, height: 12 }] },
      { file: 'main.tex', line: 8, page: 1, rects: [{ x: 330, y: 250, width: 240, height: 12 }] },
    ];
    expect(selectLiveInstantTarget(mappings, 3, 4)).toMatchObject({
      page: 1,
      x: 72,
      y: 100,
      hsize: 240,
      lineCount: 2,
    });
    const splitPages: SyncTexFileMapping[] = [
      ...mappings,
      { file: 'main.tex', line: 4, page: 2, rects: [{ x: 72, y: 80, width: 220, height: 12 }] },
    ];
    expect(selectLiveInstantTarget(splitPages, 3, 4)).toBeUndefined();
  });
  it('fails closed when otherwise-plain prose is inside a layout-sensitive environment', () => {
    const before = String.raw`\documentclass{article}
\begin{document}
\begin{quote}

Quoted plain prose.

\end{quote}
\end{document}`;
    const after = before.replace('Quoted plain prose.', 'Quoted changed prose.');
    expect(decideLiveInstantParagraph(before, after)).toEqual({
      kind: 'skip',
      reason: 'paragraph is inside a layout-sensitive environment',
    });
  });

});
