/**
 * Incremental single-file rebuild — used by watch mode.
 *
 * Reuses pipeline helpers instead of duplicating node insertion and edge building
 * logic from the main builder. This eliminates the watcher.js divergence (ROADMAP 3.9).
 *
 * Reverse-dep cascade: when a file changes, files that have edges targeting it
 * must have their outgoing edges rebuilt (since the changed file's node IDs change).
 */
import fs from 'node:fs';
import path from 'node:path';
import { bulkNodeIdsByFile, purgeFileData } from '../../../db/index.js';
import { debug, warn } from '../../../infrastructure/logger.js';
import { normalizePath } from '../../../shared/constants.js';
import { parseFileIncremental } from '../../parser.js';
import { computeConfidence, resolveImportPath } from '../resolve.js';
import { findCaller, resolveCallTargets, resolveReceiverEdge, } from './call-resolver.js';
import { BUILTIN_RECEIVERS, readFileSafe } from './helpers.js';
// ── Node insertion ──────────────────────────────────────────────────────
function insertFileNodes(stmts, relPath, symbols) {
    stmts.insertNode.run(relPath, 'file', relPath, 0, null);
    for (const def of symbols.definitions) {
        stmts.insertNode.run(def.name, def.kind, relPath, def.line, def.endLine || null);
        if (def.children?.length) {
            for (const child of def.children) {
                stmts.insertNode.run(child.name, child.kind, relPath, child.line, child.endLine || null);
            }
        }
    }
    for (const exp of symbols.exports) {
        stmts.insertNode.run(exp.name, exp.kind, relPath, exp.line, null);
    }
}
// ── Containment edges ──────────────────────────────────────────────────
function buildContainmentEdges(db, stmts, relPath, symbols) {
    const nodeIdMap = new Map();
    for (const row of bulkNodeIdsByFile(db, relPath)) {
        nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
    }
    const fileId = nodeIdMap.get(`${relPath}|file|0`);
    let edgesAdded = 0;
    for (const def of symbols.definitions) {
        const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
        if (fileId && defId) {
            stmts.insertEdge.run(fileId, defId, 'contains', 1.0, 0);
            edgesAdded++;
        }
        if (def.children?.length && defId) {
            for (const child of def.children) {
                const childId = nodeIdMap.get(`${child.name}|${child.kind}|${child.line}`);
                if (childId) {
                    stmts.insertEdge.run(defId, childId, 'contains', 1.0, 0);
                    edgesAdded++;
                    if (child.kind === 'parameter') {
                        stmts.insertEdge.run(childId, defId, 'parameter_of', 1.0, 0);
                        edgesAdded++;
                    }
                }
            }
        }
    }
    return edgesAdded;
}
// ── Reverse-dep cascade ────────────────────────────────────────────────
// Lazily-cached prepared statements for reverse-dep operations
let _revDepDb = null;
let _findRevDepsStmt = null;
let _deleteOutEdgesStmt = null;
function getRevDepStmts(db) {
    if (_revDepDb !== db) {
        _revDepDb = db;
        _findRevDepsStmt = db.prepare(`SELECT DISTINCT n_src.file FROM edges e
       JOIN nodes n_src ON e.source_id = n_src.id
       JOIN nodes n_tgt ON e.target_id = n_tgt.id
       WHERE n_tgt.file = ? AND n_src.file != ? AND n_src.kind != 'directory'`);
        _deleteOutEdgesStmt = db.prepare('DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)');
    }
    return {
        findRevDepsStmt: _findRevDepsStmt,
        deleteOutEdgesStmt: _deleteOutEdgesStmt,
    };
}
function findReverseDeps(db, relPath) {
    const { findRevDepsStmt } = getRevDepStmts(db);
    return findRevDepsStmt.all(relPath, relPath).map((r) => r.file);
}
function deleteOutgoingEdges(db, relPath) {
    const { deleteOutEdgesStmt } = getRevDepStmts(db);
    deleteOutEdgesStmt.run(relPath);
}
async function parseReverseDep(rootDir, depRelPath, engineOpts, cache) {
    const absPath = path.join(rootDir, depRelPath);
    if (!fs.existsSync(absPath))
        return null;
    let code;
    try {
        code = readFileSafe(absPath);
    }
    catch (e) {
        debug(`parseReverseDep: cannot read ${absPath}: ${e instanceof Error ? e.message : String(e)}`);
        return null;
    }
    return parseFileIncremental(cache, absPath, code, engineOpts);
}
function rebuildReverseDepEdges(db, rootDir, depRelPath, symbols, stmts, skipBarrel) {
    const fileNodeRow = stmts.getNodeId.get(depRelPath, 'file', depRelPath, 0);
    if (!fileNodeRow)
        return 0;
    const aliases = { baseUrl: null, paths: {} };
    let edgesAdded = buildContainmentEdges(db, stmts, depRelPath, symbols);
    // Don't rebuild dir->file containment for reverse-deps (it was never deleted)
    edgesAdded += buildImportEdges(stmts, depRelPath, symbols, rootDir, fileNodeRow.id, aliases, skipBarrel ? null : db);
    const importedNames = buildImportedNamesMap(symbols, rootDir, depRelPath, aliases, db);
    edgesAdded += buildCallEdges(db, stmts, depRelPath, symbols, fileNodeRow, importedNames);
    edgesAdded += buildClassHierarchyEdges(stmts, depRelPath, symbols);
    return edgesAdded;
}
// ── Directory containment edges ────────────────────────────────────────
function rebuildDirContainment(_db, stmts, relPath) {
    const dir = normalizePath(path.dirname(relPath));
    if (!dir || dir === '.')
        return 0;
    const dirRow = stmts.getNodeId.get(dir, 'directory', dir, 0);
    const fileRow = stmts.getNodeId.get(relPath, 'file', relPath, 0);
    if (dirRow && fileRow) {
        stmts.insertEdge.run(dirRow.id, fileRow.id, 'contains', 1.0, 0);
        return 1;
    }
    return 0;
}
// ── Import edge building ────────────────────────────────────────────────
// Lazily-cached prepared statements for barrel resolution (avoid re-preparing in hot loops)
let _barrelDb = null;
let _isBarrelStmt = null;
let _reexportTargetsStmt = null;
let _hasDefStmt = null;
function getBarrelStmts(db) {
    if (_barrelDb !== db) {
        _barrelDb = db;
        _isBarrelStmt = db.prepare(`SELECT COUNT(*) as c FROM edges e
       JOIN nodes n ON e.source_id = n.id
       WHERE e.kind = 'reexports' AND n.file = ? AND n.kind = 'file'`);
        _reexportTargetsStmt = db.prepare(`SELECT DISTINCT n2.file FROM edges e
       JOIN nodes n1 ON e.source_id = n1.id
       JOIN nodes n2 ON e.target_id = n2.id
       WHERE e.kind = 'reexports' AND n1.file = ? AND n1.kind = 'file'`);
        _hasDefStmt = db.prepare(`SELECT 1 FROM nodes WHERE name = ? AND file = ? AND kind != 'file' AND kind != 'directory' LIMIT 1`);
    }
    return {
        isBarrelStmt: _isBarrelStmt,
        reexportTargetsStmt: _reexportTargetsStmt,
        hasDefStmt: _hasDefStmt,
    };
}
function isBarrelFile(db, relPath) {
    const { isBarrelStmt } = getBarrelStmts(db);
    const reexportCount = isBarrelStmt.get(relPath)?.c;
    return (reexportCount || 0) > 0;
}
function resolveBarrelTarget(db, barrelPath, symbolName, visited = new Set()) {
    if (visited.has(barrelPath))
        return null;
    visited.add(barrelPath);
    const { reexportTargetsStmt, hasDefStmt } = getBarrelStmts(db);
    // Find re-export targets from this barrel
    const reexportTargets = reexportTargetsStmt.all(barrelPath);
    for (const { file: targetFile } of reexportTargets) {
        // Check if the symbol is defined in this target file
        const hasDef = hasDefStmt.get(symbolName, targetFile);
        if (hasDef)
            return targetFile;
        // Recurse through barrel chains
        if (isBarrelFile(db, targetFile)) {
            const deeper = resolveBarrelTarget(db, targetFile, symbolName, visited);
            if (deeper)
                return deeper;
        }
    }
    return null;
}
/**
 * Resolve barrel imports for a single import statement and create edges to actual source files.
 * Shared by buildImportEdges (primary file) and Pass 2 of the reverse-dep cascade.
 */
