import { describe, expect, it } from 'vitest';
import { applySourceTextEdits } from './text-edit';
import {
  createLatexTableStructureMutation,
  createLatexTableTemplate,
  parseLatexTabular,
} from './table';

describe('R2 canonical table insertion', () => {
  it('creates the requested dimensions and a first-cell focus offset', () => {
    const template = createLatexTableTemplate(3, 4);
    expect(template.text).toContain('\\begin{center}\n\\begin{tabular}{|l|l|l|l|}');
    expect(template.text).toContain('\\end{tabular}\n\\end{center}');
    expect(template.text).not.toContain('\\centering');
    expect(template.text.match(/\\hline/g)).toHaveLength(4);
    expect(template.firstCellOffset).toBeGreaterThan(template.text.indexOf('\\begin{tabular}'));
    const model = parseLatexTabular(template.text);
    expect(model?.rows).toHaveLength(3);
    expect(model?.columns).toHaveLength(4);
    expect(model?.rows[0]?.cells[0]?.from).toBe(template.firstCellOffset);
    expect(model?.rows[0]?.cells[0]?.to).toBe(template.firstCellOffset);
  });
});

describe('R2 source-preserving structural table edits', () => {
  const source = String.raw`\begin{table}[htbp]
\centering
\begin{tabular}{|l|c|}
\toprule
A & B \\
\midrule
C & D \\
\bottomrule
\end{tabular}
\caption{Data}
\label{tab:data}
\end{table}`;

  it('parses booktabs rules without making the table structurally unsafe', () => {
    const model = parseLatexTabular(source);
    expect(model?.complex).toBe(false);
    expect(model?.rows).toHaveLength(2);
    expect(model?.columns.map((column) => column.text)).toEqual(['l', 'c']);
  });

  it('adds a row below without reconstructing or deleting rules', () => {
    const mutation = createLatexTableStructureMutation(
      source,
      { from: 0, to: source.length },
      'add-row-below',
      0,
      0,
    );
    expect(mutation).toBeDefined();
    expect(mutation?.edits).toHaveLength(1);
    const next = applySourceTextEdits(source, mutation!.edits);
    const nextLines = next.split('\n');
    const headerRow = nextLines.indexOf('A & B \\\\');
    expect(headerRow).toBeGreaterThanOrEqual(0);
    expect(nextLines[headerRow + 1]?.trim()).toBe('&  \\\\');
    expect(nextLines[headerRow + 2]).toBe('\\midrule');
    expect(next).toContain('\\toprule');
    expect(next).toContain('\\midrule');
    expect(next).toContain('\\bottomrule');
  });

  it('adds and deletes columns with small cell/spec edits while preserving rules', () => {
    const add = createLatexTableStructureMutation(
      source,
      { from: 0, to: source.length },
      'add-column-right',
      0,
      0,
    );
    expect(add).toBeDefined();
    expect(add!.edits.length).toBe(3); // spec + one edit per row
    const withColumn = applySourceTextEdits(source, add!.edits);
    expect(withColumn).toContain('\\begin{tabular}{|l|l|c|}');
    expect(withColumn).toMatch(/A\s*&\s*&\s*B/);
    expect(withColumn).toContain('\\midrule');

    const remove = createLatexTableStructureMutation(
      withColumn,
      { from: 0, to: withColumn.length },
      'delete-column',
      0,
      1,
    );
    expect(remove).toBeDefined();
    const restored = applySourceTextEdits(withColumn, remove!.edits);
    expect(restored).toContain('\\begin{tabular}{|l|c|}');
    expect(restored).toContain('A & B');
    expect(restored).toContain('\\bottomrule');
  });

  it('supports p/m/b/X/@ column specifications for safe structural edits', () => {
    const complexSpec = String.raw`\begin{tabular}{|p{2cm}|@{}m{3cm}|X|}
A & B & C \\
\end{tabular}`;
    const model = parseLatexTabular(complexSpec);
    expect(model?.columns.map((column) => column.text)).toEqual(['p{2cm}', 'm{3cm}', 'X']);
    const mutation = createLatexTableStructureMutation(
      complexSpec,
      { from: 0, to: complexSpec.length },
      'add-column-left',
      0,
      1,
    );
    expect(mutation).toBeDefined();
    const next = applySourceTextEdits(complexSpec, mutation!.edits);
    expect(next).toContain('|p{2cm}|@{}m{3cm}|@{}m{3cm}|X|');
  });

  it('fails closed for multirow/multicolumn structures', () => {
    const multi = String.raw`\begin{tabular}{cc}
\multicolumn{2}{c}{Header} \\
A & B \\
\end{tabular}`;
    expect(parseLatexTabular(multi)?.complex).toBe(true);
    expect(createLatexTableStructureMutation(
      multi,
      { from: 0, to: multi.length },
      'add-row-below',
      0,
      0,
    )).toBeUndefined();
  });
});


describe('R31 BF3 boxed table structure preservation', () => {
  it('creates visible full-grid rules and keeps the first empty cell source-backed', () => {
    const template = createLatexTableTemplate(2, 2);
    expect(template.text).toContain('\\begin{tabular}{|l|l|}');
    expect(template.text.match(/\\hline/g)).toHaveLength(3);
    const model = parseLatexTabular(template.text);
    expect(model?.rows).toHaveLength(2);
    expect(model?.rows[0]?.cells[0]?.from).toBe(template.firstCellOffset);
  });

  it('adds and deletes boxed rows while preserving one hline at every boundary', () => {
    const template = createLatexTableTemplate(2, 2, {
      cells: [['A', 'B'], ['C', 'D']],
      includeLabel: false,
    });
    const add = createLatexTableStructureMutation(
      template.text,
      { from: 0, to: template.text.length },
      'add-row-below',
      0,
      0,
    );
    expect(add).toBeDefined();
    const withRow = applySourceTextEdits(template.text, add!.edits);
    expect(parseLatexTabular(withRow)?.rows).toHaveLength(3);
    expect(withRow.match(/\\hline/g)).toHaveLength(4);

    const remove = createLatexTableStructureMutation(
      withRow,
      { from: 0, to: withRow.length },
      'delete-row',
      1,
      0,
    );
    expect(remove).toBeDefined();
    const restored = applySourceTextEdits(withRow, remove!.edits);
    expect(parseLatexTabular(restored)?.rows).toHaveLength(2);
    expect(restored.match(/\\hline/g)).toHaveLength(3);
    expect(restored).toContain('A & B');
    expect(restored).toContain('C & D');
  });

  it('deleting a table materializes a source-backed blank paragraph', () => {
    const template = createLatexTableTemplate(1, 1);
    const source = `Before\n${template.text}\nAfter`;
    const from = source.indexOf('\\begin{table}');
    const to = source.indexOf('\\end{table}') + '\\end{table}'.length;
    const mutation = createLatexTableStructureMutation(source, { from, to }, 'delete-table');
    expect(mutation).toBeDefined();
    const next = applySourceTextEdits(source, mutation!.edits);
    expect(next).not.toContain('\\begin{table}');
    expect(next).toContain('Before\n\n\nAfter');
  });
});
