"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NpmNodeModulesCollector = void 0;
const builder_util_1 = require("builder-util");
const path = require("path");
const nodeModulesCollector_1 = require("./nodeModulesCollector");
const packageManager_1 = require("./packageManager");
class NpmNodeModulesCollector extends nodeModulesCollector_1.NodeModulesCollector {
    constructor() {
        super(...arguments);
        this.installOptions = {
            manager: packageManager_1.PM.NPM,
            lockfile: "package-lock.json",
        };
    }
    getArgs() {
        return ["list", "-a", "--include", "prod", "--include", "optional", "--omit", "dev", "--json", "--long", "--silent"];
    }
    async getDependenciesTree(pm) {
        try {
            return await super.getDependenciesTree(pm);
        }
        catch (error) {
            builder_util_1.log.info({ pm: this.installOptions.manager, parser: packageManager_1.PM.NPM, error: error.message }, "unable to process dependency tree, falling back to using manual node_modules traversal");
        }
        // node_modules linker fallback. (Slower due to system ops, so we only use it as a fallback) [e.g. corepack env will not allow npm CLI to extract tree]
        return this.buildNodeModulesTreeManually(this.rootDir);
    }
    async collectAllDependencies(tree) {
        for (const [, value] of Object.entries(tree.dependencies || {})) {
            const { _dependencies = {}, dependencies = {} } = value;
            const isDuplicateDep = Object.keys(_dependencies).length > 0 && Object.keys(dependencies).length === 0;
            if (isDuplicateDep) {
                continue;
            }
            this.allDependencies.set(this.packageVersionString(value), value);
            await this.collectAllDependencies(value);
        }
    }
    async extractProductionDependencyGraph(tree, dependencyId) {
        var _a, _b;
        if (this.productionGraph[dependencyId]) {
            return;
        }
        const { _dependencies: prodDependencies = {}, dependencies = {} } = tree;
        const isDuplicateDep = Object.keys(prodDependencies).length > 0 && Object.keys(dependencies).length === 0;
        const resolvedDeps = isDuplicateDep ? ((_b = (_a = this.allDependencies.get(dependencyId)) === null || _a === void 0 ? void 0 : _a.dependencies) !== null && _b !== void 0 ? _b : {}) : dependencies;
        // Initialize with empty dependencies array first to mark this dependency as "in progress"
        // After initialization, if there are libraries with the same name+version later, they will not be searched recursively again
        // This will prevents infinite loops when circular dependencies are encountered.
        this.productionGraph[dependencyId] = { dependencies: [] };
        const productionDeps = Object.entries(resolvedDeps)
            .filter(([packageName]) => this.isProdDependency(packageName, tree))
            .map(async ([, dependency]) => {
            const childDependencyId = this.packageVersionString(dependency);
            await this.extractProductionDependencyGraph(dependency, childDependencyId);
            return childDependencyId;
        });
        const collectedDependencies = await Promise.all(productionDeps);
        this.productionGraph[dependencyId] = { dependencies: collectedDependencies };
    }
    isProdDependency(packageName, tree) {
        var _a;
        return ((_a = tree._dependencies) === null || _a === void 0 ? void 0 : _a[packageName]) != null;
    }
    /**
     * Builds a dependency tree using only package.json dependencies and optionalDependencies.
     * This skips devDependencies and uses Node.js module resolution (require.resolve).
     */
    buildNodeModulesTreeManually(baseDir) {
        // Track visited packages by their resolved path to prevent infinite loops
        const visited = new Set();
        /**
         * Recursively builds dependency tree starting from a package directory.
         */
        const buildFromPackage = async (packageDir) => {
            const pkgPath = path.join(packageDir, "package.json");
            builder_util_1.log.debug({ pkgPath }, "building dependency node from package.json");
            if (!(await this.existsMemoized(pkgPath))) {
                throw new Error(`package.json not found at ${pkgPath}`);
            }
            // Read package.json using memoized require for consistency with Node.js module system
            const pkg = await this.readJsonMemoized(pkgPath);
            const resolvedPackageDir = await this.resolvePath(packageDir);
            // Use resolved path as the unique identifier to prevent circular dependencies
            if (visited.has(resolvedPackageDir)) {
                builder_util_1.log.debug({ name: pkg.name, version: pkg.version, path: resolvedPackageDir }, "skipping already visited package");
                return {
                    name: pkg.name,
                    version: pkg.version,
                    path: resolvedPackageDir,
                };
            }
            visited.add(resolvedPackageDir);
            const prodDeps = {};
            const allProdDepNames = {
                ...pkg.dependencies,
                ...pkg.optionalDependencies,
            };
            // Process all production and optional dependencies
            for (const [depName, depVersion] of Object.entries(allProdDepNames)) {
                try {
                    // Resolve the dependency using Node.js module resolution from this package's directory
                    const resolvedPackage = await this.resolvePackage(depName, packageDir);
                    if (!resolvedPackage) {
                        builder_util_1.log.warn({ package: pkg.name, dependency: depName, version: depVersion }, "dependency not found");
                    }
                    const resolvedDepPath = await this.resolvePath(resolvedPackage.packageDir);
                    // Skip if this dependency resolves to the base directory or any parent we're already processing
                    if (resolvedDepPath === resolvedPackageDir || resolvedDepPath === (await this.resolvePath(baseDir))) {
                        builder_util_1.log.debug({ package: pkg.name, dependency: depName, resolvedPath: resolvedDepPath }, "skipping self-referential dependency");
                        continue;
                    }
                    builder_util_1.log.debug({ package: pkg.name, dependency: depName, ...resolvedPackage, resolvedDepPath }, "processing production dependency");
                    // Recursively build the dependency tree for this dependency
                    prodDeps[depName] = await buildFromPackage(resolvedDepPath);
                }
                catch (error) {
                    builder_util_1.log.warn({ package: pkg.name, dependency: depName, error: error.message }, "failed to process dependency, skipping");
                }
            }
            return {
                name: pkg.name,
                version: pkg.version,
                path: resolvedPackageDir,
                dependencies: Object.keys(prodDeps).length > 0 ? prodDeps : undefined,
                optionalDependencies: pkg.optionalDependencies,
            };
        };
        return buildFromPackage(baseDir);
    }
    async parseDependenciesTree(jsonBlob) {
        return Promise.resolve(JSON.parse(jsonBlob));
    }
}
exports.NpmNodeModulesCollector = NpmNodeModulesCollector;
//# sourceMappingURL=npmNodeModulesCollector.js.map