/**
 * ProgramContext type definition and package-resolution helpers.
 *
 * Contains the ProgramContext type and helper functions for resolving
 * package roots, package info, and CLR metadata discovery.
 */
import * as fs from "node:fs";
import * as path from "path";
export const getCommonAncestor = (leftPath, rightPath) => {
    const leftResolved = path.resolve(leftPath);
    const rightResolved = path.resolve(rightPath);
    const leftParts = leftResolved.split(path.sep).filter(Boolean);
    const rightParts = rightResolved.split(path.sep).filter(Boolean);
    const shared = [];
    for (let index = 0; index < Math.min(leftParts.length, rightParts.length); index += 1) {
        const leftPart = leftParts[index];
        const rightPart = rightParts[index];
        if (leftPart === undefined || rightPart === undefined)
            break;
        if (leftPart !== rightPart)
            break;
        shared.push(leftPart);
    }
    if (shared.length === 0) {
        return path.parse(leftResolved).root;
    }
    return path.join(path.parse(leftResolved).root, ...shared);
};
export const findPackageRootForFile = (fileName, projectRootResolved, packageRootCache) => {
    const normalized = fileName.replace(/\\/g, "/");
    if (packageRootCache.has(normalized))
        return packageRootCache.get(normalized);
    const resolvedFileName = path.resolve(fileName);
    const searchFloor = resolvedFileName.startsWith(`${projectRootResolved}${path.sep}`)
        ? projectRootResolved
        : getCommonAncestor(resolvedFileName, projectRootResolved);
    let dir = path.dirname(resolvedFileName);
    while (true) {
        const pkgJson = path.join(dir, "package.json");
        if (fs.existsSync(pkgJson)) {
            packageRootCache.set(normalized, dir);
            return dir;
        }
        if (dir === searchFloor) {
            packageRootCache.set(normalized, undefined);
            return undefined;
        }
        const parent = path.dirname(dir);
        if (parent === dir) {
            packageRootCache.set(normalized, undefined);
            return undefined;
        }
        dir = parent;
    }
};
export const getPackageInfo = (pkgRoot, packageInfoCache) => {
    if (packageInfoCache.has(pkgRoot))
        return packageInfoCache.get(pkgRoot);
    try {
        const raw = fs.readFileSync(path.join(pkgRoot, "package.json"), "utf-8");
        const parsed = JSON.parse(raw);
        if (typeof parsed !== "object" || parsed === null) {
            packageInfoCache.set(pkgRoot, undefined);
            return undefined;
        }
        const name = typeof parsed.name === "string"
            ? parsed.name
            : undefined;
        const keywordsRaw = parsed.keywords;
        const keywords = Array.isArray(keywordsRaw)
            ? keywordsRaw.filter((k) => typeof k === "string")
            : [];
        const peerDepsRaw = parsed
            .peerDependencies;
        const peerDependencies = peerDepsRaw &&
            typeof peerDepsRaw === "object" &&
            !Array.isArray(peerDepsRaw)
            ? Object.fromEntries(Object.entries(peerDepsRaw).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string"))
            : {};
        const info = { name, keywords, peerDependencies };
        packageInfoCache.set(pkgRoot, info);
        return info;
    }
    catch {
        packageInfoCache.set(pkgRoot, undefined);
        return undefined;
    }
};
export const packageHasClrMetadata = (pkgRoot, _packageInfoCache, packageHasMetadataCache) => {
    const cached = packageHasMetadataCache.get(pkgRoot);
    if (cached !== undefined)
        return cached;
    let found = false;
    const stack = [pkgRoot];
    while (stack.length > 0 && !found) {
        const currentDir = stack.pop();
        if (!currentDir)
            continue;
        try {
            for (const entry of fs.readdirSync(currentDir, {
                withFileTypes: true,
            })) {
                const fullPath = path.join(currentDir, entry.name);
                if (entry.isFile() && entry.name === "bindings.json") {
                    found = true;
                    break;
                }
                if (entry.isDirectory()) {
                    // Avoid descending into nested node_modules if present.
                    if (entry.name === "node_modules")
                        continue;
                    stack.push(fullPath);
                }
            }
        }
        catch {
            // Ignore unreadable directories.
        }
    }
    packageHasMetadataCache.set(pkgRoot, found);
    return found;
};
//# sourceMappingURL=program-context-types.js.map