import { describe, it, expect } from "vitest";
import {
  graphNodeToFlowNode,
  graphEdgeToFlowEdge,
  graphToFlow,
  flowNodeToGraphNode,
  flowEdgeToGraphEdge,
  flowToGraph,
} from "./graph-converter";
import type {
  DiagramNode,
  DiagramEdge,
  GraphData,
} from "../types/graph";

describe("graphNodeToFlowNode", () => {
  it("should convert a basic node with position", () => {
    const node: DiagramNode = {
      id: "n1",
      type: "flow:process",
      label: "Start",
      position: { x: 100, y: 200 },
    };
    const result = graphNodeToFlowNode(node);
    expect(result).toEqual({
      id: "n1",
      type: "default",
      position: { x: 100, y: 200 },
      data: { ...node, label: "Start" },
    });
  });

  it("should use default position when node has no position", () => {
    const node: DiagramNode = {
      id: "n2",
      type: "bpmn:activity",
      label: "Task A",
    };
    const result = graphNodeToFlowNode(node);
    expect(result.position).toEqual({ x: 0, y: 0 });
  });

  it("should preserve all node data in data field", () => {
    const node: DiagramNode = {
      id: "n3",
      type: "er:entity",
      label: "Users",
      columns: [
        { name: "id", type: "uuid", isPrimaryKey: true },
        { name: "name", type: "text" },
      ],
    };
    const result = graphNodeToFlowNode(node);
    expect(result.data.columns).toEqual(node.columns);
    expect(result.data.type).toBe("er:entity");
  });
});

describe("graphEdgeToFlowEdge", () => {
  it("should convert from/to to source/target", () => {
    const edge: DiagramEdge = {
      id: "e1",
      from: "n1",
      to: "n2",
      label: "connects",
    };
    const result = graphEdgeToFlowEdge(edge);
    expect(result).toEqual({
      id: "e1",
      source: "n1",
      target: "n2",
      label: "connects",
      type: "default",
      data: { ...edge },
    });
  });

  it("should handle edges without labels", () => {
    const edge: DiagramEdge = { id: "e2", from: "a", to: "b" };
    const result = graphEdgeToFlowEdge(edge);
    expect(result.label).toBeUndefined();
  });

  it("should preserve E-R cardinality", () => {
    const edge: DiagramEdge = {
      id: "e3",
      from: "users",
      to: "orders",
      cardinality: "1:N",
      type: "inheritance",
    };
    const result = graphEdgeToFlowEdge(edge);
    expect(result.data?.cardinality).toBe("1:N");
  });
});

describe("graphToFlow", () => {
  it("should convert full GraphData to flow format", () => {
    const data: GraphData = {
      nodes: [
        { id: "n1", type: "flow:process", label: "A", position: { x: 0, y: 0 } },
        { id: "n2", type: "flow:decision", label: "B", position: { x: 100, y: 100 } },
      ],
      edges: [
        { id: "e1", from: "n1", to: "n2", label: "yes" },
      ],
    };
    const result = graphToFlow(data);
    expect(result.nodes).toHaveLength(2);
    expect(result.edges).toHaveLength(1);
    expect(result.nodes[0]!.id).toBe("n1");
    expect(result.edges[0]!.source).toBe("n1");
    expect(result.edges[0]!.target).toBe("n2");
  });

  it("should handle empty graph data", () => {
    const data: GraphData = { nodes: [], edges: [] };
    const result = graphToFlow(data);
    expect(result.nodes).toEqual([]);
    expect(result.edges).toEqual([]);
  });

  it("should handle all 6 diagram types", () => {
    const diagramTypes = [
      "bpmn:activity",
      "er:entity",
      "org:unit",
      "arch:service",
      "seq:participant",
      "flow:process",
    ];
    const nodes: DiagramNode[] = diagramTypes.map((type, i) => ({
      id: `n${i}`,
      type,
      label: `Node ${i}`,
    }));
    const result = graphToFlow({ nodes, edges: [] });
    expect(result.nodes).toHaveLength(6);
    result.nodes.forEach((node) => {
      expect(node.type).toBe("default");
    });
  });
});