function resolveBarrelImportEdges(db, stmts, fileNodeId, resolvedPath, imp) {
    let edgesAdded = 0;
    if (!isBarrelFile(db, resolvedPath))
        return edgesAdded;
    const resolvedSources = new Set();
    for (const name of imp.names) {
        const cleanName = name.replace(/^\*\s+as\s+/, '');
        const actualSource = resolveBarrelTarget(db, resolvedPath, cleanName);
        if (actualSource && actualSource !== resolvedPath && !resolvedSources.has(actualSource)) {
            resolvedSources.add(actualSource);
            const actualRow = stmts.getNodeId.get(actualSource, 'file', actualSource, 0);
            if (actualRow) {
                const kind = imp.typeOnly ? 'imports-type' : 'imports';
                stmts.insertEdge.run(fileNodeId, actualRow.id, kind, 0.9, 0);
                edgesAdded++;
            }
        }
    }
    return edgesAdded;
}
/** Emit symbol-level `imports-type` edges for a single `import type` statement. */
function emitTypeOnlySymbolEdges(db, stmts, imp, resolvedPath, fileNodeId) {
    let edgesAdded = 0;
    for (const name of imp.names) {
        const cleanName = name.replace(/^\*\s+as\s+/, '');
        let targetFile = resolvedPath;
        if (db && isBarrelFile(db, resolvedPath)) {
            const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
            if (actual)
                targetFile = actual;
        }
        const candidates = stmts.findNodeInFile.all(cleanName, targetFile);
        if (candidates.length === 0)
            continue;
        stmts.insertEdge.run(fileNodeId, candidates[0].id, 'imports-type', 1.0, 0);
        edgesAdded++;
    }
    return edgesAdded;
}
/**
 * Process a single import statement: emit the file→file edge, any
 * symbol-level type-only edges, and barrel re-export edges.
 */
