// @vitest-environment happy-dom
import { describe, expect, it, vi } from 'vitest';
import {
  applyCanonicalLatexCodeMirrorText,
  computeMinimalSourceEdit,
  createLatexCodeMirror,
  getLatexCodeMirrorSoftWrap,
  revealLatexCodeMirrorLocation,
  selectLatexCodeMirrorLocation,
} from '../editor';
import { LATEX_EDITOR_COMMANDS } from '../toolbar';

describe('R14.16 Learnitive CodeMirror session', () => {
  it('reduces canonical replacements to the smallest single source splice', () => {
    expect(computeMinimalSourceEdit('alpha beta gamma', 'alpha BETA gamma')).toEqual({
      from: 6,
      to: 10,
      text: 'BETA',
    });
    expect(computeMinimalSourceEdit('abc', 'abXYZc')).toEqual({
      from: 2,
      to: 2,
      text: 'XYZ',
    });
    expect(computeMinimalSourceEdit('abcXYZ', 'abc')).toEqual({
      from: 3,
      to: 6,
      text: '',
    });
    expect(computeMinimalSourceEdit('same', 'same')).toBeUndefined();
  });

  it('keeps one EditorView while soft wrap is reconfigured and does not echo canonical edits', () => {
    const parent = document.createElement('div');
    document.body.append(parent);
    const onChanges = vi.fn();
    const session = createLatexCodeMirror({
      doc: 'alpha beta gamma',
      parent,
      ariaLabel: 'test latex editor',
      softWrap: true,
      onChanges,
      onSave: vi.fn(),
      onUndo: vi.fn(),
      onRedo: vi.fn(),
    });

    try {
      const originalView = session.view;
      session.view.dispatch({ selection: { anchor: 12 } });
      const originalSelection = session.view.state.selection.main.head;

      session.setSoftWrap(false);
      expect(session.view).toBe(originalView);
      expect(getLatexCodeMirrorSoftWrap(session.view)).toBe(false);
      expect(session.view.state.selection.main.head).toBe(originalSelection);

      session.setSoftWrap(true);
      expect(session.view).toBe(originalView);
      expect(getLatexCodeMirrorSoftWrap(session.view)).toBe(true);
      expect(session.view.state.selection.main.head).toBe(originalSelection);

      revealLatexCodeMirrorLocation(session.view, 1, 2);
      expect(session.view.state.selection.main.head).toBe(originalSelection);
      selectLatexCodeMirrorLocation(session.view, 1, 2, { focus: false });
      expect(session.view.state.selection.main.head).toBe(1);
      session.view.dispatch({ selection: { anchor: originalSelection } });

      expect(applyCanonicalLatexCodeMirrorText(session.view, 'alpha BETA gamma')).toEqual({
        from: 6,
        to: 10,
        text: 'BETA',
      });
      expect(session.view.state.doc.toString()).toBe('alpha BETA gamma');
      expect(onChanges).not.toHaveBeenCalled();

      session.view.dispatch({ changes: { from: 5, to: 5, insert: '!' } });
      expect(onChanges).toHaveBeenLastCalledWith([{ from: 5, to: 5, text: '!' }]);

      session.view.dispatch({ selection: { anchor: 7, head: 11 } });
      expect(session.toolbar.run('bold')).toBe(true);
      expect(session.view.state.doc.toString()).toContain('\\textbf{BETA}');
      const wrappedSelection = session.view.state.selection.main;
      expect(
        session.view.state.sliceDoc(wrappedSelection.from, wrappedSelection.to),
      ).toBe('BETA');
    } finally {
      session.destroy();
      parent.remove();
    }
  });

  it('exposes the R14.16 semantic toolbar command set without duplicate command IDs', () => {
    const ids = LATEX_EDITOR_COMMANDS.map((command) => command.id);
    expect(new Set(ids).size).toBe(ids.length);
    expect(ids).toEqual(expect.arrayContaining([
      'bold',
      'italic',
      'underline',
      'emphasis',
      'typewriter',
      'section',
      'subsection',
      'subsubsection',
      'itemize',
      'enumerate',
      'inline-math',
      'display-math',
      'equation',
      'figure',
      'table',
      'verbatim',
      'listing',
      'superscript',
      'subscript',
      'strikethrough',
      'hyperlink',
      'citation',
      'reference',
      'label',
      'footnote',
      'text-colour',
      'highlight',
    ]));
    for (const command of LATEX_EDITOR_COMMANDS) {
      expect(command.toolbar).toBe(true);
      expect(command.slash.length).toBeGreaterThan(0);
    }
  });
});

describe('R14.17 per-file CodeMirror state', () => {
  it('serializes and restores selection, scroll and native undo history', () => {
    const firstParent = document.createElement('div');
    firstParent.style.height = '240px';
    document.body.append(firstParent);
    const original = `${Array.from({ length: 120 }, (_, index) => `line ${index}`).join('\n')}\nBASE`;
    const first = createLatexCodeMirror({
      doc: original,
      parent: firstParent,
      ariaLabel: 'first session',
      onChanges: vi.fn(),
      onSave: vi.fn(),
      onUndo: vi.fn(),
      onRedo: vi.fn(),
    });

    const insertAt = first.view.state.doc.length;
    first.view.dispatch({
      changes: { from: insertAt, insert: '\nEDIT' },
      selection: { anchor: insertAt + 5 },
    });
    first.view.scrollDOM.scrollTop = 900;
    first.view.scrollDOM.scrollLeft = 31;
    const persisted = first.captureState();
    const edited = first.view.state.doc.toString();
    first.destroy();
    firstParent.remove();

    const secondParent = document.createElement('div');
    secondParent.style.height = '240px';
    document.body.append(secondParent);
    const second = createLatexCodeMirror({
      doc: edited,
      parent: secondParent,
      ariaLabel: 'second session',
      restoredState: persisted,
      onChanges: vi.fn(),
      onSave: vi.fn(),
      onUndo: vi.fn(),
      onRedo: vi.fn(),
    });

    try {
      expect(second.view.state.selection.main.head).toBe(insertAt + 5);
      expect(second.view.scrollDOM.scrollLeft).toBe(31);
      expect(second.canUndo()).toBe(true);
      expect(second.undo()).toBe(true);
      expect(second.view.state.doc.toString()).toBe(original);
    } finally {
      second.destroy();
      secondParent.remove();
    }
  });

  it('keeps canonical background updates out of native undo history', () => {
    const parent = document.createElement('div');
    document.body.append(parent);
    const session = createLatexCodeMirror({
      doc: 'alpha',
      parent,
      ariaLabel: 'canonical history test',
      onChanges: vi.fn(),
      onSave: vi.fn(),
      onUndo: vi.fn(),
      onRedo: vi.fn(),
    });

    try {
      applyCanonicalLatexCodeMirrorText(session.view, 'alpha beta');
      expect(session.canUndo()).toBe(false);
      session.view.dispatch({ changes: { from: 10, insert: '!' } });
      expect(session.canUndo()).toBe(true);
      expect(session.undo()).toBe(true);
      expect(session.view.state.doc.toString()).toBe('alpha beta');
    } finally {
      session.destroy();
      parent.remove();
    }
  });
});