describe("flowNodeToGraphNode", () => {
  it("should convert flow node back to graph node", () => {
    const flowNode = {
      id: "n1",
      type: "default",
      position: { x: 50, y: 75 },
      data: {
        id: "n1",
        type: "bpmn:activity",
        label: "Task",
        lane: "lane1",
      },
    };
    const result = flowNodeToGraphNode(flowNode);
    expect(result.id).toBe("n1");
    expect(result.type).toBe("bpmn:activity");
    expect(result.label).toBe("Task");
    expect(result.position).toEqual({ x: 50, y: 75 });
    expect(result.lane).toBe("lane1");
  });

  it("should default to flow:process when type is missing", () => {
    const flowNode = {
      id: "n1",
      type: "default",
      position: { x: 0, y: 0 },
      data: { label: "No type" },
    };
    const result = flowNodeToGraphNode(flowNode);
    expect(result.type).toBe("flow:process");
  });
});

describe("flowEdgeToGraphEdge", () => {
  it("should convert source/target back to from/to", () => {
    const flowEdge = {
      id: "e1",
      source: "n1",
      target: "n2",
      label: "Yes",
      type: "default",
      data: { type: "sequence", color: "#ff0000" },
    };
    const result = flowEdgeToGraphEdge(flowEdge);
    expect(result.from).toBe("n1");
    expect(result.to).toBe("n2");
    expect(result.label).toBe("Yes");
    expect(result.type).toBe("sequence");
    expect(result.color).toBe("#ff0000");
  });

  it("should handle edges with no data", () => {
    const flowEdge = {
      id: "e2",
      source: "a",
      target: "b",
    };
    const result = flowEdgeToGraphEdge(flowEdge);
    expect(result.from).toBe("a");
    expect(result.to).toBe("b");
    expect(result.type).toBeUndefined();
  });
});

describe("flowToGraph", () => {
  it("should convert full flow state back to GraphData", () => {
    const nodes = [
      {
        id: "n1",
        type: "default",
        position: { x: 10, y: 20 },
        data: { type: "flow:process", label: "Step 1" },
      },
    ];
    const edges = [
      {
        id: "e1",
        source: "n1",
        target: "n2",
        label: "next",
        data: { type: "sequence" },
      },
    ];
    const meta = {
      version: "1.0",
      title: "Test",
      diagramType: "flowchart" as const,
    };
    const result = flowToGraph(nodes, edges, meta);
    expect(result.meta).toEqual(meta);
    expect(result.nodes).toHaveLength(1);
    expect(result.edges).toHaveLength(1);
    expect(result.nodes[0]!.type).toBe("flow:process");
    expect(result.edges[0]!.from).toBe("n1");
  });

  it("should roundtrip: graphToFlow -> flowToGraph preserves data", () => {
    const original: GraphData = {
      meta: {
        version: "1.0",
        title: "Roundtrip Test",
        diagramType: "er",
      },
      nodes: [
        {
          id: "users",
          type: "er:entity",
          label: "Users",
          position: { x: 0, y: 0 },
          columns: [
            { name: "id", type: "uuid", isPrimaryKey: true },
            { name: "email", type: "varchar" },
          ],
        },
        {
          id: "orders",
          type: "er:entity",
          label: "Orders",
          position: { x: 200, y: 0 },
        },
      ],
      edges: [
        {
          id: "e1",
          from: "users",
          to: "orders",
          label: "has",
          cardinality: "1:N",
        },
      ],
    };

    const flow = graphToFlow(original);
    const roundtripped = flowToGraph(flow.nodes, flow.edges, original.meta);

    expect(roundtripped.meta).toEqual(original.meta);
    expect(roundtripped.nodes).toHaveLength(2);
    expect(roundtripped.edges).toHaveLength(1);
    expect(roundtripped.nodes[0]!.type).toBe("er:entity");
    expect(roundtripped.nodes[0]!.columns).toEqual(original.nodes[0]!.columns);
    expect(roundtripped.edges[0]!.from).toBe("users");
    expect(roundtripped.edges[0]!.to).toBe("orders");
    expect(roundtripped.edges[0]!.cardinality).toBe("1:N");
  });
});
