/**
 * @module tests/unit/brainy/related-visibility-fast-path
 * @description Graph-perf #1 (8.0): the visibility-aware fast adjacency path.
 *
 * `related({ from })` / `related({ to })` route through the O(degree)
 * GraphAdjacencyIndex fast paths in `storage.getVerbs`. Before this fix, the
 * default visibility exclusion (`excludeVisibility: ['internal','system']`,
 * which every default `related()` sets) DISQUALIFIED those fast paths and forced
 * a full O(E) shard scan — the root cause of multi-second per-node edge lookups
 * on large graphs. The fast paths now hydrate each candidate's metadata and
 * apply the subtype / visibility filters on the small O(degree) candidate set.
 *
 * Two observable contracts are pinned here (the perf change itself is structural
 * — these prove the fast path produces the SAME correct results the scan did):
 * 1. default `related()` excludes `internal` / `system` edges; `includeInternal`
 *    / `includeSystem` opt them back in.
 * 2. returned `Relation`s now carry `visibility` (previously dropped by
 *    `hydrateVerbWithMetadata`, so `related({ includeInternal })` could not even
 *    report which edges were internal).
 */

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'

describe('related() visibility-aware fast adjacency (graph-perf #1)', () => {
  let brain: Brainy
  let a: string, b: string, c: string, d: 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.Person, subtype: 'employee', data: 'C' })
    d = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'D' })
    // A → B public, A → C internal, A → D public.
    await brain.relate({ from: a, to: b, type: VerbType.RelatedTo, subtype: 'colleague' })
    await brain.relate({ from: a, to: c, type: VerbType.RelatedTo, subtype: 'colleague', visibility: 'internal' })
    await brain.relate({ from: a, to: d, type: VerbType.RelatedTo, subtype: 'colleague' })
  })

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

  it('default related({ from }) excludes internal edges (out-direction fast path)', async () => {
    const edges = await brain.related({ from: a })
    const targets = edges.map((e) => e.to).sort()
    expect(targets).toEqual([b, d].sort())
    // None of the default results carry a non-public visibility.
    expect(edges.every((e) => e.visibility === undefined || e.visibility === 'public')).toBe(true)
  })

  it('related({ from, includeInternal }) surfaces the internal edge AND reports its visibility', async () => {
    const edges = await brain.related({ from: a, includeInternal: true })
    expect(edges.map((e) => e.to).sort()).toEqual([b, c, d].sort())
    const internalEdge = edges.find((e) => e.to === c)
    // The latent-bug fix: visibility now flows through hydration to the Relation.
    expect(internalEdge?.visibility).toBe('internal')
  })

  it('default related({ to }) excludes an internal edge (in-direction fast path)', async () => {
    // B has one public in-edge; C has one internal in-edge.
    const intoB = await brain.related({ to: b })
    expect(intoB.map((e) => e.from)).toEqual([a])

    const intoC = await brain.related({ to: c })
    expect(intoC).toHaveLength(0) // internal edge hidden by default

    const intoCAll = await brain.related({ to: c, includeInternal: true })
    expect(intoCAll.map((e) => e.from)).toEqual([a])
    expect(intoCAll[0]?.visibility).toBe('internal')
  })

  it('subtype filter still works on the fast path (returns only matching, excludes internal)', async () => {
    const colleagues = await brain.related({ from: a, subtype: 'colleague' })
    expect(colleagues.map((e) => e.to).sort()).toEqual([b, d].sort())
  })
})
