/**
 * Stage: collectFiles
 *
 * Collects all source files to process. Handles both normal and scoped rebuilds.
 * For incremental builds with a valid journal, reconstructs the file list from
 * the DB's file_hashes table + journal deltas, skipping the filesystem scan.
 */
import fs from 'node:fs';
import path from 'node:path';
import { performance } from 'node:perf_hooks';
import { debug, info } from '../../../../infrastructure/logger.js';
import { normalizePath } from '../../../../shared/constants.js';
import { compileGlobs } from '../../../../shared/globs.js';
import { readJournal } from '../../journal.js';
import { collectFiles as collectFilesUtil, passesIncludeExclude } from '../helpers.js';
/**
 * Reconstruct allFiles from DB file_hashes + journal deltas.
 * Returns null when the fast path isn't applicable (first build, no journal, etc).
 */
function tryFastCollect(ctx) {
    const { db, rootDir, config } = ctx;
    const useNative = ctx.engineName === 'native' && !!ctx.nativeDb?.getCollectFilesData;
    // 1. Check that file_hashes table exists and has entries
    let dbFileCount;
    let dbFiles;
    if (useNative) {
        const data = ctx.nativeDb.getCollectFilesData();
        dbFileCount = data.count;
        dbFiles = data.files;
    }
    else {
        try {
            dbFileCount = db.prepare('SELECT COUNT(*) as c FROM file_hashes').get().c;
        }
        catch {
            return null;
        }
        dbFiles = []; // deferred — loaded below only if needed
    }
    if (dbFileCount === 0)
        return null;
    // 2. Read the journal — only use fast path when journal has entries,
    // proving the watcher was active and tracking changes. An empty-but-valid
    // journal (no watcher) could miss file deletions.
    const journal = readJournal(rootDir);
    if (!journal.valid)
        return null;
    const hasEntries = (journal.changed && journal.changed.length > 0) ||
        (journal.removed && journal.removed.length > 0);
    if (!hasEntries)
        return null;
    // 3. Load existing file list from file_hashes (relative paths)
    if (!useNative) {
        dbFiles = db.prepare('SELECT file FROM file_hashes').all().map((r) => r.file);
    }
    // 4. Apply journal deltas: remove deleted files, add new/changed files
    const fileSet = new Set(dbFiles);
    if (journal.removed) {
        for (const removed of journal.removed) {
            fileSet.delete(removed);
        }
    }
    if (journal.changed) {
        for (const changed of journal.changed) {
            fileSet.add(changed);
        }
    }
    // 5. Convert to absolute paths and compute directories, honoring
    //    config.include / config.exclude globs so incremental builds reflect
    //    config changes (paths from the DB were collected under older config).
    const includeRegexes = compileGlobs(config?.include);
    const excludeRegexes = compileGlobs(config?.exclude);
    const hasGlobFilters = includeRegexes.length > 0 || excludeRegexes.length > 0;
    const files = [];
    const directories = new Set();
    for (const relPath of fileSet) {
        if (hasGlobFilters) {
            const normRel = normalizePath(relPath);
            if (!passesIncludeExclude(normRel, includeRegexes, excludeRegexes))
                continue;
        }
        const absPath = path.join(rootDir, relPath);
        files.push(absPath);
        directories.add(path.dirname(absPath));
    }
    debug(`collectFiles fast path: ${dbFiles.length} from DB, journal: +${journal.changed?.length ?? 0}/-${journal.removed?.length ?? 0} → ${files.length} files`);
    return { files, directories };
}
export async function collectFiles(ctx) {
    const { rootDir, config, opts } = ctx;
    // Skip when the JS-side fast-skip pre-flight (#1054) already populated the
    // file list and changes were detected, causing fallthrough to the native
    // orchestrator and then to runPipelineStages. Avoids redoing the filesystem
    // walk on the non-skip path (~8ms on 473 files). On pre-flight failure the
    // caller resets ctx.allFiles so this guard correctly falls through.
    if (!opts.scope && ctx.allFiles?.length && ctx.discoveredDirs?.size) {
        return;
    }
    if (opts.scope) {
        // Scoped rebuild: rebuild only specified files.
        //
        // Timer only wraps the filesystem-walk portion (existence checks + file
        // list construction). Change-detection outputs (parseChanges, removed,
        // isFullBuild) are attributed to detectMs for semantic consistency with
        // the non-scoped path, even though this stage computes them.
        const start = performance.now();
        const scopedFiles = opts.scope.map((f) => normalizePath(f));
        const existing = [];
        const missing = [];
        try {
            for (const rel of scopedFiles) {
                const abs = path.join(rootDir, rel);
                if (fs.existsSync(abs)) {
                    existing.push({ file: abs, relPath: rel });
                }
                else {
                    missing.push(rel);
                }
            }
            ctx.allFiles = existing.map((e) => e.file);
            ctx.discoveredDirs = new Set(existing.map((e) => path.dirname(e.file)));
        }
        finally {
            ctx.timing.collectMs = performance.now() - start;
        }
        // Change-detection outputs — timed under detectMs for semantic parity.
        const detectStart = performance.now();
        ctx.parseChanges = existing;
        ctx.metadataUpdates = [];
        ctx.removed = missing;
        ctx.isFullBuild = false;
        ctx.timing.detectMs = (ctx.timing.detectMs ?? 0) + (performance.now() - detectStart);
        info(`Scoped rebuild: ${existing.length} files to rebuild, ${missing.length} to purge`);
        return;
    }
    const start = performance.now();
    try {
        // Incremental fast path: reconstruct file list from DB + journal deltas
        // instead of full recursive filesystem scan (~8ms savings on 473 files).
        if (ctx.incremental && !ctx.forceFullRebuild) {
            const fast = tryFastCollect(ctx);
            if (fast) {
                ctx.allFiles = fast.files;
                ctx.discoveredDirs = fast.directories;
                info(`Found ${ctx.allFiles.length} files (cached)`);
                return;
            }
        }
        const collected = collectFilesUtil(rootDir, [], config, new Set());
        ctx.allFiles = collected.files;
        ctx.discoveredDirs = collected.directories;
        info(`Found ${ctx.allFiles.length} files to parse`);
    }
    finally {
        ctx.timing.collectMs = performance.now() - start;
    }
}
//# sourceMappingURL=collect-files.js.map