"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractPackageFile = extractPackageFile;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const fs_1 = require("../../../util/fs");
const cargo_1 = require("../../versioning/cargo");
const locked_version_1 = require("./locked-version");
const schema_1 = require("./schema");
const utils_1 = require("./utils");
const DEFAULT_REGISTRY_ID = 'crates-io';
function getCargoIndexEnv(registryName) {
    const registry = registryName.toUpperCase().replaceAll('-', '_');
    return process.env[`CARGO_REGISTRIES_${registry}_INDEX`] ?? null;
}
function extractFromSection(dependencies, cargoRegistries, target) {
    if (!dependencies) {
        return [];
    }
    const deps = [];
    for (const dep of Object.values(dependencies)) {
        let registryUrls;
        if (dep.managerData?.registryName) {
            const registryUrl = getCargoIndexEnv(dep.managerData.registryName) ??
                cargoRegistries[dep.managerData?.registryName];
            if (registryUrl) {
                if (registryUrl !== utils_1.DEFAULT_REGISTRY_URL) {
                    registryUrls = [registryUrl];
                }
            }
            else {
                dep.skipReason = 'unknown-registry';
            }
        }
        if (registryUrls) {
            dep.registryUrls = registryUrls;
        }
        else {
            // if we don't have an explicit registry URL check if the default registry has a non-standard url
            if (cargoRegistries[DEFAULT_REGISTRY_ID]) {
                if (cargoRegistries[DEFAULT_REGISTRY_ID] !== utils_1.DEFAULT_REGISTRY_URL) {
                    dep.registryUrls = [cargoRegistries[DEFAULT_REGISTRY_ID]];
                }
            }
            else {
                // we always expect to have DEFAULT_REGISTRY_ID set, if it's not it means the config defines an alternative
                // registry that we couldn't resolve.
                dep.skipReason = 'unknown-registry';
            }
        }
        if (target) {
            dep.target = target;
        }
        deps.push(dep);
    }
    return deps;
}
/** Reads `.cargo/config.toml`, or, if not found, `.cargo/config` */
async function readCargoConfig() {
    for (const configName of ['config.toml', 'config']) {
        const path = `.cargo/${configName}`;
        const payload = await (0, fs_1.readLocalFile)(path, 'utf8');
        if (payload) {
            const parsedCargoConfig = schema_1.CargoConfigSchema.safeParse(payload);
            if (parsedCargoConfig.success) {
                return parsedCargoConfig.data;
            }
            else {
                logger_1.logger.debug({ err: parsedCargoConfig.error, path }, `Error parsing cargo config`);
            }
        }
    }
    logger_1.logger.debug('Neither .cargo/config nor .cargo/config.toml found');
    return null;
}
/** Extracts a map of cargo registries from a CargoConfig */
function extractCargoRegistries(config) {
    const result = {};
    // check if we're overriding our default registry index
    result[DEFAULT_REGISTRY_ID] = resolveRegistryIndex(DEFAULT_REGISTRY_ID, config);
    const registryNames = new Set([
        ...Object.keys(config.registries ?? {}),
        ...Object.keys(config.source ?? {}),
    ]);
    for (const registryName of registryNames) {
        result[registryName] = resolveRegistryIndex(registryName, config);
    }
    return result;
}
function resolveRegistryIndex(registryName, config, originalNames = new Set()) {
    // if we have a source replacement, follow that.
    // https://doc.rust-lang.org/cargo/reference/source-replacement.html
    const replacementName = config.source?.[registryName]?.['replace-with'];
    if (replacementName) {
        logger_1.logger.debug(`Replacing index of cargo registry ${registryName} with ${replacementName}`);
        if (originalNames.has(replacementName)) {
            logger_1.logger.warn(`${registryName} cargo registry resolves to itself`);
            return null;
        }
        return resolveRegistryIndex(replacementName, config, originalNames.add(replacementName));
    }
    const sourceRegistry = config.source?.[registryName]?.registry;
    if (sourceRegistry) {
        logger_1.logger.debug(`Replacing cargo source registry with ${sourceRegistry} for ${registryName}`);
        return sourceRegistry;
    }
    const registryIndex = config.registries?.[registryName]?.index;
    if (registryIndex) {
        return registryIndex;
    }
    else {
        // we don't need an explicit index if we're using the default registry
        if (registryName === DEFAULT_REGISTRY_ID) {
            return utils_1.DEFAULT_REGISTRY_URL;
        }
        else {
            logger_1.logger.debug(`${registryName} cargo registry is missing index`);
            return null;
        }
    }
}
async function extractPackageFile(content, packageFile, _config) {
    logger_1.logger.trace(`cargo.extractPackageFile(${packageFile})`);
    const cargoConfig = (await readCargoConfig()) ?? {};
    const cargoRegistries = extractCargoRegistries(cargoConfig);
    const parsedCargoManifest = schema_1.CargoManifestSchema.safeParse(content);
    if (!parsedCargoManifest.success) {
        logger_1.logger.debug({ err: parsedCargoManifest.error, packageFile }, 'Error parsing Cargo.toml file');
        return null;
    }
    const cargoManifest = parsedCargoManifest.data;
    /*
      There are the following sections in Cargo.toml:
      [package]
      [dependencies]
      [dev-dependencies]
      [build-dependencies]
      [target.*.dependencies]
      [workspace.dependencies]
    */
    const targetSection = cargoManifest.target;
    // An array of all dependencies in the target section
    let targetDeps = [];
    if (targetSection) {
        const targets = Object.keys(targetSection);
        targets.forEach((target) => {
            const targetContent = targetSection[target];
            // Dependencies for `${target}`
            const deps = [
                ...extractFromSection(targetContent.dependencies, cargoRegistries, target),
                ...extractFromSection(targetContent['dev-dependencies'], cargoRegistries, target),
                ...extractFromSection(targetContent['build-dependencies'], cargoRegistries, target),
            ];
            targetDeps = targetDeps.concat(deps);
        });
    }
    const workspaceSection = cargoManifest.workspace;
    let workspaceDeps = [];
    if (workspaceSection) {
        workspaceDeps = extractFromSection(workspaceSection.dependencies, cargoRegistries, undefined);
    }
    const deps = [
        ...extractFromSection(cargoManifest.dependencies, cargoRegistries),
        ...extractFromSection(cargoManifest['dev-dependencies'], cargoRegistries),
        ...extractFromSection(cargoManifest['build-dependencies'], cargoRegistries),
        ...targetDeps,
        ...workspaceDeps,
    ];
    if (!deps.length) {
        return null;
    }
    const packageSection = cargoManifest.package;
    let version = undefined;
    if (packageSection) {
        if (is_1.default.string(packageSection.version)) {
            version = packageSection.version;
        }
        else if (is_1.default.object(packageSection.version) &&
            cargoManifest.workspace?.package?.version) {
            // TODO: Support reading from parent workspace manifest?
            version = cargoManifest.workspace.package.version;
        }
    }
    const lockFileName = await (0, fs_1.findLocalSiblingOrParent)(packageFile, 'Cargo.lock');
    const res = { deps, packageFileVersion: version };
    if (lockFileName) {
        logger_1.logger.debug(`Found lock file ${lockFileName} for packageFile: ${packageFile}`);
        const versionsByPackage = await (0, locked_version_1.extractLockFileVersions)(lockFileName);
        if (!versionsByPackage) {
            logger_1.logger.debug(`Could not extract lock file versions from ${lockFileName}.`);
            return res;
        }
        res.lockFiles = [lockFileName];
        for (const dep of deps) {
            const packageName = dep.packageName ?? dep.depName;
            const versions = (0, array_1.coerceArray)(versionsByPackage.get(packageName));
            const lockedVersion = cargo_1.api.getSatisfyingVersion(versions, dep.currentValue);
            if (lockedVersion) {
                dep.lockedVersion = lockedVersion;
            }
            else {
                logger_1.logger.debug(`No locked version found for package ${dep.depName} in the range of ${dep.currentValue}.`);
            }
        }
    }
    return res;
}
//# sourceMappingURL=extract.js.map