import { spawnSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
import { basename, dirname, join, parse, resolve, sep } from "node:path";
import { resolveConfigLevels } from "../config/paths.js";
import { loadConfigWithDiagnostics } from "../config/schema.js";
import { loadModelResolver } from "../models/catalog.js";
const CHILD_AGENTS_RG_TIMEOUT_MS = 2000;
const CHILD_AGENTS_WALK_TIMEOUT_MS = 1000;
const CHILD_AGENTS_WALK_MAX_DIRS = 10_000;
const DEFAULT_IGNORED_CHILD_DIRS = new Set([
    ".cache",
    ".git",
    ".hg",
    ".jj",
    ".next",
    ".nuxt",
    ".parcel-cache",
    ".svn",
    ".turbo",
    ".venv",
    ".vite",
    "__pycache__",
    "build",
    "coverage",
    "dist",
    "node_modules",
    "out",
    "target",
    "vendor",
    "venv",
]);
function isSameOrParentPath(parent, child) {
    if (parent === child)
        return true;
    const root = parse(parent).root;
    if (parent === root)
        return child.startsWith(root);
    return child.startsWith(parent + sep);
}
function resolveAgentContextPath(args) {
    const resolved = resolve(args.path);
    if (basename(resolved) !== "AGENTS.md")
        return null;
    let realPath;
    try {
        realPath = realpathSync(resolved);
    }
    catch {
        return null;
    }
    if (basename(realPath) !== "AGENTS.md")
        return null;
    try {
        const stat = statSync(realPath);
        if (!stat.isFile())
            return null;
    }
    catch {
        return null;
    }
    const cwdAbs = resolve(args.cwd);
    const homeAbs = resolve(args.home);
    let cwdReal = cwdAbs;
    let homeReal = homeAbs;
    try {
        cwdReal = realpathSync(cwdAbs);
    }
    catch {
        // fall back to cwdAbs
    }
    try {
        homeReal = realpathSync(homeAbs);
    }
    catch {
        // fall back to homeAbs
    }
    const withinHome = cwdReal === homeReal || cwdReal.startsWith(homeReal + sep);
    if (withinHome && !isSameOrParentPath(homeReal, realPath))
        return null;
    if (!isAgentContextPathInScope(realPath, cwdReal))
        return null;
    return resolved;
}
export function isAgentContextPathInScope(filePath, cwd) {
    const cwdAbs = resolve(cwd);
    const fileDir = dirname(resolve(filePath));
    return isSameOrParentPath(fileDir, cwdAbs) || isSameOrParentPath(cwdAbs, fileDir);
}
function createAgentsConfigDeps(cwd, home) {
    return {
        fs: {
            readFile: (path) => readFileSync(path, "utf-8"),
            exists: (path) => existsSync(path),
            listDir: (path) => readdirSync(path),
            stat: (path) => statSync(path),
        },
        env: {
            getEnv: () => ({}),
            cwd: () => cwd,
            home: () => home,
        },
    };
}
export function findAgentsFilesFromCwdToHome(cwd, home) {
    const cwdAbs = resolve(cwd);
    const homeAbs = resolve(home);
    const withinHome = cwdAbs === homeAbs || cwdAbs.startsWith(homeAbs + sep);
    const stopAbs = withinHome ? homeAbs : parse(cwdAbs).root;
    const found = [];
    let dir = cwdAbs;
    // Closest-first order: cwd, parent, ..., home/root.
    while (true) {
        const candidate = join(dir, "AGENTS.md");
        if (existsSync(candidate)) {
            const resolved = resolveAgentContextPath({ path: candidate, cwd: cwdAbs, home });
            if (resolved) {
                found.push(resolved);
            }
        }
        if (dir === stopAbs)
            break;
        const parent = dirname(dir);
        if (parent === dir)
            break;
        dir = parent;
    }
    return found;
}
function buildIgnoredChildDirGlobs() {
    return [...DEFAULT_IGNORED_CHILD_DIRS].flatMap((dir) => ["--glob", `!${dir}/`]);
}
function findChildAgentsFilesWithRipgrep(cwd, home) {
    const cwdAbs = resolve(cwd);
    const result = spawnSync("rg", [
        "--files",
        "--hidden",
        "--no-ignore",
        "--glob",
        "**/AGENTS.md",
        ...buildIgnoredChildDirGlobs(),
    ], {
        cwd: cwdAbs,
        encoding: "utf8",
        maxBuffer: 10 * 1024 * 1024,
        stdio: ["ignore", "pipe", "ignore"],
        timeout: CHILD_AGENTS_RG_TIMEOUT_MS,
    });
    if (result.error) {
        const code = "code" in result.error ? result.error.code : undefined;
        return code === "ETIMEDOUT" ? [] : null;
    }
    if (result.status !== 0 && result.status !== 1)
        return null;
    if (typeof result.stdout !== "string")
        return null;
    return result.stdout
        .split("\n")
        .map((line) => line.trim())
        .filter(Boolean)
        .sort((a, b) => a.localeCompare(b))
        .map((filePath) => resolve(cwdAbs, filePath))
        .filter((filePath) => dirname(filePath) !== cwdAbs)
        .flatMap((filePath) => {
        const resolved = resolveAgentContextPath({ path: filePath, cwd: cwdAbs, home });
        return resolved ? [resolved] : [];
    });
}
function findChildAgentsFilesByWalking(cwd, home) {
    const cwdAbs = resolve(cwd);
    const found = [];
    const seenDirs = new Set();
    const startedAt = Date.now();
    let visitedDirs = 0;
    let bailed = false;
    const shouldBail = () => {
        if (bailed)
            return true;
        if (visitedDirs >= CHILD_AGENTS_WALK_MAX_DIRS) {
            bailed = true;
            return true;
        }
        if (Date.now() - startedAt >= CHILD_AGENTS_WALK_TIMEOUT_MS) {
            bailed = true;
            return true;
        }
        return false;
    };
    const walk = (dir) => {
        if (shouldBail())
            return;
        let dirReal = dir;
        try {
            dirReal = realpathSync(dir);
        }
        catch {
            return;
        }
        if (seenDirs.has(dirReal))
            return;
        seenDirs.add(dirReal);
        visitedDirs += 1;
        let entries;
        try {
            entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
        }
        catch {
            return;
        }
        for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
            if (shouldBail())
                return;
            if (!entry.isDirectory() && !entry.isSymbolicLink())
                continue;
            if (DEFAULT_IGNORED_CHILD_DIRS.has(entry.name))
                continue;
            const childDir = join(dir, entry.name);
            let isDirectory = entry.isDirectory();
            if (entry.isSymbolicLink()) {
                try {
                    isDirectory = statSync(childDir).isDirectory();
                }
                catch {
                    isDirectory = false;
                }
            }
            if (!isDirectory)
                continue;
            const candidate = join(childDir, "AGENTS.md");
            if (existsSync(candidate)) {
                const resolved = resolveAgentContextPath({ path: candidate, cwd: cwdAbs, home });
                if (resolved) {
                    found.push(resolved);
                }
            }
            walk(childDir);
        }
    };
    walk(cwdAbs);
    return found;
}
export function findChildAgentsFiles(cwd, home) {
    return findChildAgentsFilesWithRipgrep(cwd, home) ?? findChildAgentsFilesByWalking(cwd, home);
}
function findAdditionalAgentsFilesFromConfigsDetailed(args) {
    const deps = createAgentsConfigDeps(args.cwd, args.home);
    const levels = resolveConfigLevels(deps, { cwd: args.cwd });
    const modelResolver = loadModelResolver({ deps, levels });
    const configResult = loadConfigWithDiagnostics(deps, { levels, modelResolver });
    const files = [];
    const errors = [...configResult.errors];
    for (const pathRaw of configResult.config.agentContextFiles ?? []) {
        const resolved = resolveAgentContextPath({ path: pathRaw, cwd: args.cwd, home: args.home });
        if (resolved) {
            files.push(resolved);
        }
    }
    return { files, errors };
}
export function findAgentsFilesInScopeDetailed(cwd, home) {
    const base = findAgentsFilesFromCwdToHome(cwd, home);
    const extra = findAdditionalAgentsFilesFromConfigsDetailed({ cwd, home });
    const seen = new Set();
    const files = [];
    for (const file of [...base, ...extra.files]) {
        let dedupeKey = file;
        try {
            dedupeKey = realpathSync(file);
        }
        catch {
            // fallback to the resolved path
        }
        if (seen.has(dedupeKey))
            continue;
        seen.add(dedupeKey);
        files.push(file);
    }
    return { files, errors: extra.errors };
}
export function findAgentsFilesInScope(cwd, home) {
    return findAgentsFilesInScopeDetailed(cwd, home).files;
}
//# sourceMappingURL=agents_files.js.map