import { describe, it, expect } from "vitest"
import type { KpWithRelations } from "./types"
import { computeGraphLayout } from "./graph-layout"

describe("textbooks/graph-layout", () => {
  const makeKp = (
    id: string,
    parentId: string | null = null,
    prerequisiteIds: string[] = [],
  ): KpWithRelations => ({
    id,
    name: `KP-${id}`,
    description: null,
    parentId,
    chapterId: "c1",
    level: 1,
    order: 0,
    chapterTitle: "Chapter 1",
    questionCount: 0,
    prerequisiteIds,
  })

  describe("computeGraphLayout", () => {
    it("should return empty layout for empty input", () => {
      const layout = computeGraphLayout([])
      expect(layout.nodes).toEqual([])
      expect(layout.edges).toEqual([])
    })

    it("should place single node", () => {
      const layout = computeGraphLayout([makeKp("1")])
      expect(layout.nodes).toHaveLength(1)
      expect(layout.nodes[0].id).toBe("1")
    })

    it("should generate parent-child edge", () => {
      const layout = computeGraphLayout([makeKp("1"), makeKp("2", "1")])
      expect(layout.nodes).toHaveLength(2)
      const parentEdge = layout.edges.find((e) => e.id === "parent-1-2")
      expect(parentEdge).toBeDefined()
    })

    it("should generate prerequisite edge", () => {
      const layout = computeGraphLayout([
        makeKp("1"),
        makeKp("2", null, ["1"]),
      ])
      const prereqEdge = layout.edges.find((e) => e.id === "prereq-1-2")
      expect(prereqEdge).toBeDefined()
    })

    it("should assign positions to all nodes", () => {
      const layout = computeGraphLayout([
        makeKp("1"),
        makeKp("2", "1"),
        makeKp("3", "1"),
      ])
      for (const node of layout.nodes) {
        expect(node.position.x).toBeGreaterThanOrEqual(0)
        expect(node.position.y).toBeGreaterThanOrEqual(0)
      }
    })

    it("should not create edge for non-existent parentId", () => {
      const layout = computeGraphLayout([makeKp("1", "nonexistent")])
      expect(layout.nodes).toHaveLength(1)
      expect(layout.edges).toHaveLength(0)
    })

    it("should not create edge for non-existent prerequisiteIds", () => {
      const layout = computeGraphLayout([makeKp("1", null, ["nonexistent"])])
      expect(layout.nodes).toHaveLength(1)
      expect(layout.edges).toHaveLength(0)
    })

    it("should generate both parent and prerequisite edges for one node", () => {
      const layout = computeGraphLayout([
        makeKp("1"),
        makeKp("2"),
        makeKp("3", "1", ["2"]),
      ])
      const parentEdge = layout.edges.find((e) => e.id === "parent-1-3")
      const prereqEdge = layout.edges.find((e) => e.id === "prereq-2-3")
      expect(parentEdge).toBeDefined()
      expect(prereqEdge).toBeDefined()
      expect(layout.edges).toHaveLength(2)
    })

    it("should return positive width/height for non-empty graph", () => {
      const layout = computeGraphLayout([
        makeKp("1"),
        makeKp("2", "1"),
        makeKp("3", "1"),
      ])
      expect(layout.width).toBeGreaterThan(0)
      expect(layout.height).toBeGreaterThan(0)
    })

    it("should handle deeply nested parent chain (boundary)", () => {
      const depth = 20
      const kps: KpWithRelations[] = [makeKp("0")]
      for (let i = 1; i < depth; i++) {
        kps.push(makeKp(String(i), String(i - 1)))
      }
      const layout = computeGraphLayout(kps)
      expect(layout.nodes).toHaveLength(depth)
      // 应生成 depth-1 条 parent 边
      const parentEdges = layout.edges.filter((e) => e.id.startsWith("parent-"))
      expect(parentEdges).toHaveLength(depth - 1)
    })

    it("should handle many nodes (boundary, 50 nodes)", () => {
      const count = 50
      const kps: KpWithRelations[] = []
      for (let i = 0; i < count; i++) {
        kps.push(makeKp(`n${i}`))
      }
      const layout = computeGraphLayout(kps)
      expect(layout.nodes).toHaveLength(count)
      expect(layout.edges).toHaveLength(0)
      // 所有节点都应有有效位置
      for (const node of layout.nodes) {
        expect(Number.isFinite(node.position.x)).toBe(true)
        expect(Number.isFinite(node.position.y)).toBe(true)
      }
    })
  })
})
