import fg from 'fast-glob';
import { lstat, stat } from 'node:fs/promises';
import path from 'node:path';
import { z } from 'zod';
import { CODE_EXTENSIONS, normalizePath } from './languages.js';
import { abortIfNeeded, buildExcludeDirGlobs, createIgnoreMatcher, DEFAULT_EXCLUDE_DIRS, mapWithConcurrency, } from '../utils.js';
export { DEFAULT_EXCLUDE_DIRS } from '../utils.js';
export const DiscoverCodeOptionsSchema = z.object({
    excludeDir: z.array(z.string()).optional(),
    noGitignore: z.boolean().optional(),
    ignorePatterns: z.array(z.string()).optional(),
    overlayIgnorePatterns: z.array(z.string()).optional(),
});
export async function discoverCodeFiles(repoRoot, options = {}) {
    const resolvedRoot = path.resolve(repoRoot);
    const excludeDir = options.excludeDir ?? [...DEFAULT_EXCLUDE_DIRS];
    abortIfNeeded(options.signal);
    const ignoreMatcher = await createIgnoreMatcher(resolvedRoot, {
        excludeDir,
        noGitignore: options.noGitignore,
        ignorePatterns: options.ignorePatterns,
        overlayIgnorePatterns: options.overlayIgnorePatterns,
    });
    const extensions = options.extensions ?? CODE_EXTENSIONS;
    if (extensions.length === 0)
        return [];
    const pattern = extensions.length === 1 ? `**/*.${extensions[0]}` : `**/*.{${extensions.join(',')}}`;
    const discovered = await fg(pattern, {
        cwd: resolvedRoot,
        absolute: true,
        onlyFiles: true,
        dot: false,
        followSymbolicLinks: false,
        ignore: buildExcludeDirGlobs(excludeDir),
    });
    abortIfNeeded(options.signal);
    const statted = await mapWithConcurrency(discovered.sort(), 32, async (filePath) => {
        try {
            const fileStat = await stat(filePath);
            const relativePath = normalizePath(path.relative(resolvedRoot, filePath));
            return fileStat.isFile() && !ignoreMatcher.ignores(relativePath) ? filePath : null;
        }
        catch {
            // Ignore files that disappear during discovery.
            return null;
        }
    }, options.signal);
    return statted.filter((filePath) => filePath !== null);
}
function hasContiguousSegments(haystack, needle) {
    for (let start = 0; start + needle.length <= haystack.length; start++) {
        if (needle.every((segment, offset) => haystack[start + offset] === segment))
            return true;
    }
    return false;
}
/**
 * Single-path equivalent of the fast-glob options discoverCodeFiles relies
 * on: `dot: false` (fast-glob rejects a dot-segment anywhere in the path),
 * `ignore: buildExcludeDirGlobs(excludeDir)` (`**\/<entry>` + `**\/<entry>/**`,
 * i.e. the entry's segments appearing as a contiguous run anywhere), and
 * `followSymbolicLinks: false` (neither the file nor any ancestor directory
 * below `repoRoot` may be a symlink). Kept next to discoverCodeFiles so the
 * watcher fast path (classifyChangedFiles) cannot drift from discovery.
 * Existence is deliberately not checked — absent path components cannot veto,
 * so deletions of previously indexed files still pass through.
 */
export async function wouldDiscoverPath(repoRoot, absolutePath, excludeDir = DEFAULT_EXCLUDE_DIRS) {
    const resolvedRoot = path.resolve(repoRoot);
    const relativePath = normalizePath(path.relative(resolvedRoot, absolutePath));
    if (!relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath))
        return false;
    const segments = relativePath.split('/');
    if (segments.some(segment => segment.startsWith('.')))
        return false;
    for (const dir of excludeDir) {
        const entrySegments = normalizePath(dir)
            .split('/')
            .filter(segment => segment.length > 0);
        if (entrySegments.length === 0)
            continue;
        if (hasContiguousSegments(segments, entrySegments))
            return false;
    }
    let current = resolvedRoot;
    for (const segment of segments) {
        current = path.join(current, segment);
        try {
            if ((await lstat(current)).isSymbolicLink())
                return false;
        }
        catch {
            // Absent components cannot be symlinks; existence is the caller's concern.
        }
    }
    return true;
}
//# sourceMappingURL=discover.js.map