import { describe, expect, test } from "vitest"

import { dedent } from "../util"

import { myersCharDiff, myersDiff } from "./myers"

describe("Test myers diff function", () => {
  test("should ...", () => {
    const linesA = dedent`
              A
              B
              C
              D
              E
            `
    const linesB = dedent`
              A
              B
              C'
              D'
              E
            `
    const diffLines = myersDiff(linesA, linesB)
    expect(diffLines).toEqual([
      { type: "same", line: "A" },
      { type: "same", line: "B" },
      { type: "old", line: "C" },
      { type: "old", line: "D" },
      { type: "new", line: "C'" },
      { type: "new", line: "D'" },
      { type: "same", line: "E" },
    ])
  })

  test("should ignore newline differences at end", () => {
    const linesA = "A\nB\nC\n"
    const linesB = "A\nB\nC"

    const diffLines = myersDiff(linesA, linesB)
    expect(diffLines).toEqual([
      { type: "same", line: "A" },
      { type: "same", line: "B" },
      { type: "same", line: "C" },
    ])
  })

  test("should ignore single-line whitespace-only differences", () => {
    const linesA = "A\n    B\nC\n"
    const linesB = "A\nB\nC"

    const diffLines = myersDiff(linesA, linesB)
    expect(diffLines).toEqual([
      { type: "same", line: "A" },
      { type: "same", line: "    B" },
      { type: "same", line: "C" },
    ])
  })
})

describe("Test myersCharDiff function on the same line", () => {
  test("should differentiate character changes", () => {
    const oldContent = "hello world"
    const newContent = "hello earth"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "hello ",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "wo",
        oldIndex: 6,
        oldCharIndexInLine: 6,
        oldLineIndex: 0,
      },
      {
        type: "new",
        char: "ea",
        newIndex: 6,
        newCharIndexInLine: 6,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "r",
        oldIndex: 8,
        newIndex: 8,
        oldCharIndexInLine: 8,
        newCharIndexInLine: 8,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "ld",
        oldIndex: 9,
        oldCharIndexInLine: 9,
        oldLineIndex: 0,
      },
      {
        type: "new",
        char: "th",
        newIndex: 9,
        newCharIndexInLine: 9,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle insertions", () => {
    const oldContent = "abc"
    const newContent = "abxyzc"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "ab",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "new",
        char: "xyz",
        newIndex: 2,
        newCharIndexInLine: 2,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "c",
        oldIndex: 2,
        newIndex: 5,
        oldCharIndexInLine: 2,
        newCharIndexInLine: 5,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle deletions", () => {
    const oldContent = "abxyzc"
    const newContent = "abc"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "ab",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "xyz",
        oldIndex: 2,
        oldCharIndexInLine: 2,
        oldLineIndex: 0,
      },
      {
        type: "same",
        char: "c",
        oldIndex: 5,
        newIndex: 2,
        oldCharIndexInLine: 5,
        newCharIndexInLine: 2,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle empty strings", () => {
    const oldContent = ""
    const newContent = "abc"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "new",
        char: "abc",
        newIndex: 0,
        newCharIndexInLine: 0,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle identical strings", () => {
    const content = "no changes here"

    const diffChars = myersCharDiff(content, content)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "no changes here",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle whitespace changes", () => {
    const oldContent = "hello world"
    const newContent = "hello  world"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "hello ",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "new",
        char: " ",
        newIndex: 6,
        newCharIndexInLine: 6,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "world",
        oldIndex: 6,
        newIndex: 7,
        oldCharIndexInLine: 6,
        newCharIndexInLine: 7,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
    ])
  })

  test("should handle complex changes", () => {
    const oldContent = "The quick brown fox jumps over the lazy dog"
    const newContent = "The fast brown fox leaps over the sleeping dog"

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "The ",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "quick",
        oldIndex: 4,
        oldCharIndexInLine: 4,
        oldLineIndex: 0,
      },
      {
        type: "new",
        char: "fast",
        newIndex: 4,
        newCharIndexInLine: 4,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: " brown fox ",
        oldIndex: 9,
        newIndex: 8,
        oldCharIndexInLine: 9,
        newCharIndexInLine: 8,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "jum",
        oldIndex: 20,
        oldCharIndexInLine: 20,
        oldLineIndex: 0,
      },
      {
        type: "new",
        char: "lea",
        newIndex: 19,
        newCharIndexInLine: 19,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "ps over the ",
        oldIndex: 23,
        newIndex: 22,
        oldCharIndexInLine: 23,
        newCharIndexInLine: 22,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "new",
        char: "s",
        newIndex: 34,
        newCharIndexInLine: 34,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "l",
        oldIndex: 35,
        newIndex: 35,
        oldCharIndexInLine: 35,
        newCharIndexInLine: 35,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "azy",
        oldIndex: 36,
        oldCharIndexInLine: 36,
        oldLineIndex: 0,
      },
      {
        type: "new",
        char: "eeping",
        newIndex: 36,
        newCharIndexInLine: 36,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: " dog",
        oldIndex: 39,
        newIndex: 42,
        oldCharIndexInLine: 39,
        newCharIndexInLine: 42,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
    ])
  })
})

