// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as semver from 'semver';
import { DependencyType } from '../api/PackageJsonEditor';
export class DependencyAnalyzer {
    constructor(rushConfiguration) {
        this._rushConfiguration = rushConfiguration;
    }
    static forRushConfiguration(rushConfiguration) {
        if (!DependencyAnalyzer._dependencyAnalyzerByRushConfiguration) {
            DependencyAnalyzer._dependencyAnalyzerByRushConfiguration = new WeakMap();
        }
        let analyzer = DependencyAnalyzer._dependencyAnalyzerByRushConfiguration.get(rushConfiguration);
        if (!analyzer) {
            analyzer = new DependencyAnalyzer(rushConfiguration);
            DependencyAnalyzer._dependencyAnalyzerByRushConfiguration.set(rushConfiguration, analyzer);
        }
        return analyzer;
    }
    getAnalysis(subspace, variant, addAction) {
        // Use an empty string as the key when no variant provided. Anything else would possibly conflict
        // with a variant created by the user
        const variantKey = variant || '';
        if (!this._analysisByVariantBySubspace) {
            this._analysisByVariantBySubspace = new Map();
        }
        const subspaceToAnalyze = subspace || this._rushConfiguration.defaultSubspace;
        let analysisForVariant = this._analysisByVariantBySubspace.get(variantKey);
        if (!analysisForVariant) {
            analysisForVariant = new WeakMap();
            this._analysisByVariantBySubspace.set(variantKey, analysisForVariant);
        }
        let analysisForSubspace = analysisForVariant.get(subspaceToAnalyze);
        if (!analysisForSubspace) {
            analysisForSubspace = this._getAnalysisInternal(subspaceToAnalyze, variant, addAction);
            analysisForVariant.set(subspaceToAnalyze, analysisForSubspace);
        }
        return analysisForSubspace;
    }
    /**
     * Generates the {@link IDependencyAnalysis}.
     *
     * @remarks
     * The result of this function is not cached.
     */
    _getAnalysisInternal(subspace, variant, addAction) {
        var _a;
        const commonVersionsConfiguration = subspace.getCommonVersions(variant);
        const allVersionsByPackageName = new Map();
        const allowedAlternativeVersions = commonVersionsConfiguration.allowedAlternativeVersions;
        let projectsToProcess = this._rushConfiguration.projects;
        if (addAction && this._rushConfiguration.subspacesFeatureEnabled) {
            projectsToProcess = subspace.getProjects();
        }
        for (const project of projectsToProcess) {
            const dependencies = [
                ...project.packageJsonEditor.dependencyList,
                ...project.packageJsonEditor.devDependencyList
            ];
            for (const { name: dependencyName, version: dependencyVersion, dependencyType } of dependencies) {
                if (dependencyType === DependencyType.Peer) {
                    // If this is a peer dependency, it isn't a real dependency in this context, so it shouldn't
                    // be included in the list of dependency versions.
                    continue;
                }
                if (dependencyVersion.startsWith('workspace:')) {
                    // If this is a workspace protocol dependency, ignore it.
                    continue;
                }
                // Is it a local project?
                const localProject = this._rushConfiguration.getProjectByName(dependencyName);
                if (localProject) {
                    if (!project.decoupledLocalDependencies.has(dependencyName) &&
                        semver.satisfies(localProject.packageJson.version, dependencyVersion)) {
                        // For now, ignore local dependencies (that aren't cyclic dependencies).
                        continue;
                    }
                }
                let allVersionForDependency = allVersionsByPackageName.get(dependencyName);
                if (!allVersionForDependency) {
                    allVersionForDependency = new Set();
                    allVersionsByPackageName.set(dependencyName, allVersionForDependency);
                }
                allVersionForDependency.add(dependencyVersion);
            }
        }
        const implicitlyPreferredVersionByPackageName = new Map();
        // Only generate implicitly preferred versions when requested
        const useImplicitlyPreferredVersions = (_a = commonVersionsConfiguration.implicitlyPreferredVersions) !== null && _a !== void 0 ? _a : true;
        if (useImplicitlyPreferredVersions) {
            for (const [dependencyName, versions] of allVersionsByPackageName) {
                // For each dependency, we're collecting the set of all version specifiers that appear across the repo.
                // If there is only one version specifier, then that's the "preferred" one.
                const alternativesForThisDependency = new Set(allowedAlternativeVersions.get(dependencyName));
                let implicitlyPreferredVersion = undefined;
                for (const version of versions) {
                    // Versions listed in the common-versions.json's "allowedAlternativeVersions" property
                    // can be safely ignored in determining the set of implicitly preferred versions.
                    // (Even if it's the only version specifier anywhere in the repo, we still ignore it, because
                    // otherwise the rule would be difficult to explain.)
                    if (!alternativesForThisDependency.has(version)) {
                        if (implicitlyPreferredVersion === undefined) {
                            // There isn't a candidate for an implicitly preferred version yet. Set this value as a candidate.
                            implicitlyPreferredVersion = version;
                        }
                        else {
                            // There was already another version that was a candidate. Clear that out and break.
                            // This dependency does not have an implicitly preferred version because there are at least
                            // two candidates.
                            implicitlyPreferredVersion = undefined;
                            break;
                        }
                    }
                }
                if (implicitlyPreferredVersion !== undefined) {
                    implicitlyPreferredVersionByPackageName.set(dependencyName, implicitlyPreferredVersion);
                }
            }
        }
        return {
            commonVersionsConfiguration,
            implicitlyPreferredVersionByPackageName,
            allVersionsByPackageName
        };
    }
}
//# sourceMappingURL=DependencyAnalyzer.js.map