/**
 * LogicalTraversal - Deprecated facade over GraphTraversal + QueryReadModelNeighborProvider.
 *
 * New code should use GraphTraversal directly.
 *
 * @deprecated
 */
import TraversalError from "../../errors/TraversalError.js";
import GraphTraversal from "./GraphTraversal.js";
import QueryReadModelNeighborProvider from "./QueryReadModelNeighborProvider.js";
import { stripUndefined, assertDirection, normalizeLabelFilter, } from "./traversalHelpers.js";
const DEFAULT_MAX_DEPTH = 1000;
/** @deprecated Use GraphTraversal + QueryReadModelNeighborProvider directly. */
export default class LogicalTraversal {
    _provider;
    constructor(provider) {
        this._provider = provider;
    }
    /** Prepares engine from the current query read model. Does NOT validate start nodes. */
    async _prepareEngine(opts) {
        const direction = assertDirection(opts.dir);
        const labelSet = normalizeLabelFilter(opts.labelFilter);
        const depthLimit = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
        const provider = new QueryReadModelNeighborProvider(await this._provider.openQueryReadModel());
        const engine = new GraphTraversal({ provider });
        const options = labelSet ? { labels: labelSet } : undefined;
        return { engine, provider, direction, options, depthLimit };
    }
    /** Prepares engine and validates a single start node. */
    async _prepare(start, opts) {
        const prepared = await this._prepareEngine(opts);
        if (!(await prepared.provider.hasNode(start))) {
            throw new TraversalError(`Start node not found: ${start}`, { code: 'NODE_NOT_FOUND', context: { start } });
        }
        return prepared;
    }
    /** Validates one or more start nodes exist. */
    async _validateStarts(provider, starts) {
        const arr = Array.isArray(starts) ? starts : [starts];
        for (const s of arr) {
            if (!(await provider.hasNode(s))) {
                throw new TraversalError(`Start node not found: ${s}`, { code: 'NODE_NOT_FOUND', context: { start: s } });
            }
        }
    }
    /** Breadth-first traversal. */
    async bfs(start, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepare(start, options);
        const { nodes } = await engine.bfs(stripUndefined({
            start, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity, signal: options.signal,
        }));
        return nodes;
    }
    /** Breadth-first traversal stream. */
    async *bfsStream(start, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepare(start, options);
        yield* engine.bfsStream(stripUndefined({
            start, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity, signal: options.signal,
        }));
    }
    /** Depth-first traversal (pre-order). */
    async dfs(start, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepare(start, options);
        const { nodes } = await engine.dfs(stripUndefined({
            start, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity, signal: options.signal,
        }));
        return nodes;
    }
    /** Depth-first traversal stream. */
    async *dfsStream(start, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepare(start, options);
        yield* engine.dfsStream(stripUndefined({
            start, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity, signal: options.signal,
        }));
    }
    /** Shortest path (unweighted BFS). */
    async shortestPath(from, to, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepare(from, options);
        const { found, path, length } = await engine.shortestPath(stripUndefined({
            start: from, goal: to, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity,
        }));
        return { found, path, length };
    }
    /** Connected component (undirected BFS). */
    async connectedComponent(start, options = {}) {
        return await this.bfs(start, { ...options, dir: 'both' });
    }
    /** Reachability check — BFS with early termination. Non-existent nodes are unreachable. */
    async isReachable(from, to, options = {}) {
        const { engine, direction, options: opts, depthLimit } = await this._prepareEngine(options);
        const { reachable } = await engine.isReachable(stripUndefined({
            start: from, goal: to, direction, options: opts, maxDepth: depthLimit, maxNodes: Infinity, signal: options.signal,
        }));
        return { reachable };
    }
    /** Weighted shortest path (Dijkstra). */
    async weightedShortestPath(from, to, options = {}) {
        const { engine, direction, options: opts } = await this._prepare(from, options);
        const { path, totalCost } = await engine.weightedShortestPath(stripUndefined({
            start: from, goal: to, direction, options: opts,
            weightFn: options.weightFn, nodeWeightFn: options.nodeWeightFn,
            maxNodes: Infinity, signal: options.signal,
        }));
        return { path, totalCost };
    }
    /** A* search with heuristic guidance. */
    async aStarSearch(from, to, options = {}) {
        const { engine, direction, options: opts } = await this._prepare(from, options);
        const { path, totalCost, nodesExplored } = await engine.aStarSearch(stripUndefined({
            start: from, goal: to, direction, options: opts,
            weightFn: options.weightFn, nodeWeightFn: options.nodeWeightFn, heuristicFn: options.heuristicFn,
            maxNodes: Infinity, signal: options.signal,
        }));
        return { path, totalCost, nodesExplored };
    }
    /** Bidirectional A* search. Direction fixed: forward=out, backward=in. */
    async bidirectionalAStar(from, to, options = {}) {
        const { engine, provider, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, from);
        const { path, totalCost, nodesExplored } = await engine.bidirectionalAStar(stripUndefined({
            start: from, goal: to, options: opts,
            weightFn: options.weightFn, nodeWeightFn: options.nodeWeightFn,
            forwardHeuristic: options.forwardHeuristic, backwardHeuristic: options.backwardHeuristic,
            maxNodes: Infinity, signal: options.signal,
        }));
        return { path, totalCost, nodesExplored };
    }
    /** Topological sort (Kahn's algorithm). */
    async topologicalSort(start, options = {}) {
        const { engine, provider, direction, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, start);
        const { sorted, hasCycle } = await engine.topologicalSort(stripUndefined({
            start, direction, options: opts, maxNodes: Infinity, throwOnCycle: options.throwOnCycle, signal: options.signal,
        }));
        return { sorted, hasCycle };
    }
    /** Common ancestors — multi-source ancestor intersection. Direction fixed to 'in'. */
    async commonAncestors(nodes, options = {}) {
        const { engine, provider, options: opts, depthLimit } = await this._prepareEngine(options);
        for (const n of nodes) {
            if (!(await provider.hasNode(n))) {
                throw new TraversalError(`Node not found: ${n}`, { code: 'NODE_NOT_FOUND', context: { node: n } });
            }
        }
        const { ancestors } = await engine.commonAncestors(stripUndefined({
            nodes, options: opts, maxDepth: depthLimit, maxResults: options.maxResults, signal: options.signal,
        }));
        return { ancestors };
    }
    /** Weighted longest path via topological sort + DP (DAGs only). */
    async weightedLongestPath(from, to, options = {}) {
        const { engine, direction, options: opts } = await this._prepare(from, options);
        const { path, totalCost } = await engine.weightedLongestPath(stripUndefined({
            start: from, goal: to, direction, options: opts,
            weightFn: options.weightFn, nodeWeightFn: options.nodeWeightFn,
            maxNodes: Infinity, signal: options.signal,
        }));
        return { path, totalCost };
    }
    /** Longest-path level assignment (DAGs only). */
    async levels(start, options = {}) {
        const { engine, provider, direction, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, start);
        const { levels, maxLevel } = await engine.levels(stripUndefined({
            start, direction, options: opts, maxNodes: Infinity, signal: options.signal,
        }));
        return { levels, maxLevel };
    }
    /** Transitive reduction — minimal edge set preserving reachability (DAGs only). */
    async transitiveReduction(start, options = {}) {
        const { engine, provider, direction, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, start);
        const { edges, removed } = await engine.transitiveReduction(stripUndefined({
            start, direction, options: opts, maxNodes: Infinity, signal: options.signal,
        }));
        return { edges, removed };
    }
    /** Transitive closure — all implied reachability edges. */
    async transitiveClosure(start, options = {}) {
        const { engine, provider, direction, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, start);
        const { edges } = await engine.transitiveClosure(stripUndefined({
            start, direction, options: opts, maxNodes: Infinity, maxEdges: options.maxEdges, signal: options.signal,
        }));
        return { edges };
    }
    /** Transitive closure stream — yields implied reachability edges lazily. */
    async *transitiveClosureStream(start, options = {}) {
        const { engine, provider, direction, options: opts } = await this._prepareEngine(options);
        await this._validateStarts(provider, start);
        yield* engine.transitiveClosureStream(stripUndefined({
            start, direction, options: opts, maxNodes: Infinity, maxEdges: options.maxEdges, signal: options.signal,
        }));
    }
    /** Find all root ancestors (in-degree-0 nodes) reachable backward from start. */
    async rootAncestors(start, options = {}) {
        const { engine, options: opts, depthLimit } = await this._prepare(start, options);
        const { roots } = await engine.rootAncestors(stripUndefined({
            start, options: opts, maxNodes: Infinity, maxDepth: options.maxDepth ?? depthLimit, signal: options.signal,
        }));
        return { roots };
    }
}
