import { processFile, type FileDiffMetadata } from "@pierre/diffs";
import { describe, expect, it } from "vitest";
import { LineOp, type DiffFile } from "$lib/api/diff";
import { renderUnifiedDiff } from "$lib/api/rawdiff";
import { toFileDiffMetadata } from "./fileDiff";

// the mapper must produce exactly what pierre's own parser
// produces from the rendered text!
//
// excluded: type/prevName, the parser
// can only get it from git header lines the renderer doesn't emit (mode too),
// cacheKey, only worker pools use it, and hunkSpecs
const matchesParsedPatch = (file: DiffFile) => {
	const parsed = processFile(renderUnifiedDiff({ diff: [file] }));
	expect(parsed).toBeDefined();
	const stripKeys = (value: object, keys: string[]): Record<string, unknown> =>
		Object.fromEntries(Object.entries(value).filter(([key]) => !keys.includes(key)));
	const strip = (meta: FileDiffMetadata) => ({
		...stripKeys(meta, ["type", "prevName", "mode", "cacheKey"]),
		hunks: meta.hunks.map((hunk) => stripKeys(hunk, ["hunkSpecs"]))
	});
	expect(strip(toFileDiffMetadata(file))).toEqual(strip(parsed!));
};

describe("toFileDiffMetadata", () => {
	it("maps a modified file", () => {
		matchesParsedPatch({
			name: { old: "foo.go", new: "foo.go" },
			text_fragments: [
				{
					Comment: "func main()",
					OldPosition: 1,
					OldLines: 3,
					NewPosition: 1,
					NewLines: 3,
					Lines: [
						{ Op: LineOp.Context, Line: "package main\n" },
						{ Op: LineOp.Delete, Line: "// old comment\n" },
						{ Op: LineOp.Add, Line: "// new comment\n" },
						{ Op: LineOp.Context, Line: "func main() {}\n" }
					]
				}
			]
		});
	});

	it("maps a new file", () => {
		matchesParsedPatch({
			name: { old: "", new: "new.go" },
			is_new: true,
			text_fragments: [
				{
					OldPosition: 0,
					OldLines: 0,
					NewPosition: 1,
					NewLines: 2,
					Lines: [
						{ Op: LineOp.Add, Line: "package main\n" },
						{ Op: LineOp.Add, Line: "func main() {}\n" }
					]
				}
			]
		});
	});

	it("maps a deleted file", () => {
		matchesParsedPatch({
			name: { old: "old.go", new: "" },
			is_delete: true,
			text_fragments: [
				{
					OldPosition: 1,
					OldLines: 2,
					NewPosition: 0,
					NewLines: 0,
					Lines: [
						{ Op: LineOp.Delete, Line: "package main\n" },
						{ Op: LineOp.Delete, Line: "func main() {}\n" }
					]
				}
			]
		});
	});

	it("maps a renamed file with multiple fragments", () => {
		matchesParsedPatch({
			name: { old: "old.go", new: "renamed.go" },
			is_rename: true,
			text_fragments: [
				{
					OldPosition: 1,
					OldLines: 2,
					NewPosition: 1,
					NewLines: 2,
					Lines: [
						{ Op: LineOp.Context, Line: "package main\n" },
						{ Op: LineOp.Delete, Line: "func old() {}\n" },
						{ Op: LineOp.Add, Line: "func renamed() {}\n" }
					]
				},
				{
					Comment: "func init()",
					OldPosition: 10,
					OldLines: 1,
					NewPosition: 10,
					NewLines: 1,
					Lines: [{ Op: LineOp.Context, Line: "var x = 1\n" }]
				}
			]
		});
	});

	it("maps a missing trailing newline", () => {
		matchesParsedPatch({
			name: { old: "foo.go", new: "foo.go" },
			text_fragments: [
				{
					OldPosition: 1,
					OldLines: 2,
					NewPosition: 1,
					NewLines: 2,
					Lines: [
						{ Op: LineOp.Delete, Line: "old" },
						{ Op: LineOp.Add, Line: "new\n" },
						{ Op: LineOp.Context, Line: "tail" }
					]
				}
			]
		});
	});

	it("marks the file type from the wire flags", () => {
		expect(toFileDiffMetadata({ name: { old: "", new: "n.go" }, is_new: true }).type).toBe("new");
		expect(toFileDiffMetadata({ name: { old: "o.go", new: "" }, is_delete: true }).type).toBe(
			"deleted"
		);
		const renamed = toFileDiffMetadata({ name: { old: "o.go", new: "n.go" }, is_rename: true });
		expect(renamed.type).toBe("rename-changed");
		expect(renamed.prevName).toBe("o.go");
		expect(toFileDiffMetadata({ name: { old: "n.go", new: "n.go" } }).prevName).toBeUndefined();
	});
});
