import fg from 'fast-glob';
import path from 'node:path';
import { stat } from 'node:fs/promises';
import { z } from 'zod';
import { buildExcludeDirGlobs, createIgnoreMatcher, normalizeRelativePath, OFFICE_EXTENSIONS } from './utils.js';
import { FileEntrySchema, SkippedEntrySchema } from './types.js';
export const FindFilesOptionsSchema = z.object({
    includeExt: z.string().optional(),
    excludeExt: z.string().optional(),
    excludeDir: z.array(z.string()).optional(),
    noGitignore: z.boolean().optional(),
    ignorePatterns: z.array(z.string()).optional(),
    overlayIgnorePatterns: z.array(z.string()).optional(),
    largeFileLimit: z.number().optional(),
});
export const FindFilesResultSchema = z.object({
    files: z.array(FileEntrySchema),
    skipped: z.array(SkippedEntrySchema),
});
export async function findFiles(directories, options = {}) {
    const { includeExt, excludeExt, excludeDir = ['node_modules', '.git'], noGitignore = false, largeFileLimit = 50, } = options;
    let extensions = OFFICE_EXTENSIONS;
    if (includeExt) {
        extensions = includeExt.split(',').map(e => e.trim().toLowerCase());
    }
    if (excludeExt) {
        const excluded = new Set(excludeExt.split(',').map(e => e.trim().toLowerCase()));
        extensions = extensions.filter(e => !excluded.has(e));
    }
    if (extensions.length === 0) {
        return { files: [], skipped: [] };
    }
    const pattern = extensions.length === 1 ? `**/*.${extensions[0]}` : `**/*.{${extensions.join(',')}}`;
    const dirs = directories.length > 0 ? directories : [process.cwd()];
    const allPaths = [];
    // Overlapping input directories (one nested in another, or the same directory
    // passed twice) can resolve to the same absolute file path, so results are
    // deduplicated by absolute path to avoid counting a file more than once.
    const seenPaths = new Set();
    for (const dir of dirs) {
        const resolvedDir = path.resolve(dir);
        const ignoreMatcher = await createIgnoreMatcher(resolvedDir, {
            excludeDir,
            noGitignore,
            ignorePatterns: options.ignorePatterns,
            overlayIgnorePatterns: options.overlayIgnorePatterns,
        });
        const found = await fg(pattern, {
            cwd: resolvedDir,
            absolute: true,
            ignore: buildExcludeDirGlobs(excludeDir),
            dot: false,
            onlyFiles: true,
            followSymbolicLinks: false,
            // Extensions are normalized to lowercase; match case-insensitively so
            // files with upper/mixed-case extensions (e.g. Contract.PDF) are found.
            caseSensitiveMatch: false,
        });
        for (const filePath of found) {
            if (seenPaths.has(filePath))
                continue;
            const relativePath = normalizeRelativePath(path.relative(resolvedDir, filePath));
            if (ignoreMatcher.ignores(relativePath))
                continue;
            seenPaths.add(filePath);
            allPaths.push(filePath);
        }
    }
    const limitBytes = largeFileLimit * 1024 * 1024;
    const files = [];
    const skipped = [];
    // Batch stat calls for better throughput on large directories
    const BATCH_SIZE = 50;
    for (let i = 0; i < allPaths.length; i += BATCH_SIZE) {
        const batch = allPaths.slice(i, i + BATCH_SIZE);
        const results = await Promise.allSettled(batch.map(p => stat(p)));
        for (let j = 0; j < results.length; j++) {
            const p = batch[j];
            const r = results[j];
            if (r.status === 'rejected') {
                const err = r.reason;
                skipped.push({ path: p, reason: err.code === 'EACCES' ? 'Permission denied' : err.message, size: 0 });
            }
            else if (r.value.size > limitBytes) {
                skipped.push({ path: p, reason: `Exceeds ${largeFileLimit}MB limit`, size: r.value.size });
            }
            else {
                files.push({ path: p, size: r.value.size });
            }
        }
    }
    return { files, skipped };
}
//# sourceMappingURL=walker.js.map