import test from "node:test";
import assert from "node:assert/strict";
import { loadPatchDiffs } from "./patch-diffs.js";

test("loads every patch diff and preserves per-file failures", async () => {
  const files = [{ path: "first.js" }, { path: "missing.js" }];
  const diffs = await loadPatchDiffs(files, async (path) => {
    if (path === "missing.js") throw new Error("failed");
    return { diff: "+added" };
  });

  assert.equal(diffs[0].path, "first.js");
  assert.equal(diffs[0].additions, 1);
  assert.equal(diffs[0].deletions, 0);
  assert.equal(diffs[0].lines[0].kind, "addition");
  assert.deepEqual(diffs[1], { path: "missing.js", error: "changes_diff_failed", lines: [] });
});

test("marks activity files that are no longer current git changes", async () => {
  const diffs = await loadPatchDiffs([{ path: "committed.js" }], async () => {
    throw new Error("changed_file_not_found");
  });

  assert.deepEqual(diffs, [{ path: "committed.js", unavailableReason: "changed_file_not_found", lines: [] }]);
});

test("uses file activity diffs without requesting the current workspace diff", async () => {
  let calls = 0;
  const diffs = await loadPatchDiffs([{ path: "patched.js", diff: "+from activity" }], async () => {
    calls += 1;
    throw new Error("must_not_call");
  });

  assert.equal(calls, 0);
  assert.equal(diffs[0].path, "patched.js");
  assert.equal(diffs[0].additions, 1);
  assert.equal(diffs[0].deletions, 0);
  assert.equal(diffs[0].lines[0].kind, "addition");
});

test("preserves safe diff limit reasons without requesting file content", async () => {
  let calls = 0;
  const diffs = await loadPatchDiffs([
    { path: "large.txt", unavailableReason: "diff_too_large" },
    { path: "image.png", unavailableReason: "binary_file" }
  ], async () => {
    calls += 1;
    throw new Error("must_not_call");
  });

  assert.equal(calls, 0);
  assert.deepEqual(diffs, [
    { path: "large.txt", unavailableReason: "diff_too_large", additions: 0, deletions: 0, lines: [] },
    { path: "image.png", unavailableReason: "binary_file", additions: 0, deletions: 0, lines: [] }
  ]);
});

test("preserves file change counts when the diff has no countable lines", async () => {
  const diffs = await loadPatchDiffs([{ path: "binary.png", additions: 4, deletions: 2 }], async () => {
    return { diff: "Binary files a/binary.png and b/binary.png differ" };
  });

  assert.equal(diffs[0].additions, 4);
  assert.equal(diffs[0].deletions, 2);
});

test("reports each diff as soon as that file finishes loading", async () => {
  const pending = new Map();
  const completed = [];
  const result = loadPatchDiffs(
    [{ path: "slow.js" }, { path: "fast.js" }],
    (path) => new Promise((resolve) => pending.set(path, resolve)),
    (diff) => completed.push(diff.path)
  );
  await Promise.resolve();

  pending.get("fast.js")({ diff: "+fast" });
  await new Promise((resolve) => setTimeout(resolve, 0));
  assert.deepEqual(completed, ["fast.js"]);

  pending.get("slow.js")({ diff: "+slow" });
  assert.deepEqual((await result).map((diff) => diff.path), ["slow.js", "fast.js"]);
  assert.deepEqual(completed, ["fast.js", "slow.js"]);
});
