import {
  handleBeginOfFilePatch,
  handleEndOfFilePatch,
  handleStandardPatch,
} from './patcher';
import type { Token } from './types';

// Helper to create mock tokens for testing.
// Simplified: we only care about text, startIndex, and endIndex for these tests.
const makeTokens = (texts: string[]): Token[] => {
  let offset = 0;
  return texts.map((text) => {
    const start = offset;
    const end = start + text.length;
    offset = end + 1; // Simulate space
    return {
      text,
      type: 'test',
      startIndex: start,
      endIndex: end,
      startPosition: { row: 0, column: start },
    };
  });
};

describe('patcher', () => {
  describe('handleStandardPatch', () => {
    it('should find a unique prefix and suffix', () => {
      const sourceTokens = makeTokens(['prefix', '(', 'old', ')', 'suffix']);
      const patchTokens = makeTokens(['prefix', '(', 'new', ')', 'suffix']);
      const result = handleStandardPatch(sourceTokens, patchTokens);
      expect(result).toEqual({
        patchInsertEnd: 21,
        patchInsertStart: 0,
        replaceStart: 0, // 'prefix' startIndex
        replaceEnd: 21, // 'suffix' endIndex
      });
    });

    it('should trim non-matching tokens from both ends to find a match', () => {
      const sourceTokens = makeTokens(['prefix', 'content', 'suffix']);
      // The patch has extra tokens at the start and end that aren't in the source.
      const patchTokens = makeTokens([
        'bad', // 0-3
        'prefix', // 4-10
        'new-content', // 11-22
        'suffix', // 23-29
        'good', // 30-34
      ]);
      const result = handleStandardPatch(sourceTokens, patchTokens);
      // It should fail with 'bad'/'good', then trim them, then succeed on ['prefix', 'new-content', 'suffix']
      expect(result).toEqual({
        replaceStart: 0, // start of 'prefix' in source
        replaceEnd: 21, // end of 'suffix' in source
        patchInsertStart: 4, // start of 'prefix' in patch
        patchInsertEnd: 29, // end of 'suffix' in patch
      });
    });

    it('should throw for persistently ambiguous prefix', () => {
      const sourceTokens = makeTokens(['a', 'b', 'c', 'a', 'b', 'd']);
      const patchTokens = makeTokens(['a', 'b', 'e']); // a b is ambiguous
      expect(() => handleStandardPatch(sourceTokens, patchTokens)).toThrow(
        /Ambiguous prefix anchor/,
      );
    });

    it('should throw for persistently ambiguous suffix', () => {
      const sourceTokens = makeTokens(['prefix', 'x', 'suffix', 'y', 'suffix']);
      const patchTokens = makeTokens(['prefix', 'z', 'suffix']);
      expect(() => handleStandardPatch(sourceTokens, patchTokens)).toThrow(
        /Ambiguous suffix anchor/,
      );
    });

    it('should throw if patch has fewer than two tokens', () => {
      const sourceTokens = makeTokens(['a', 'b', 'c']);
      const patchTokens = makeTokens(['a']);
      expect(() => handleStandardPatch(sourceTokens, patchTokens)).toThrow(
        'Patch must contain at least two tokens to form a prefix and a suffix.',
      );
    });
  });

  describe('handleBeginOfFilePatch', () => {
    it('should find a unique suffix anchor', () => {
      const sourceTokens = makeTokens(['a', 'b', 'c', 'd']);
      const patchTokens = makeTokens(['x', 'y', 'c', 'd']); // Suffix is 'c', 'd'
      const result = handleBeginOfFilePatch(sourceTokens, patchTokens);
      expect(result).toEqual({
        patchInsertEnd: 7,
        patchInsertStart: 0,
        replaceStart: 0,
        replaceEnd: 7, // endIndex of 'd'
      });
    });

    it('should trim from the start to find a unique suffix', () => {
      // The "good" part of the patch, ['a', 'b'], is unique in the source.
      const sourceTokens = makeTokens(['x', 'y', 'a', 'b']);
      // The initial patch will fail because the suffix ['y', 'a', 'b'] is not in the source.
      const patchTokens = makeTokens([
        'bad', // 0-3
        'token', // 4-9
        'a', // 10-11
        'b', // 12-13
      ]);
      const result = handleBeginOfFilePatch(sourceTokens, patchTokens);
      // Should fail, trim 'bad', fail, trim 'token', then succeed with suffix ['a', 'b'].
      expect(result).toEqual({
        replaceStart: 0,
        replaceEnd: 7, // end of 'b' in source
        patchInsertStart: 0, // start of 'a' in patch
        patchInsertEnd: 13, // end of 'b' in patch
      });
    });

    it('should throw for a persistently ambiguous suffix', () => {
      const sourceTokens = makeTokens(['a', 'b', 'a', 'b']);
      const patchTokens = makeTokens(['x', 'y', 'a', 'b']);
      expect(() => handleBeginOfFilePatch(sourceTokens, patchTokens)).toThrow(
        /Ambiguous suffix anchor/,
      );
    });

    it('should throw if anchor not found even after trimming', () => {
      const sourceTokens = makeTokens(['a', 'b', 'c']);
      const patchTokens = makeTokens(['x', 'y', 'z']);
      expect(() => handleBeginOfFilePatch(sourceTokens, patchTokens)).toThrow(
        /Suffix anchor not found/,
      );
    });
  });

  describe('handleEndOfFilePatch', () => {
    const sourceCode = 'a b c d'; // length 7
    const sourceTokens = makeTokens(['a', 'b', 'c', 'd']);

    it('should find a unique prefix anchor', () => {
      const patchTokens = makeTokens(['a', 'b', 'x', 'y']); // Prefix is 'a', 'b'
      const result = handleEndOfFilePatch(
        sourceTokens,
        patchTokens,
        sourceCode,
      );
      expect(result).toEqual({
        patchInsertEnd: 7,
        patchInsertStart: 0,
        replaceStart: 0, // startIndex of 'a'
        replaceEnd: 7, // sourceCode.length
      });
    });

    it('should trim from the end to find a unique prefix', () => {
      // The "good" part of the patch, ['c', 'd'], is unique in the source.
      const sourceTokens = makeTokens(['c', 'd', 'x', 'y']);
      const sourceCode = 'c d x y'; // length 7
      // The initial patch will fail because the prefix ['c', 'd', 'bad'] is not in the source.
      const patchTokens = makeTokens([
        'c', // 0-1
        'd', // 2-3
        'bad', // 4-7
        'token', // 8-13
      ]);
      const result = handleEndOfFilePatch(
        sourceTokens,
        patchTokens,
        sourceCode,
      );
      // Should fail, trim 'token', fail, trim 'bad', then succeed with prefix ['c', 'd'].
      expect(result).toEqual({
        replaceStart: 0, // start of 'c' in source
        replaceEnd: 7, // sourceCode.length
        patchInsertStart: 0, // start of 'c' in patch
        patchInsertEnd: 13, // end of 'd' in patch
      });
    });

    it('should throw for a persistently ambiguous prefix', () => {
      const ambiguousSourceTokens = makeTokens(['a', 'b', 'a', 'b']);
      const patchTokens = makeTokens(['a', 'b', 'x', 'y']);
      expect(() =>
        handleEndOfFilePatch(ambiguousSourceTokens, patchTokens, 'a b a b'),
      ).toThrow(/Ambiguous prefix anchor/);
    });

    it('should throw if anchor not found even after trimming', () => {
      const patchTokens = makeTokens(['x', 'y', 'z']);
      expect(() =>
        handleEndOfFilePatch(sourceTokens, patchTokens, sourceCode),
      ).toThrow(/Prefix anchor not found/);
    });
  });
});
