"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractAllPackageFiles = exports.extractPackageFile = void 0;
const tslib_1 = require("tslib");
const upath_1 = tslib_1.__importDefault(require("upath"));
const logger_1 = require("../../../logger");
const fs_1 = require("../../../util/fs");
const util_1 = require("../../../util/fs/util");
const common_1 = require("../../datasource/pypi/common");
const extract_1 = require("../pip_requirements/extract");
const pip_setup_1 = require("../pip_setup");
const common_2 = require("./common");
const utils_1 = require("./utils");
function matchManager(filename) {
    if (filename.endsWith('setup.py')) {
        return 'pip_setup';
    }
    if (filename.endsWith('setup.cfg')) {
        return 'setup-cfg';
    }
    if (filename.endsWith('pyproject.toml')) {
        return 'pep621';
    }
    // naive, could be improved, maybe use pip_requirements.fileMatch
    if (filename.endsWith('.in')) {
        return 'pip_requirements';
    }
    return 'unknown';
}
function extractPackageFile(content, packageFile, _config) {
    logger_1.logger.trace('pip-compile.extractPackageFile()');
    const manager = matchManager(packageFile);
    switch (manager) {
        case 'pip_setup':
            return (0, pip_setup_1.extractPackageFile)(content, packageFile, _config);
        case 'pip_requirements':
            return (0, extract_1.extractPackageFile)(content);
        case 'unknown':
            logger_1.logger.warn({ packageFile }, `pip-compile: could not determine manager for source file`);
            return null;
        default:
            logger_1.logger.warn({ packageFile, manager }, `pip-compile: support for manager is not yet implemented`);
            return null;
    }
}
exports.extractPackageFile = extractPackageFile;
async function extractAllPackageFiles(config, fileMatches) {
    logger_1.logger.trace('pip-compile.extractAllPackageFiles()');
    const lockFileArgs = new Map();
    const depsBetweenFiles = [];
    const packageFiles = new Map();
    const lockFileSources = new Map();
    for (const fileMatch of fileMatches) {
        const fileContent = await (0, fs_1.readLocalFile)(fileMatch, 'utf8');
        if (!fileContent) {
            logger_1.logger.debug(`pip-compile: no content found for fileMatch ${fileMatch}`);
            continue;
        }
        let compileArgs;
        let compileDir;
        try {
            compileArgs = (0, common_2.extractHeaderCommand)(fileContent, fileMatch);
            compileDir = (0, utils_1.inferCommandExecDir)(fileMatch, compileArgs.outputFile);
        }
        catch (error) {
            logger_1.logger.warn({ fileMatch }, `pip-compile: ${error.message}`);
            continue;
        }
        lockFileArgs.set(fileMatch, compileArgs);
        for (const constraint in compileArgs.constraintsFiles) {
            depsBetweenFiles.push({
                sourceFile: constraint,
                outputFile: fileMatch,
                type: 'constraint',
            });
        }
        const lockedDeps = (0, extract_1.extractPackageFile)(fileContent)?.deps;
        if (!lockedDeps) {
            logger_1.logger.debug({ fileMatch }, 'pip-compile: Failed to extract dependencies from lock file');
            continue;
        }
        for (const relativeSourceFile of compileArgs.sourceFiles) {
            const packageFile = upath_1.default.normalizeTrim(upath_1.default.join(compileDir, relativeSourceFile));
            try {
                (0, util_1.ensureLocalPath)(packageFile);
            }
            catch (error) {
                logger_1.logger.warn({ fileMatch, packageFile }, 'pip-compile: Source file path outside of repository');
                continue;
            }
            depsBetweenFiles.push({
                sourceFile: packageFile,
                outputFile: fileMatch,
                type: 'requirement',
            });
            if (fileMatches.includes(packageFile)) {
                // TODO(not7cd): do something about it
                logger_1.logger.warn({ sourceFile: packageFile, lockFile: fileMatch }, 'pip-compile: lock file acts as source file for another lock file');
                continue;
            }
            if (packageFiles.has(packageFile)) {
                logger_1.logger.debug(`pip-compile: ${packageFile} used in multiple output files`);
                const existingPackageFile = packageFiles.get(packageFile);
                existingPackageFile.lockFiles.push(fileMatch);
                extendWithIndirectDeps(existingPackageFile, lockedDeps);
                lockFileSources.set(fileMatch, existingPackageFile);
                continue;
            }
            const content = await (0, fs_1.readLocalFile)(packageFile, 'utf8');
            if (!content) {
                logger_1.logger.debug(`pip-compile: No content for source file ${packageFile}`);
                continue;
            }
            const packageFileContent = extractPackageFile(content, packageFile, config);
            if (packageFileContent) {
                if (packageFileContent.managerData?.requirementsFiles) {
                    for (const file of packageFileContent.managerData.requirementsFiles) {
                        depsBetweenFiles.push({
                            sourceFile: file,
                            outputFile: packageFile,
                            type: 'requirement',
                        });
                    }
                }
                if (packageFileContent.managerData?.constraintsFiles) {
                    for (const file of packageFileContent.managerData.constraintsFiles) {
                        depsBetweenFiles.push({
                            sourceFile: file,
                            outputFile: packageFile,
                            type: 'requirement',
                        });
                    }
                }
                for (const dep of packageFileContent.deps) {
                    const lockedVersion = lockedDeps?.find((lockedDep) => (0, common_1.normalizeDepName)(lockedDep.depName) ===
                        (0, common_1.normalizeDepName)(dep.depName))?.currentVersion;
                    if (lockedVersion) {
                        dep.lockedVersion = lockedVersion;
                    }
                    else {
                        logger_1.logger.warn({ depName: dep.depName, lockFile: fileMatch }, 'pip-compile: dependency not found in lock file');
                    }
                }
                extendWithIndirectDeps(packageFileContent, lockedDeps);
                const newPackageFile = {
                    ...packageFileContent,
                    lockFiles: [fileMatch],
                    packageFile,
                };
                packageFiles.set(packageFile, newPackageFile);
                lockFileSources.set(fileMatch, newPackageFile);
            }
            else {
                logger_1.logger.warn({ packageFile }, 'pip-compile: failed to find dependencies in source file');
            }
        }
    }
    if (packageFiles.size === 0) {
        return null;
    }
    const result = (0, utils_1.sortPackageFiles)(depsBetweenFiles, packageFiles);
    // This needs to go in reverse order to handle transitive dependencies
    for (const packageFile of [...result].reverse()) {
        for (const reqFile of packageFile.managerData?.requirementsFiles ?? []) {
            let sourceFile = undefined;
            if (fileMatches.includes(reqFile)) {
                sourceFile = lockFileSources.get(reqFile);
            }
            else if (packageFiles.has(reqFile)) {
                sourceFile = packageFiles.get(reqFile);
            }
            if (!sourceFile) {
                logger_1.logger.warn(`pip-compile: ${packageFile.packageFile} references ${reqFile} which does not appear to be a requirements file managed by pip-compile`);
                continue;
            }
            sourceFile.lockFiles.push(...packageFile.lockFiles);
        }
    }
    logger_1.logger.debug('pip-compile: dependency graph:\n' +
        (0, utils_1.generateMermaidGraph)(depsBetweenFiles, lockFileArgs));
    return result;
}
exports.extractAllPackageFiles = extractAllPackageFiles;
function extendWithIndirectDeps(packageFileContent, lockedDeps) {
    for (const lockedDep of lockedDeps) {
        if (!packageFileContent.deps.find((dep) => (0, common_1.normalizeDepName)(lockedDep.depName) ===
            (0, common_1.normalizeDepName)(dep.depName))) {
            packageFileContent.deps.push(indirectDep(lockedDep));
        }
    }
}
/**
 * As indirect dependecies don't exist in the package file, we need to
 * create them from the lock file.
 *
 * By removing currentValue and currentVersion, we ensure that they
 * are handled like unconstrained dependencies with locked version.
 * Such packages are updated when their update strategy
 * is set to 'update-lockfile',
 * see: lib/workers/repository/process/lookup/index.ts.
 *
 * By disabling them by default, we won't create noise by updating them.
 * Unless they have vulnerability alert, then they are forced to be updated.
 * @param dep dependency extracted from lock file (requirements.txt)
 * @returns unconstrained dependency with locked version
 */
function indirectDep(dep) {
    const result = {
        ...dep,
        lockedVersion: dep.currentVersion,
        depType: 'indirect',
        enabled: false,
    };
    delete result.currentValue;
    delete result.currentVersion;
    return result;
}
//# sourceMappingURL=extract.js.map