function emitEdgesForImport(stmts, imp, fileNodeId, relPath, rootDir, aliases, db) {
    const resolvedPath = resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
    const targetRow = stmts.getNodeId.get(resolvedPath, 'file', resolvedPath, 0);
    if (!targetRow)
        return 0;
    const edgeKind = imp.reexport
        ? 'reexports'
        : imp.typeOnly
            ? 'imports-type'
            : imp.dynamicImport
                ? 'dynamic-imports'
                : 'imports';
    stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
    let edgesAdded = 1;
    if (imp.typeOnly) {
        edgesAdded += emitTypeOnlySymbolEdges(db, stmts, imp, resolvedPath, fileNodeId);
    }
    if (!imp.reexport && db) {
        edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeId, resolvedPath, imp);
    }
    return edgesAdded;
}
function buildImportEdges(stmts, relPath, symbols, rootDir, fileNodeId, aliases, db) {
    let edgesAdded = 0;
    for (const imp of symbols.imports) {
        edgesAdded += emitEdgesForImport(stmts, imp, fileNodeId, relPath, rootDir, aliases, db);
    }
    return edgesAdded;
}
function buildImportedNamesMap(symbols, rootDir, relPath, aliases, db) {
    const importedNames = new Map();
    for (const imp of symbols.imports) {
        const resolvedPath = resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
        for (const name of imp.names) {
            const cleanName = name.replace(/^\*\s+as\s+/, '');
            // Mirror full-build's `buildImportedNamesMap`: follow barrel re-exports so
            // `importedNames` maps to the *defining* file, not the barrel. This ensures
            // `computeConfidence` gets `importedFrom === targetFile` and returns 1.0
            // instead of the cross-directory fallback (0.3).
            let targetFile = resolvedPath;
            if (isBarrelFile(db, resolvedPath)) {
                const actual = resolveBarrelTarget(db, resolvedPath, cleanName);
                if (actual)
                    targetFile = actual;
            }
            importedNames.set(cleanName, targetFile);
        }
    }
    return importedNames;
}
const HIERARCHY_SOURCE_KINDS = new Set(['class', 'struct', 'record', 'enum']);
const EXTENDS_TARGET_KINDS = new Set(['class', 'struct', 'trait', 'record']);
const IMPLEMENTS_TARGET_KINDS = new Set(['interface', 'trait', 'class']);
function buildClassHierarchyEdges(stmts, relPath, symbols) {
    let edgesAdded = 0;
    for (const cls of symbols.classes) {
        const sourceRow = stmts.findNodeInFile.all(cls.name, relPath).find((n) => HIERARCHY_SOURCE_KINDS.has(n.kind));
        if (!sourceRow)
            continue;
        if (cls.extends) {
            for (const t of stmts.findNodeByName.all(cls.extends).filter((n) => EXTENDS_TARGET_KINDS.has(n.kind))) {
                stmts.insertEdge.run(sourceRow.id, t.id, 'extends', 1.0, 0);
                edgesAdded++;
            }
        }
        if (cls.implements) {
            for (const t of stmts.findNodeByName.all(cls.implements).filter((n) => IMPLEMENTS_TARGET_KINDS.has(n.kind))) {
                stmts.insertEdge.run(sourceRow.id, t.id, 'implements', 1.0, 0);
                edgesAdded++;
            }
        }
    }
    return edgesAdded;
}
// ── Call edge building ──────────────────────────────────────────────────
function makeIncrementalLookup(db, stmts) {
    return {
        byNameAndFile: (name, file) => stmts.findNodeInFile.all(name, file),
        byName: (name) => stmts.findNodeByName.all(name),
        isBarrel: (file) => isBarrelFile(db, file),
        resolveBarrel: (barrelFile, symbolName) => resolveBarrelTarget(db, barrelFile, symbolName),
        nodeId: (name, kind, file, line) => stmts.getNodeId.get(name, kind, file, line),
    };
}
function buildCallEdges(db, stmts, relPath, symbols, fileNodeRow, importedNames) {
    const rawTM = symbols.typeMap;
    const typeMap = rawTM instanceof Map
        ? rawTM
        : Array.isArray(rawTM) && rawTM.length > 0
            ? new Map(rawTM.map((e) => [
                e.name,
                e.typeName ?? e.type ?? null,
            ]))
            : new Map();
    // Phase 8.3f: seed typeMap[callee::restName] = { type: argName } from
    // objectRestParamBindings × paramBindings, mirroring buildObjectRestParamPostPass.
    // Scoped keys prevent same-name rest-param collisions when two functions in
    // the same file both use `...rest` (#1358). The unscoped key is also seeded
    // when only one callee uses a given rest name, preserving resolution when
    // callerName is null (findCaller couldn't identify the enclosing function).
    if (symbols.objectRestParamBindings?.length && symbols.paramBindings?.length) {
        const restNameCallees = new Map();
        for (const orpb of symbols.objectRestParamBindings) {
            if (!restNameCallees.has(orpb.restName))
                restNameCallees.set(orpb.restName, new Set());
            restNameCallees.get(orpb.restName).add(orpb.callee);
        }
        for (const orpb of symbols.objectRestParamBindings) {
            for (const pb of symbols.paramBindings) {
                if (pb.callee === orpb.callee && pb.argIndex === orpb.argIndex) {
                    const scopedKey = `${orpb.callee}::${orpb.restName}`;
                    if (!typeMap.has(scopedKey)) {
                        typeMap.set(scopedKey, { type: pb.argName, confidence: 0.65 });
                        if (restNameCallees.get(orpb.restName).size === 1 && !typeMap.has(orpb.restName)) {
                            typeMap.set(orpb.restName, { type: pb.argName, confidence: 0.65 });
                        }
                    }
                }
            }
        }
    }
    const seenCallEdges = new Set();
    const lookup = makeIncrementalLookup(db, stmts);
    let edgesAdded = 0;
    for (const call of symbols.calls) {
        if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver))
            continue;
        const caller = findCaller(lookup, call, symbols.definitions, relPath, fileNodeRow);
        const { targets: initialTargets, importedFrom } = resolveCallTargets(lookup, call, relPath, importedNames, typeMap, caller.callerName);
        let targets = initialTargets;
        if (targets.length === 0 && call.receiver === 'this' && caller.callerName != null) {
            const dotIdx = caller.callerName.indexOf('.');
            if (dotIdx > 0) {
                const className = caller.callerName.slice(0, dotIdx);
                const qualifiedName = `${className}.${call.name}`;
                const qualified = lookup
                    .byNameAndFile(qualifiedName, relPath)
                    .filter((n) => n.kind === 'method');
                if (qualified.length > 0) {
                    targets = qualified;
                }
            }
        }
        if (targets.length === 0 &&
            call.receiver === 'this' &&
            caller.callerName != null &&
            symbols.definePropertyReceivers) {
            const receiverVarName = symbols.definePropertyReceivers.get(caller.callerName);
            if (receiverVarName) {
                const typeEntry = typeMap.get(receiverVarName);
                const typeName = typeEntry
                    ? typeof typeEntry === 'string'
                        ? typeEntry
                        : typeEntry.type
                    : null;
                if (typeName) {
                    const qualifiedName = `${typeName}.${call.name}`;
                    const qualified = lookup.byNameAndFile(qualifiedName, relPath);
                    if (qualified.length > 0) {
                        targets = [...qualified];
                    }
                }
                if (targets.length === 0) {
                    // Narrow to function/method kinds only to avoid matching unrelated
                    // variables or classes that share a name in the same file.
                    const sameFile = lookup
                        .byNameAndFile(call.name, relPath)
                        .filter((n) => n.kind === 'function' || n.kind === 'method');
                    if (sameFile.length > 0) {
                        targets = [...sameFile];
                    }
                }
            }
        }
        for (const t of targets) {
            const edgeKey = `${caller.id}|${t.id}`;
            if (t.id !== caller.id && !seenCallEdges.has(edgeKey)) {
                seenCallEdges.add(edgeKey);
                const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
                stmts.insertEdge.run(caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0);
                edgesAdded++;
            }
        }
        if (call.receiver &&
            !BUILTIN_RECEIVERS.has(call.receiver) &&
            call.receiver !== 'this' &&
            call.receiver !== 'self' &&
            call.receiver !== 'super') {
            const recv = resolveReceiverEdge(lookup, { name: call.name, receiver: call.receiver }, caller, relPath, typeMap, seenCallEdges);
            if (recv) {
                stmts.insertEdge.run(recv.callerId, recv.receiverId, 'receiver', recv.confidence, 0);
                edgesAdded++;
            }
        }
    }
    return edgesAdded;
}
// ── Main entry point ────────────────────────────────────────────────────
/** Build the "this file was deleted" result returned by `rebuildFile`. */
function buildDeletionResult(relPath, oldNodes, edgesBefore, oldSymbols, diffSymbols) {
    const symbolDiff = diffSymbols ? diffSymbols(oldSymbols, []) : null;
    return {
        file: relPath,
        nodesAdded: 0,
        nodesRemoved: oldNodes,
        edgesAdded: 0,
        edgesBefore,
        deleted: true,
        event: 'deleted',
        symbolDiff,
        nodesBefore: oldNodes,
        nodesAfter: 0,
    };
}
/** Rebuild all edges originating in the single (just-parsed) target file. */
function rebuildEdgesForTargetFile(db, stmts, relPath, symbols, fileNodeRow, rootDir) {
    const aliases = { baseUrl: null, paths: {} };
    let edgesAdded = buildContainmentEdges(db, stmts, relPath, symbols);
    edgesAdded += rebuildDirContainment(db, stmts, relPath);
    edgesAdded += buildImportEdges(stmts, relPath, symbols, rootDir, fileNodeRow.id, aliases, db);
    const importedNames = buildImportedNamesMap(symbols, rootDir, relPath, aliases, db);
    edgesAdded += buildCallEdges(db, stmts, relPath, symbols, fileNodeRow, importedNames);
    edgesAdded += buildClassHierarchyEdges(stmts, relPath, symbols);
    return edgesAdded;
}
/**
 * Re-parse the reverse-deps and delete their outgoing edges so the cascade
 * can rebuild them. Returns the parsed symbols map together with the total
 * edge count across all deps measured *before* deletion — callers add this
 * to their own `edgesBefore` so the net delta stays correct even when the
 * reverse-dep cascade re-inserts edges.
 */
