import { describe, expect, it } from "vitest";
import {
  buildCodePointOffsetMap,
  codePointToUtf16,
  utf16ToCodePoint,
} from "./codepoint-to-utf16";
import {
  buildUtf8OffsetMap,
  utf16ToUtf8Byte,
  utf8ByteToUtf16,
} from "./utf8-to-utf16";
import { buildEditorOffsetMap, lineAndColumnAt } from "./offset-map";

describe("offset normalization", () => {
  it.each([
    ["", 0, 0],
    ["ASCII", 5, 5],
    ["é", 2, 1],
    ["😀", 4, 2],
    ["e\u0301", 3, 2],
    ["👩‍💻", 11, 5],
  ])("maps UTF-8 boundaries for %j", (value, bytes, utf16) => {
    const map = buildUtf8OffsetMap(value);
    expect(map.byteLength).toBe(bytes);
    expect(map.utf16Length).toBe(utf16);
    expect(utf8ByteToUtf16(map, bytes)).toBe(utf16);
    expect(utf16ToUtf8Byte(map, utf16)).toBe(bytes);
  });

  it("rejects offsets inside encoded sequences or surrogate pairs", () => {
    const map = buildUtf8OffsetMap("😀");
    expect(() => utf8ByteToUtf16(map, 1)).toThrow(
      /inside an encoded sequence/u,
    );
    expect(() => utf16ToUtf8Byte(map, 1)).toThrow(/splits a surrogate pair/u);
  });

  it("maps code points and UTF-16 boundaries", () => {
    const map = buildCodePointOffsetMap("a😀b");
    expect(map.codePointLength).toBe(3);
    expect(codePointToUtf16(map, 2)).toBe(3);
    expect(utf16ToCodePoint(map, 3)).toBe(2);
    expect(() => utf16ToCodePoint(map, 2)).toThrow(/splits a surrogate pair/u);
  });

  it("tracks lines and end-of-input", () => {
    const map = buildEditorOffsetMap("a\n😀\n");
    expect(lineAndColumnAt(map, 0)).toEqual({ line: 1, column: 1 });
    expect(lineAndColumnAt(map, 2)).toEqual({ line: 2, column: 1 });
    expect(lineAndColumnAt(map, 5)).toEqual({ line: 3, column: 1 });
  });

  it("maps CRLF, bare CR and Unicode line separators", () => {
    const map = buildEditorOffsetMap("a\r\nb\rc\u2028d\u2029e");

    expect(lineAndColumnAt(map, 3)).toEqual({ line: 2, column: 1 });
    expect(lineAndColumnAt(map, 5)).toEqual({ line: 3, column: 1 });
    expect(lineAndColumnAt(map, 7)).toEqual({ line: 4, column: 1 });
    expect(lineAndColumnAt(map, 9)).toEqual({ line: 5, column: 1 });
  });

  it("marks lone surrogates for future UTF-8 engine rejection", () => {
    expect(buildUtf8OffsetMap("\ud800").containsLoneSurrogate).toBe(true);
    expect(buildUtf8OffsetMap("😀").containsLoneSurrogate).toBe(false);
  });
});
