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

import { buildBpmnElkGraph, resolveBpmnPositions } from "./bpmn-layout";
import type { GraphData } from "../types/graph";

function createTestBpmnData(): GraphData {
  return {
    meta: {
      version: "1.0",
      title: "Test BPMN",
      diagramType: "bpmn",
      layoutDirection: "RIGHT",
      edgeRouting: "ORTHOGONAL",
    },
    pools: [
      {
        id: "pool1",
        label: "Main Pool",
        lanes: [
          { id: "lane1", label: "Lane 1" },
          { id: "lane2", label: "Lane 2" },
        ],
      },
    ],
    nodes: [
      { id: "start", type: "bpmn:start-event", label: "Start", lane: "lane1" },
      {
        id: "task1",
        type: "bpmn:activity",
        label: "Do Work",
        tag: "Task 1",
        lane: "lane1",
      },
      {
        id: "gw1",
        type: "bpmn:gateway-exclusive",
        label: "Decision?",
        lane: "lane2",
      },
      { id: "end", type: "bpmn:end-event", label: "End", lane: "lane2" },
      {
        id: "note1",
        type: "bpmn:annotation",
        label: "Free-floating note",
      },
    ],
    edges: [
      { id: "e1", from: "start", to: "task1" },
      { id: "e2", from: "task1", to: "gw1", label: "Go" },
      { id: "e3", from: "gw1", to: "end" },
    ],
  };
}

describe("buildBpmnElkGraph", () => {
  it("should build a root graph with pool hierarchy", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    expect(graph.id).toBe("root");
    expect(graph.layoutOptions?.["elk.hierarchyHandling"]).toBe(
      "INCLUDE_CHILDREN",
    );
  });

  it("should create pool as a child of root", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    // root children: pool1 + free-floating note1
    expect(graph.children?.length).toBe(2);
    const poolChild = graph.children?.find((c) => c.id === "pool1");
    expect(poolChild).toBeDefined();
  });

  it("should create lanes as children of pool", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    const poolChild = graph.children?.find((c) => c.id === "pool1");
    expect(poolChild?.children?.length).toBe(2);

    const lane1 = poolChild?.children?.find((c) => c.id === "lane1");
    const lane2 = poolChild?.children?.find((c) => c.id === "lane2");
    expect(lane1).toBeDefined();
    expect(lane2).toBeDefined();
  });

  it("should place nodes in their assigned lanes", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    const poolChild = graph.children?.find((c) => c.id === "pool1");
    const lane1 = poolChild?.children?.find((c) => c.id === "lane1");
    const lane2 = poolChild?.children?.find((c) => c.id === "lane2");

    const lane1NodeIds = lane1?.children?.map((c) => c.id);
    expect(lane1NodeIds).toContain("start");
    expect(lane1NodeIds).toContain("task1");

    const lane2NodeIds = lane2?.children?.map((c) => c.id);
    expect(lane2NodeIds).toContain("gw1");
    expect(lane2NodeIds).toContain("end");
  });

  it("should place free-floating nodes at root level", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    const rootNodeIds = graph.children?.map((c) => c.id);
    expect(rootNodeIds).toContain("note1");
  });

  it("should assign same-lane edges to lane container", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    // e1: start→task1, both in lane1 → belongs to lane1
    const poolChild = graph.children?.find((c) => c.id === "pool1");
    const lane1 = poolChild?.children?.find((c) => c.id === "lane1");
    const lane1Edges = (lane1 as { edges?: Array<{ id: string }> }).edges;
    const lane1EdgeIds = lane1Edges?.map((e) => e.id);
    expect(lane1EdgeIds).toContain("e0"); // first edge
  });

  it("should assign cross-lane edges to pool container", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    // e2: task1(lane1)→gw1(lane2), cross-lane → belongs to pool1
    const poolChild = graph.children?.find((c) => c.id === "pool1");
    const poolEdges = (poolChild as { edges?: Array<{ id: string }> }).edges;
    const poolEdgeIds = poolEdges?.map((e) => e.id);
    expect(poolEdgeIds).toContain("e1"); // second edge
  });

  it("should use BPMN_SIZES dimensions for nodes", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data);

    const poolChild = graph.children?.find((c) => c.id === "pool1");
    const lane1 = poolChild?.children?.find((c) => c.id === "lane1");
    const startNode = lane1?.children?.find((c) => c.id === "start");
    expect(startNode?.width).toBe(36); // start-event width
    expect(startNode?.height).toBe(36);
  });

  it("should handle data without pools", () => {
    const data: GraphData = {
      nodes: [
        { id: "a", type: "bpmn:activity", label: "A" },
        { id: "b", type: "bpmn:activity", label: "B" },
      ],
      edges: [{ id: "e1", from: "a", to: "b" }],
    };
    const graph = buildBpmnElkGraph(data);
    const childIds = graph.children?.map((c) => c.id);
    expect(childIds).toContain("a");
    expect(childIds).toContain("b");
  });

  it("should respect layout direction from options", () => {
    const data = createTestBpmnData();
    const graph = buildBpmnElkGraph(data, { direction: "DOWN" });
    expect(graph.layoutOptions?.["elk.direction"]).toBe("DOWN");
  });
});

describe("resolveBpmnPositions", () => {
  it("should resolve absolute positions from nested structure", () => {
    const mockElkResult = {
      id: "root",
      x: 0,
      y: 0,
      width: 800,
      height: 600,
      children: [
        {
          id: "pool1",
          x: 10,
          y: 10,
          width: 700,
          height: 500,
          children: [
            {
              id: "lane1",
              x: 5,
              y: 5,
              width: 690,
              height: 240,
              children: [
                { id: "start", x: 20, y: 30, width: 36, height: 36 },
              ],
            },
          ],
        },
      ],
    };

    const positions = resolveBpmnPositions(mockElkResult);

    // root at (0,0)
    expect(positions.get("root")).toEqual({
      x: 0,
      y: 0,
      w: 800,
      h: 600,
    });

    // pool1 at root offset (10,10)
    expect(positions.get("pool1")).toEqual({
      x: 10,
      y: 10,
      w: 700,
      h: 500,
    });

    // lane1 at pool1 + (5,5) = (15, 15)
    expect(positions.get("lane1")).toEqual({
      x: 15,
      y: 15,
      w: 690,
      h: 240,
    });

    // start at lane1 + (20,30) = (35, 45)
    expect(positions.get("start")).toEqual({
      x: 35,
      y: 45,
      w: 36,
      h: 36,
    });
  });

  it("should handle empty children", () => {
    const mockElkResult = {
      id: "root",
      x: 0,
      y: 0,
      width: 100,
      height: 100,
    };

    const positions = resolveBpmnPositions(mockElkResult);
    expect(positions.size).toBe(1);
    expect(positions.get("root")).toBeDefined();
  });
});
