import { describe, expect, it } from "vitest";

import type { Node, Edge } from "@xyflow/react";

import { countNodeDiffs, countEdgeDiffs, buildSummary } from "./useProposalDiff";

function createTestNode(id: string, label: string, type = "flowProcess"): Node {
  return {
    id,
    type,
    position: { x: 0, y: 0 },
    data: { id, label, type },
  };
}

function createTestEdge(id: string, source: string, target: string): Edge {
  return { id, source, target };
}

describe("countNodeDiffs", () => {
  it("should detect added nodes", () => {
    const previous = [createTestNode("n1", "A")];
    const proposed = [createTestNode("n1", "A"), createTestNode("n2", "B")];
    const result = countNodeDiffs(previous, proposed);
    expect(result.added).toBe(1);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(0);
  });

  it("should detect removed nodes", () => {
    const previous = [createTestNode("n1", "A"), createTestNode("n2", "B")];
    const proposed = [createTestNode("n1", "A")];
    const result = countNodeDiffs(previous, proposed);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(1);
    expect(result.modified).toBe(0);
  });

  it("should detect modified nodes by label", () => {
    const previous = [createTestNode("n1", "Old Label")];
    const proposed = [createTestNode("n1", "New Label")];
    const result = countNodeDiffs(previous, proposed);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(1);
  });

  it("should detect modified nodes by type", () => {
    const previous = [createTestNode("n1", "A", "flowProcess")];
    const proposed = [createTestNode("n1", "A", "flowDecision")];
    const result = countNodeDiffs(previous, proposed);
    expect(result.modified).toBe(1);
  });

  it("should handle mixed changes", () => {
    const previous = [
      createTestNode("n1", "A"),
      createTestNode("n2", "B"),
      createTestNode("n3", "C"),
    ];
    const proposed = [
      createTestNode("n1", "Modified A"),
      createTestNode("n3", "C"),
      createTestNode("n4", "New"),
    ];
    const result = countNodeDiffs(previous, proposed);
    expect(result.added).toBe(1);
    expect(result.removed).toBe(1);
    expect(result.modified).toBe(1);
  });

  it("should handle empty graphs", () => {
    const result = countNodeDiffs([], []);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(0);
  });

  it("should handle no changes", () => {
    const nodes = [createTestNode("n1", "A"), createTestNode("n2", "B")];
    const result = countNodeDiffs(nodes, nodes);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(0);
  });

  it("should detect all nodes as added when starting from empty", () => {
    const proposed = [createTestNode("n1", "A"), createTestNode("n2", "B")];
    const result = countNodeDiffs([], proposed);
    expect(result.added).toBe(2);
    expect(result.removed).toBe(0);
  });
});

describe("countEdgeDiffs", () => {
  it("should detect added edges", () => {
    const previous = [createTestEdge("e1", "n1", "n2")];
    const proposed = [createTestEdge("e1", "n1", "n2"), createTestEdge("e2", "n2", "n3")];
    const result = countEdgeDiffs(previous, proposed);
    expect(result.added).toBe(1);
    expect(result.removed).toBe(0);
  });

  it("should detect removed edges", () => {
    const previous = [createTestEdge("e1", "n1", "n2"), createTestEdge("e2", "n2", "n3")];
    const proposed = [createTestEdge("e1", "n1", "n2")];
    const result = countEdgeDiffs(previous, proposed);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(1);
  });

  it("should detect modified edges by label", () => {
    const previous = [{ ...createTestEdge("e1", "n1", "n2"), label: "old" } as Edge];
    const proposed = [{ ...createTestEdge("e1", "n1", "n2"), label: "new" } as Edge];
    const result = countEdgeDiffs(previous, proposed);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(1);
  });

  it("should detect modified edges by type", () => {
    const previous = [{ ...createTestEdge("e1", "n1", "n2"), type: "sync" } as Edge];
    const proposed = [{ ...createTestEdge("e1", "n1", "n2"), type: "async" } as Edge];
    const result = countEdgeDiffs(previous, proposed);
    expect(result.modified).toBe(1);
  });

  it("should handle empty edge lists", () => {
    const result = countEdgeDiffs([], []);
    expect(result.added).toBe(0);
    expect(result.removed).toBe(0);
    expect(result.modified).toBe(0);
  });
});

describe("buildSummary", () => {
  it("should return 'No changes' when nothing changed", () => {
    expect(buildSummary(0, 0, 0, 0, 0)).toBe("No changes");
  });

  it("should format added nodes", () => {
    expect(buildSummary(3, 0, 0, 0, 0)).toBe("Adding 3 nodes");
  });

  it("should format single node correctly", () => {
    expect(buildSummary(1, 0, 0, 0, 0)).toBe("Adding 1 node");
  });

  it("should format mixed node and edge changes", () => {
    const summary = buildSummary(2, 1, 1, 1, 0);
    expect(summary).toContain("Adding 2 nodes");
    expect(summary).toContain("modifying 1 node");
    expect(summary).toContain("removing 1 node");
    expect(summary).toContain("adding 1 edge");
  });

  it("should format removed edges", () => {
    expect(buildSummary(0, 0, 0, 0, 2)).toBe("removing 2 edges");
  });

  it("should format modified edges", () => {
    expect(buildSummary(0, 0, 0, 0, 0, 3)).toBe("modifying 3 edges");
  });

  it("should separate parts with commas", () => {
    const summary = buildSummary(1, 1, 0, 0, 0);
    expect(summary).toBe("Adding 1 node, removing 1 node");
  });
});