describe("Test myersCharDiff function on different lines", () => {
  test("should track line indices for multi-line changes", () => {
    const oldContent = ["Line one", "Line two", "Line three"].join("\n")

    const newContent = ["Line one", "Modified line", "Line three"].join("\n")

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "Line one",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "\n",
        oldIndex: 8,
        newIndex: 8,
        oldCharIndexInLine: 8,
        newCharIndexInLine: 8,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "L",
        oldIndex: 9,
        oldCharIndexInLine: 0,
        oldLineIndex: 1,
      },
      {
        type: "new",
        char: "Mod",
        newIndex: 9,
        newCharIndexInLine: 0,
        newLineIndex: 1,
      },
      {
        char: "i",
        newCharIndexInLine: 3,
        newIndex: 12,
        newLineIndex: 1,
        oldCharIndexInLine: 1,
        oldIndex: 10,
        oldLineIndex: 1,
        type: "same",
      },
      {
        char: "n",
        oldCharIndexInLine: 2,
        oldIndex: 11,
        oldLineIndex: 1,
        type: "old",
      },
      {
        char: "fi",
        newCharIndexInLine: 4,
        newIndex: 13,
        newLineIndex: 1,
        type: "new",
      },
      {
        char: "e",
        newCharIndexInLine: 6,
        newIndex: 15,
        newLineIndex: 1,
        oldCharIndexInLine: 3,
        oldIndex: 12,
        oldLineIndex: 1,
        type: "same",
      },
      {
        char: "d",
        newCharIndexInLine: 7,
        newIndex: 16,
        newLineIndex: 1,
        type: "new",
      },
      {
        char: " ",
        newCharIndexInLine: 8,
        newIndex: 17,
        newLineIndex: 1,
        oldCharIndexInLine: 4,
        oldIndex: 13,
        oldLineIndex: 1,
        type: "same",
      },
      {
        type: "old",
        char: "two",
        oldCharIndexInLine: 5,
        oldIndex: 14,
        oldLineIndex: 1,
      },
      {
        type: "new",
        char: "line",
        newCharIndexInLine: 9,
        newIndex: 18,
        newLineIndex: 1,
      },
      {
        type: "same",
        char: "\n",
        oldIndex: 17,
        oldCharIndexInLine: 8,
        oldLineIndex: 1,
        newIndex: 22,
        newCharIndexInLine: 13,
        newLineIndex: 1,
      },
      {
        type: "same",
        char: "Line three",
        oldIndex: 18,
        newIndex: 23,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 2,
        newLineIndex: 2,
      },
    ])
  })

  test("should track line indices when adding new lines", () => {
    const oldContent = ["First line", "Last line"].join("\n")

    const newContent = ["First line", "Middle line", "Another middle", "Last line"].join("\n")

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "First line",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "\n",
        oldIndex: 10,
        newIndex: 10,
        oldCharIndexInLine: 10,
        newCharIndexInLine: 10,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "new",
        char: "Middle line",
        newIndex: 11,
        newCharIndexInLine: 0,
        newLineIndex: 1,
      },
      {
        type: "new",
        char: "\n",
        newIndex: 22,
        newCharIndexInLine: 11,
        newLineIndex: 1,
      },
      {
        type: "new",
        char: "Another middle",
        newCharIndexInLine: 0,
        newIndex: 23,
        newLineIndex: 2,
      },
      {
        type: "new",
        char: "\n",
        newCharIndexInLine: 14,
        newIndex: 37,
        newLineIndex: 2,
      },
      {
        type: "same",
        char: "Last line",
        oldIndex: 11,
        oldCharIndexInLine: 0,
        oldLineIndex: 1,
        newIndex: 38,
        newCharIndexInLine: 0,
        newLineIndex: 3,
      },
    ])
  })

  test("should track line indices when removing lines", () => {
    const oldContent = ["Start", "Line to remove", "Another to remove", "End"].join("\n")

    const newContent = ["Start", "End"].join("\n")

    const diffChars = myersCharDiff(oldContent, newContent)
    expect(diffChars).toEqual([
      {
        type: "same",
        char: "Start",
        oldIndex: 0,
        newIndex: 0,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "same",
        char: "\n",
        oldIndex: 5,
        newIndex: 5,
        oldCharIndexInLine: 5,
        newCharIndexInLine: 5,
        oldLineIndex: 0,
        newLineIndex: 0,
      },
      {
        type: "old",
        char: "Line to remove",
        oldIndex: 6,
        oldCharIndexInLine: 0,
        oldLineIndex: 1,
      },
      {
        type: "old",
        char: "\n",
        oldCharIndexInLine: 14,
        oldIndex: 20,
        oldLineIndex: 1,
      },
      {
        type: "old",
        char: "Another to remove",
        oldCharIndexInLine: 0,
        oldIndex: 21,
        oldLineIndex: 2,
      },
      {
        type: "old",
        char: "\n",
        oldCharIndexInLine: 17,
        oldIndex: 38,
        oldLineIndex: 2,
      },
      {
        type: "same",
        char: "End",
        oldIndex: 39,
        newIndex: 6,
        oldCharIndexInLine: 0,
        newCharIndexInLine: 0,
        oldLineIndex: 3,
        newLineIndex: 1,
      },
    ])
  })
})
