import path from 'node:path';
import { findCrossFileCallTargets, findDbPath, findFileNodes, findNodesByFile, } from '../../db/index.js';
import { cachedStmt } from '../../db/repository/cached-stmt.js';
import { debug } from '../../infrastructure/logger.js';
import { isTestFile } from '../../infrastructure/test-filter.js';
import { createFileLinesReader, extractSignature, extractSummary, } from '../../shared/file-utils.js';
import { paginateResult } from '../../shared/paginate.js';
import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
/** Cache the schema probe for the `exported` column per db handle. */
const _hasExportedColCache = new WeakMap();
const _exportedNodesStmtCache = new WeakMap();
const _consumersStmtCache = new WeakMap();
const _reexportsFromStmtCache = new WeakMap();
const _reexportsToStmtCache = new WeakMap();
export function exportsData(file, customDbPath, opts = {}) {
    return withReadonlyDb(customDbPath, (db) => {
        const { noTests, displayOpts } = resolveAnalysisOpts(opts);
        const dbFilePath = findDbPath(customDbPath);
        const repoRoot = path.resolve(path.dirname(dbFilePath), '..');
        const getFileLines = createFileLinesReader(repoRoot);
        const unused = opts.unused || false;
        const fileResults = exportsFileImpl(db, file, noTests, getFileLines, unused, displayOpts);
        if (fileResults.length === 0) {
            return paginateResult({
                file,
                results: [],
                reexports: [],
                reexportedSymbols: [],
                totalExported: 0,
                totalInternal: 0,
                totalUnused: 0,
                totalReexported: 0,
                totalReexportedUnused: 0,
            }, 'results', { limit: opts.limit, offset: opts.offset });
        }
        // For single-file match return flat; for multi-match return first (like explainData)
        const first = fileResults[0];
        const base = {
            file: first.file,
            results: first.results,
            reexports: first.reexports,
            reexportedSymbols: first.reexportedSymbols,
            totalExported: first.totalExported,
            totalInternal: first.totalInternal,
            totalUnused: first.totalUnused,
            totalReexported: first.totalReexported,
            totalReexportedUnused: first.totalReexportedUnused,
        };
        const paginated = paginateResult(base, 'results', {
            limit: opts.limit,
            offset: opts.offset,
        });
        // Paginate reexportedSymbols with the same limit/offset (match paginateResult behaviour)
        if (opts.limit != null) {
            const off = opts.offset || 0;
            paginated.reexportedSymbols = paginated.reexportedSymbols.slice(off, off + opts.limit);
            // Update _pagination.hasMore to account for reexportedSymbols (barrel-only files
            // have empty results[], so hasMore would always be false without this)
            if (paginated._pagination) {
                const reexTotal = opts.unused ? base.totalReexportedUnused : base.totalReexported;
                const resultsHasMore = paginated._pagination.hasMore;
                const reexHasMore = off + opts.limit < reexTotal;
                paginated._pagination.hasMore = resultsHasMore || reexHasMore;
            }
        }
        return paginated;
    });
}
/** Collect symbols re-exported through barrel files. */
function collectReexportedSymbols(db, fileNodeId, reexportsToStmt, exportedNodesStmt, hasExportedCol, getFileLines, buildSymbolResult) {
    const reexportTargets = reexportsToStmt.all(fileNodeId);
    const reexportedSymbols = [];
    for (const reexTarget of reexportTargets) {
        let targetExported;
        if (hasExportedCol) {
            targetExported = exportedNodesStmt.all(reexTarget.file);
        }
        else {
            const targetSymbols = findNodesByFile(db, reexTarget.file);
            const exportedIds = findCrossFileCallTargets(db, reexTarget.file);
            targetExported = targetSymbols.filter((s) => exportedIds.has(s.id));
        }
        for (const s of targetExported) {
            reexportedSymbols.push({
                ...buildSymbolResult(s, getFileLines(reexTarget.file)),
                originFile: reexTarget.file,
            });
        }
    }
    return reexportedSymbols;
}
function exportsFileImpl(db, target, noTests, getFileLines, unused, displayOpts) {
    const fileNodes = findFileNodes(db, `%${target}%`);
    if (fileNodes.length === 0)
        return [];
    // Detect whether exported column exists (cached per db handle)
    let hasExportedCol;
    if (_hasExportedColCache.has(db)) {
        hasExportedCol = _hasExportedColCache.get(db);
    }
    else {
        hasExportedCol = false;
        try {
            db.prepare('SELECT exported FROM nodes LIMIT 0').raw(true);
            hasExportedCol = true;
        }
        catch (e) {
            debug(`exported column not available, using fallback: ${e.message}`);
        }
        _hasExportedColCache.set(db, hasExportedCol);
    }
    const exportedNodesStmt = hasExportedCol
        ? cachedStmt(_exportedNodesStmtCache, db, "SELECT * FROM nodes WHERE file = ? AND kind != 'file' AND exported = 1 ORDER BY line")
        : null;
    const consumersStmt = cachedStmt(_consumersStmtCache, db, `SELECT n.name, n.file, n.line FROM edges e JOIN nodes n ON e.source_id = n.id
         WHERE e.target_id = ? AND e.kind = 'calls'`);
    const reexportsFromStmt = cachedStmt(_reexportsFromStmtCache, db, `SELECT DISTINCT n.file FROM edges e JOIN nodes n ON e.source_id = n.id
       WHERE e.target_id = ? AND e.kind = 'reexports'`);
    const reexportsToStmt = cachedStmt(_reexportsToStmtCache, db, `SELECT DISTINCT n.file FROM edges e JOIN nodes n ON e.target_id = n.id
       WHERE e.source_id = ? AND e.kind = 'reexports'`);
    return fileNodes.map((fn) => {
        const symbols = findNodesByFile(db, fn.file);
        let exported;
        if (hasExportedCol) {
            // Use the exported column populated during build
            exported = exportedNodesStmt.all(fn.file);
        }
        else {
            // Fallback: symbols that have incoming calls from other files
            const exportedIds = findCrossFileCallTargets(db, fn.file);
            exported = symbols.filter((s) => exportedIds.has(s.id));
        }
        const internalCount = symbols.length - exported.length;
        const buildSymbolResult = (s, fileLines) => {
            let consumers = consumersStmt.all(s.id);
            if (noTests)
                consumers = consumers.filter((c) => !isTestFile(c.file));
            return {
                name: s.name,
                kind: s.kind,
                line: s.line,
                endLine: s.end_line ?? null,
                role: s.role || null,
                signature: fileLines ? extractSignature(fileLines, s.line, displayOpts) : null,
                summary: fileLines ? extractSummary(fileLines, s.line, displayOpts) : null,
                consumers: consumers.map((c) => ({ name: c.name, file: c.file, line: c.line })),
                consumerCount: consumers.length,
            };
        };
        const results = exported.map((s) => buildSymbolResult(s, getFileLines(fn.file)));
        const totalUnused = results.filter((r) => r.consumerCount === 0).length;
        const reexports = reexportsFromStmt.all(fn.id).map((r) => ({
            file: r.file,
        }));
        // Gather symbols re-exported from target modules (barrel file support)
        const reexportedSymbols = collectReexportedSymbols(db, fn.id, reexportsToStmt, exportedNodesStmt, hasExportedCol, getFileLines, buildSymbolResult);
        let filteredResults = results;
        let filteredReexported = reexportedSymbols;
        if (unused) {
            filteredResults = results.filter((r) => r.consumerCount === 0);
            filteredReexported = reexportedSymbols.filter((r) => r.consumerCount === 0);
        }
        const totalReexported = reexportedSymbols.length;
        const totalReexportedUnused = reexportedSymbols.filter((r) => r.consumerCount === 0).length;
        return {
            file: fn.file,
            results: filteredResults,
            reexports,
            reexportedSymbols: filteredReexported,
            totalExported: exported.length,
            totalInternal: internalCount,
            totalUnused,
            totalReexported,
            totalReexportedUnused,
        };
    });
}
//# sourceMappingURL=exports.js.map