/**
 * @module tests/unit/brainy/graph-export
 * @description Graph engine #22: `brain.graph.export()` — stream the whole graph
 * in one O(N+E) pass (the right primitive for visualizing all data, vs. paging
 * per node). Exercises the pure-TS fallback (no native provider in CI), which
 * streams all nouns then all verbs via cursor pagination.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../../src/index.js'
import { NounType, VerbType } from '../../../src/types/graphTypes.js'
import { createTestConfig } from '../../helpers/test-factory.js'
import type { GraphView } from '../../../src/index.js'

async function collect(stream: AsyncIterable<GraphView>): Promise<{
  nodes: Map<string, { id: string; type?: NounType; subtype?: string }>
  edges: Map<string, { id: string; from: string; to: string }>
  chunks: number
}> {
  const nodes = new Map<string, { id: string; type?: NounType; subtype?: string }>()
  const edges = new Map<string, { id: string; from: string; to: string }>()
  let chunks = 0
  for await (const chunk of stream) {
    chunks++
    for (const n of chunk.nodes) nodes.set(n.id, n)
    for (const e of chunk.edges) edges.set(e.id, e)
  }
  return { nodes, edges, chunks }
}

describe('brain.graph.export() (graph engine #22)', () => {
  let brain: Brainy
  let a: string, b: string, c: string, iso: string

  beforeEach(async () => {
    brain = new Brainy(createTestConfig())
    await brain.init()
    a = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'A' })
    b = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'B' })
    c = await brain.add({ type: NounType.Project, subtype: 'milestone', data: 'C' })
    iso = await brain.add({ type: NounType.Concept, subtype: 'general', data: 'isolated' })
    await brain.relate({ from: a, to: b, type: VerbType.RelatedTo, subtype: 'colleague' })
    await brain.relate({ from: b, to: c, type: VerbType.ParticipatesIn, subtype: 'assignment' })
  })

  afterEach(async () => {
    await brain.close()
  })

  it('streams the WHOLE graph — every node (incl. isolated) and every edge', async () => {
    const { nodes, edges } = await collect(brain.graph.export())
    // All four nouns, including the isolated one (the node stream catches it).
    expect(new Set(nodes.keys())).toEqual(new Set([a, b, c, iso]))
    // Node refs carry type/subtype straight from the noun records.
    expect(nodes.get(c)?.type).toBe(NounType.Project)
    expect(nodes.get(c)?.subtype).toBe('milestone')
    // Both edges.
    const pairs = [...edges.values()].map((e) => `${e.from}->${e.to}`).sort()
    expect(pairs).toEqual([`${a}->${b}`, `${b}->${c}`].sort())
  })

  it('excludes internal-visibility nodes/edges by default; includeInternal surfaces them', async () => {
    const hidden = await brain.add({
      type: NounType.Person,
      subtype: 'employee',
      data: 'hidden',
      visibility: 'internal'
    })
    await brain.relate({ from: a, to: hidden, type: VerbType.RelatedTo, subtype: 'colleague', visibility: 'internal' })

    const visible = await collect(brain.graph.export())
    expect(visible.nodes.has(hidden)).toBe(false)
    expect([...visible.edges.values()].some((e) => e.to === hidden)).toBe(false)

    const all = await collect(brain.graph.export({ includeInternal: true }))
    expect(all.nodes.has(hidden)).toBe(true)
    expect([...all.edges.values()].some((e) => e.to === hidden)).toBe(true)
  })

  it('includeNodes:false streams only edges; includeEdges:false streams only nodes', async () => {
    const edgesOnly = await collect(brain.graph.export({ includeNodes: false }))
    expect(edgesOnly.nodes.size).toBe(0)
    expect(edgesOnly.edges.size).toBe(2)

    const nodesOnly = await collect(brain.graph.export({ includeEdges: false }))
    expect(nodesOnly.edges.size).toBe(0)
    expect(nodesOnly.nodes.size).toBe(4)
  })

  it('chunks the stream by chunkSize (multiple passes, same total)', async () => {
    const { nodes, edges, chunks } = await collect(brain.graph.export({ chunkSize: 1 }))
    // 4 nodes + 2 edges at 1 per chunk → several chunks.
    expect(chunks).toBeGreaterThan(1)
    expect(nodes.size).toBe(4)
    expect(edges.size).toBe(2)
  })
})