async function parseReverseDeps(db, rootDir, reverseDeps, stmts, engineOpts, cache) {
    const depSymbols = new Map();
    let reverseDepsEdgesBefore = 0;
    for (const depRelPath of reverseDeps) {
        const symbols_ = await parseReverseDep(rootDir, depRelPath, engineOpts, cache);
        if (symbols_) {
            reverseDepsEdgesBefore += stmts.countEdges.get(depRelPath)?.c ?? 0;
            deleteOutgoingEdges(db, depRelPath);
            depSymbols.set(depRelPath, symbols_);
        }
    }
    return { depSymbols, reverseDepsEdgesBefore };
}
/**
 * Pass 2 of the reverse-dep cascade: now that the changed file's `reexports`
 * edges exist, resolve barrel imports for every reverse-dep so transitive
 * call edges through the barrel still find their targets.
 */
function emitBarrelImportEdgesForReverseDeps(db, stmts, depSymbols, rootDir) {
    let edgesAdded = 0;
    for (const [depRelPath, symbols_] of depSymbols) {
        const fileNodeRow_ = stmts.getNodeId.get(depRelPath, 'file', depRelPath, 0);
        if (!fileNodeRow_)
            continue;
        const aliases_ = { baseUrl: null, paths: {} };
        for (const imp of symbols_.imports) {
            if (imp.reexport)
                continue;
            const resolvedPath = resolveImportPath(path.join(rootDir, depRelPath), imp.source, rootDir, aliases_);
            edgesAdded += resolveBarrelImportEdges(db, stmts, fileNodeRow_.id, resolvedPath, imp);
        }
    }
    return edgesAdded;
}
/**
 * Two-pass reverse-dep cascade:
 *   1. Rebuild direct edges (creating `reexports` edges for barrels).
 *   2. Add barrel import edges (which need `reexports` edges to exist).
 * Returns both the gross edges-added count and the pre-deletion edge count
 * for all reverse deps so callers can compute a true net delta.
 */
