import { describe, it, expect, beforeEach } from "vitest";
import { useGraphStore } from "./useGraphStore";

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

const makeNode = (id: string, label = "Node"): Node => ({
  id,
  type: "default",
  position: { x: 0, y: 0 },
  data: { label },
});

const makeEdge = (id: string, source: string, target: string): Edge => ({
  id,
  source,
  target,
});

describe("useGraphStore", () => {
  beforeEach(() => {
    useGraphStore.getState().reset();
  });

  describe("initializeFromGraphData", () => {
    it("should set nodes, edges, and nodeCount", () => {
      const nodes = [makeNode("n1"), makeNode("n2")];
      const edges = [makeEdge("e1", "n1", "n2")];

      useGraphStore.getState().initializeFromGraphData(nodes, edges);

      expect(useGraphStore.getState().nodes).toHaveLength(2);
      expect(useGraphStore.getState().edges).toHaveLength(1);
      expect(useGraphStore.getState().nodeCount).toBe(2);
    });
  });

  describe("setNodes", () => {
    it("should replace nodes and update nodeCount", () => {
      useGraphStore.getState().setNodes([makeNode("a"), makeNode("b"), makeNode("c")]);
      expect(useGraphStore.getState().nodes).toHaveLength(3);
      expect(useGraphStore.getState().nodeCount).toBe(3);
    });

    it("should handle empty array", () => {
      useGraphStore.getState().setNodes([makeNode("x")]);
      useGraphStore.getState().setNodes([]);
      expect(useGraphStore.getState().nodes).toEqual([]);
      expect(useGraphStore.getState().nodeCount).toBe(0);
    });
  });

  describe("setEdges", () => {
    it("should replace edges", () => {
      const edges = [makeEdge("e1", "a", "b"), makeEdge("e2", "b", "c")];
      useGraphStore.getState().setEdges(edges);
      expect(useGraphStore.getState().edges).toHaveLength(2);
    });
  });

  describe("onNodesChange", () => {
    it("should apply node position changes", () => {
      useGraphStore.getState().setNodes([makeNode("n1")]);

      useGraphStore.getState().onNodesChange([
        {
          type: "position",
          id: "n1",
          position: { x: 100, y: 200 },
        },
      ]);

      const updatedNode = useGraphStore.getState().nodes[0];
      expect(updatedNode!.position).toEqual({ x: 100, y: 200 });
    });

    it("should update nodeCount when nodes are removed", () => {
      useGraphStore.getState().setNodes([makeNode("n1"), makeNode("n2")]);
      expect(useGraphStore.getState().nodeCount).toBe(2);

      useGraphStore.getState().onNodesChange([
        { type: "remove", id: "n1" },
      ]);

      expect(useGraphStore.getState().nodeCount).toBe(1);
    });
  });

  describe("onEdgesChange", () => {
    it("should apply edge removal", () => {
      useGraphStore.getState().setEdges([makeEdge("e1", "a", "b")]);

      useGraphStore.getState().onEdgesChange([
        { type: "remove", id: "e1" },
      ]);

      expect(useGraphStore.getState().edges).toHaveLength(0);
    });
  });

  describe("onViewportChange", () => {
    it("should update viewport and zoomLevel", () => {
      useGraphStore.getState().onViewportChange({ x: 50, y: 50, zoom: 1.5 });

      expect(useGraphStore.getState().viewport).toEqual({ x: 50, y: 50, zoom: 1.5 });
      expect(useGraphStore.getState().zoomLevel).toBe(150);
    });

    it("should round zoomLevel to nearest integer", () => {
      useGraphStore.getState().onViewportChange({ x: 0, y: 0, zoom: 0.333 });
      expect(useGraphStore.getState().zoomLevel).toBe(33);
    });

    it("should handle zoom at 100%", () => {
      useGraphStore.getState().onViewportChange({ x: 0, y: 0, zoom: 1.0 });
      expect(useGraphStore.getState().zoomLevel).toBe(100);
    });
  });

  describe("layout state", () => {
    it("should have default layout direction DOWN", () => {
      expect(useGraphStore.getState().layoutDirection).toBe("DOWN");
    });

    it("should have default edge routing ORTHOGONAL", () => {
      expect(useGraphStore.getState().edgeRouting).toBe("ORTHOGONAL");
    });

    it("should have default isLayouting false", () => {
      expect(useGraphStore.getState().isLayouting).toBe(false);
    });

    it("should update layout direction", () => {
      useGraphStore.getState().setLayoutDirection("RIGHT");
      expect(useGraphStore.getState().layoutDirection).toBe("RIGHT");
    });

    it("should update edge routing", () => {
      useGraphStore.getState().setEdgeRouting("SPLINES");
      expect(useGraphStore.getState().edgeRouting).toBe("SPLINES");
    });

    it("should update isLayouting", () => {
      useGraphStore.getState().setIsLayouting(true);
      expect(useGraphStore.getState().isLayouting).toBe(true);
    });

    it("should reset layout state on reset()", () => {
      useGraphStore.getState().setLayoutDirection("LEFT");
      useGraphStore.getState().setEdgeRouting("POLYLINE");
      useGraphStore.getState().setIsLayouting(true);

      useGraphStore.getState().reset();

      expect(useGraphStore.getState().layoutDirection).toBe("DOWN");
      expect(useGraphStore.getState().edgeRouting).toBe("ORTHOGONAL");
      expect(useGraphStore.getState().isLayouting).toBe(false);
    });
  });

  describe("highlightedNodeId", () => {
    it("should default to null", () => {
      expect(useGraphStore.getState().highlightedNodeId).toBeNull();
    });

    it("should set highlighted node id", () => {
      useGraphStore.getState().setHighlightedNodeId("n1");
      expect(useGraphStore.getState().highlightedNodeId).toBe("n1");
    });

    it("should clear highlighted node id", () => {
      useGraphStore.getState().setHighlightedNodeId("n1");
      useGraphStore.getState().setHighlightedNodeId(null);
      expect(useGraphStore.getState().highlightedNodeId).toBeNull();
    });

    it("should reset highlighted node id on reset()", () => {
      useGraphStore.getState().setHighlightedNodeId("n1");
      useGraphStore.getState().reset();
      expect(useGraphStore.getState().highlightedNodeId).toBeNull();
    });
  });
});
