import { Repository, SqliteRepository } from '../../db/index.js';
import { isTestFile } from '../../infrastructure/test-filter.js';
import { normalizeSymbol, toSymbolRef } from '../../shared/normalize.js';
import { paginateResult } from '../../shared/paginate.js';
import { resolveAnalysisOpts, withRepo } from './query-helpers.js';
import { findMatchingNodes } from './symbol-lookup.js';
/** Cache so repeated raw-db calls reuse the same SqliteRepository (preserves per-instance memoization). */
const repoCache = new WeakMap();
/** Coerce a raw db handle or Repository into a Repository instance. */
function toRepo(dbOrRepo) {
    if (dbOrRepo instanceof Repository)
        return dbOrRepo;
    const db = dbOrRepo;
    let repo = repoCache.get(db);
    if (!repo) {
        repo = new SqliteRepository(db);
        repoCache.set(db, repo);
    }
    return repo;
}
// --- Shared BFS: transitive callers ---
const INTERFACE_LIKE_KINDS = new Set(['interface', 'trait']);
/** Record an implementor node at the given depth, adding to frontier and levels. */
function recordImplementor(impl, parentId, depth, visited, frontier, levels, noTests, onVisit) {
    if (visited.has(impl.id) || (noTests && isTestFile(impl.file)))
        return;
    visited.add(impl.id);
    frontier.push(impl.id);
    if (!levels[depth])
        levels[depth] = [];
    levels[depth].push({
        name: impl.name,
        kind: impl.kind,
        file: impl.file,
        line: impl.line,
        viaImplements: true,
    });
    if (onVisit)
        onVisit({ ...impl, viaImplements: true }, parentId, depth);
}
/** Expand implementors for an interface/trait node into the BFS frontier. */
function expandImplementors(repo, nodeId, depth, visited, frontier, levels, noTests, onVisit) {
    const impls = repo.findImplementors(nodeId);
    for (const impl of impls) {
        recordImplementor(impl, nodeId, depth, visited, frontier, levels, noTests, onVisit);
    }
}
export function bfsTransitiveCallers(dbOrRepo, startId, { noTests = false, maxDepth = 3, includeImplementors = true, onVisit, } = {}) {
    const repo = toRepo(dbOrRepo);
    const resolveImplementors = includeImplementors && repo.hasImplementsEdges();
    const visited = new Set([startId]);
    const levels = {};
    let frontier = [startId];
    // Seed: if start node is an interface/trait, include its implementors at depth 1
    const implNextFrontier = [];
    if (resolveImplementors) {
        const startNode = repo.findNodeById(startId);
        if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
            expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
        }
    }
    for (let d = 1; d <= maxDepth; d++) {
        if (d === 1 && implNextFrontier.length > 0) {
            frontier = [...frontier, ...implNextFrontier];
        }
        const nextFrontier = [];
        for (const fid of frontier) {
            const callers = repo.findDistinctCallers(fid);
            for (const c of callers) {
                if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
                    visited.add(c.id);
                    nextFrontier.push(c.id);
                    if (!levels[d])
                        levels[d] = [];
                    levels[d].push(toSymbolRef(c));
                    if (onVisit)
                        onVisit(c, fid, d);
                }
                if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
                    expandImplementors(repo, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
                }
            }
        }
        frontier = nextFrontier;
        if (frontier.length === 0)
            break;
    }
    return { totalDependents: visited.size - 1, levels };
}
export function impactAnalysisData(file, customDbPath, opts = {}) {
    return withRepo(customDbPath, (repo) => {
        const noTests = opts.noTests || false;
        const fileNodes = repo.findFileNodes(`%${file}%`);
        if (fileNodes.length === 0) {
            return { file, sources: [], levels: {}, totalDependents: 0 };
        }
        const visited = new Set();
        const queue = [];
        const levels = new Map();
        for (const fn of fileNodes) {
            visited.add(fn.id);
            queue.push(fn.id);
            levels.set(fn.id, 0);
        }
        while (queue.length > 0) {
            const current = queue.shift();
            const level = levels.get(current);
            const dependents = repo.findImportDependents(current);
            for (const dep of dependents) {
                if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
                    visited.add(dep.id);
                    queue.push(dep.id);
                    levels.set(dep.id, level + 1);
                }
            }
        }
        const byLevel = {};
        for (const [id, level] of levels) {
            if (level === 0)
                continue;
            if (!byLevel[level])
                byLevel[level] = [];
            const node = repo.findNodeById(id);
            if (node)
                byLevel[level].push({ file: node.file });
        }
        return {
            file,
            sources: fileNodes.map((f) => f.file),
            levels: byLevel,
            totalDependents: visited.size - fileNodes.length,
        };
    });
}
export function fnImpactData(name, customDbPath, opts = {}) {
    return withRepo(customDbPath, (repo) => {
        const { noTests, config } = resolveAnalysisOpts(opts);
        const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
        const hc = new Map();
        const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
        if (nodes.length === 0) {
            return { name, results: [] };
        }
        const includeImplementors = opts.includeImplementors !== false;
        const results = nodes.map((node) => {
            const { levels, totalDependents } = bfsTransitiveCallers(repo, node.id, {
                noTests,
                maxDepth,
                includeImplementors,
            });
            return {
                ...normalizeSymbol(node, repo, hc),
                levels,
                totalDependents,
            };
        });
        const base = { name, results };
        return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
    });
}
//# sourceMappingURL=fn-impact.js.map