async function runReverseDepCascade(db, rootDir, reverseDeps, stmts, engineOpts, cache) {
    const { depSymbols, reverseDepsEdgesBefore } = await parseReverseDeps(db, rootDir, reverseDeps, stmts, engineOpts, cache);
    let edgesAdded = 0;
    // Pass 1: direct edges only (no barrel resolution) — creates reexports edges
    for (const [depRelPath, symbols_] of depSymbols) {
        edgesAdded += rebuildReverseDepEdges(db, rootDir, depRelPath, symbols_, stmts, true);
    }
    // Pass 2: add barrel import edges (reexports edges now exist)
    edgesAdded += emitBarrelImportEdgesForReverseDeps(db, stmts, depSymbols, rootDir);
    return { edgesAdded, reverseDepsEdgesBefore };
}
/**
 * Parse a single file and update the database incrementally.
 */
export async function rebuildFile(db, rootDir, filePath, stmts, engineOpts, cache, options = {}) {
    const { diffSymbols } = options;
    const relPath = normalizePath(path.relative(rootDir, filePath));
    const oldNodes = stmts.countNodes.get(relPath)?.c || 0;
    const edgesBefore = stmts.countEdges.get(relPath)?.c || 0;
    const oldSymbols = diffSymbols ? stmts.listSymbols.all(relPath) : [];
    // Find reverse-deps BEFORE purging (edges still reference the old nodes)
    const reverseDeps = findReverseDeps(db, relPath);
    // Purge ancillary tables (incl. embeddings), edges, and nodes in one pass.
    // Embeddings must be purged before nodes — better-sqlite3 enforces foreign
    // keys by default, and `embeddings.node_id` references `nodes.id`. Issue #1176.
    // `purgeHashes: false` preserves file_hashes for the next incremental build.
    purgeFileData(db, relPath, { purgeHashes: false });
    if (!fs.existsSync(filePath)) {
        if (cache)
            cache.remove(filePath);
        return buildDeletionResult(relPath, oldNodes, edgesBefore, oldSymbols, diffSymbols);
    }
    let code;
    try {
        code = readFileSafe(filePath);
    }
    catch (err) {
        warn(`Cannot read ${relPath}: ${err.message}`);
        return null;
    }
    const symbols = await parseFileIncremental(cache, filePath, code, engineOpts);
    if (!symbols)
        return null;
    insertFileNodes(stmts, relPath, symbols);
    const newNodes = stmts.countNodes.get(relPath)?.c || 0;
    const newSymbols = diffSymbols ? stmts.listSymbols.all(relPath) : [];
    const fileNodeRow = stmts.getNodeId.get(relPath, 'file', relPath, 0);
    if (!fileNodeRow)
        return {
            file: relPath,
            nodesAdded: newNodes,
            nodesRemoved: oldNodes,
            edgesAdded: 0,
            edgesBefore,
        };
    let edgesAdded = rebuildEdgesForTargetFile(db, stmts, relPath, symbols, fileNodeRow, rootDir);
    const { edgesAdded: cascadeEdges, reverseDepsEdgesBefore } = await runReverseDepCascade(db, rootDir, reverseDeps, stmts, engineOpts, cache);
    edgesAdded += cascadeEdges;
    // Include pre-deletion edge counts from reverse deps so the net delta
    // (edgesAdded - edgesBefore) is correct even when the cascade re-inserts
    // their edges unchanged.
    const totalEdgesBefore = edgesBefore + reverseDepsEdgesBefore;
    const symbolDiff = diffSymbols ? diffSymbols(oldSymbols, newSymbols) : null;
    const event = oldNodes === 0 ? 'added' : 'modified';
    return {
        file: relPath,
        nodesAdded: newNodes,
        nodesRemoved: oldNodes,
        edgesAdded,
        edgesBefore: totalEdgesBefore,
        deleted: false,
        event,
        symbolDiff,
        nodesBefore: oldNodes,
        nodesAfter: newNodes,
    };
}
//# sourceMappingURL=incremental.js.map