import { describe, expect, it } from "vitest"
import type { Entity, EntityKind, GraphProvider, Property, Relation } from "@enesis/editor"
import { GraphService } from "./graph"

/**
 * In-memory GraphProvider for testing the GraphService factory and
 * integration with application patterns (sidebar, suggestions, etc.).
 */
class MemoryGraphProvider implements GraphProvider {
  entities: Entity[] = []
  relations: Relation[] = []
  properties: Property[] = []

  async getEntities(kind?: EntityKind): Promise<Entity[]> {
    if (kind) return this.entities.filter((e) => e.kind === kind)
    return [...this.entities]
  }

  async getEntity(id: string): Promise<Entity | undefined> {
    return this.entities.find((e) => e.id === id)
  }

  async getEntitiesByIds(ids: string[]): Promise<Entity[]> {
    const idSet = new Set(ids)
    return this.entities.filter((e) => idSet.has(e.id))
  }

  async getRelations(from?: string, to?: string, type?: Relation["type"]): Promise<Relation[]> {
    return this.relations.filter((r) => {
      if (from && r.from !== from) return false
      if (to && r.to !== to) return false
      if (type && r.type !== type) return false
      return true
    })
  }

  async getProperties(entityId?: string, key?: string): Promise<Property[]> {
    return this.properties.filter((p) => {
      if (entityId && p.entityId !== entityId) return false
      if (key && p.key !== key) return false
      return true
    })
  }
}

function createFixture() {
  const provider = new MemoryGraphProvider()
  const graph = new GraphService(provider)
  return { provider, graph }
}

describe("GraphService — useSidebarIndex integration", () => {
  it("lists all pages with titles", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "pages/Foo.md", kind: "page", title: "Foo" },
      { id: "pages/Bar.md", kind: "page", title: "Bar" },
    )

    const pages = await graph.getEntities("page")
    expect(pages).toHaveLength(2)
    expect(pages.map((p) => p.title).sort()).toEqual(["Bar", "Foo"])
  })

  it("returns empty list when no pages exist", async () => {
    const { graph } = createFixture()
    expect(await graph.getEntities("page")).toEqual([])
  })
})

describe("GraphService — useIndexSuggestions integration", () => {
  it("lists pages for [[ suggestion menu", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "pages/Architecture.md", kind: "page", title: "Architecture" },
      { id: "pages/Notes.md", kind: "page", title: "Notes" },
    )

    const pages = await graph.getEntities("page")
    expect(pages).toHaveLength(2)
    expect(pages[0].title).toBeTruthy()
  })

  it("lists blocks for (( suggestion menu", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "abc123", kind: "block", title: "Some block content", path: "pages/Foo.md" },
      { id: "def456", kind: "block", title: "Another block", path: "pages/Bar.md" },
    )

    const blocks = await graph.getEntities("block")
    expect(blocks).toHaveLength(2)
    expect(blocks[0].path).toBeTruthy()
  })

  it("lists tags for # suggestion menu", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "design", kind: "tag", title: "design" },
      { id: "dev", kind: "tag", title: "dev" },
    )

    const tags = await graph.getEntities("tag")
    expect(tags).toHaveLength(2)
    expect(tags.map((t) => t.id).sort()).toEqual(["design", "dev"])
  })
})

describe("GraphService — usePageNavigation integration", () => {
  it("resolves block ID to its page path", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "block-1", kind: "block", path: "pages/Foo.md" },
    )

    const entity = await graph.getEntity("block-1")
    expect(entity).toBeDefined()
    expect(entity!.path).toBe("pages/Foo.md")
  })

  it("returns undefined for unknown block ID", async () => {
    const { graph } = createFixture()
    expect(await graph.getEntity("nonexistent")).toBeUndefined()
  })
})

describe("GraphService — TagView integration", () => {
  it("returns tagged blocks for a given tag", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "block-1", kind: "block", title: "Design notes", path: "pages/Design.md" },
      { id: "block-2", kind: "block", title: "Code review", path: "pages/Code.md" },
    )
    provider.relations.push(
      { from: "block-1", to: "design", type: "tagged" },
      { from: "block-2", to: "dev", type: "tagged" },
    )

    const incoming = await graph.getIncoming("design")
    expect(incoming).toHaveLength(1)
    expect(incoming[0].from.id).toBe("block-1")
    expect(incoming[0].via.type).toBe("tagged")
  })

  it("returns empty when no blocks are tagged", async () => {
    const { graph } = createFixture()
    expect(await graph.getIncoming("untagged-tag")).toEqual([])
  })
})

describe("GraphService — [...slug].vue page title integration", () => {
  it("resolves page path to entity with title", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "pages/Foo.md", kind: "page", title: "My Page Title" },
    )

    const entity = await graph.getEntity("pages/Foo.md")
    expect(entity).toBeDefined()
    expect(entity!.title).toBe("My Page Title")
  })

  it("falls back when page has no title", async () => {
    const { provider, graph } = createFixture()
    provider.entities.push(
      { id: "pages/Untitled.md", kind: "page", title: "Untitled" },
    )

    const entity = await graph.getEntity("pages/Untitled.md")
    expect(entity).toBeDefined()
    expect(entity!.title).toBe("Untitled")
  })
})
