"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DependencySpecifier = exports.DependencySpecifierType = void 0;
const npm_package_arg_1 = __importDefault(require("npm-package-arg"));
const node_core_library_1 = require("@rushstack/node-core-library");
/**
 * match workspace protocol in dependencies value declaration in `package.json`
 * example:
 * `"workspace:*"`
 * `"workspace:alias@1.2.3"`
 */
const WORKSPACE_PREFIX_REGEX = /^workspace:((?<alias>[^._/][^@]*)@)?(?<version>.*)$/;
/**
 * match catalog protocol in dependencies value declaration in `package.json`
 * example:
 * `"catalog:"` - references the default catalog
 * `"catalog:catalogName"` - references a named catalog
 */
const CATALOG_PREFIX_REGEX = /^catalog:(?<catalogName>.*)$/;
/**
 * resolve workspace protocol(from `@pnpm/workspace.spec-parser`).
 * used by pnpm. see [pkgs-graph](https://github.com/pnpm/pnpm/blob/27c33f0319f86c45c1645d064cd9c28aada80780/workspace/pkgs-graph/src/index.ts#L49)
 */
class WorkspaceSpec {
    constructor(version, alias) {
        this.version = version;
        this.alias = alias;
        this.versionSpecifier = alias ? `${alias}@${version}` : version;
    }
    static tryParse(pref) {
        const parts = WORKSPACE_PREFIX_REGEX.exec(pref);
        if (parts === null || parts === void 0 ? void 0 : parts.groups) {
            return new WorkspaceSpec(parts.groups.version, parts.groups.alias);
        }
    }
    toString() {
        return `workspace:${this.versionSpecifier}`;
    }
}
/**
 * resolve catalog protocol.
 * Used by pnpm for centralized version management via catalogs.
 */
class CatalogSpec {
    constructor(catalogName) {
        this.catalogName = catalogName;
    }
    static tryParse(pref) {
        const parts = CATALOG_PREFIX_REGEX.exec(pref);
        if ((parts === null || parts === void 0 ? void 0 : parts.groups) !== undefined) {
            return new CatalogSpec(parts.groups.catalogName);
        }
    }
    toString() {
        return `catalog:${this.catalogName}`;
    }
}
/**
 * The parsed format of a provided version specifier.
 */
var DependencySpecifierType;
(function (DependencySpecifierType) {
    /**
     * A git repository
     */
    DependencySpecifierType["Git"] = "Git";
    /**
     * A tagged version, e.g. "example@latest"
     */
    DependencySpecifierType["Tag"] = "Tag";
    /**
     * A specific version number, e.g. "example@1.2.3"
     */
    DependencySpecifierType["Version"] = "Version";
    /**
     * A version range, e.g. "example@2.x"
     */
    DependencySpecifierType["Range"] = "Range";
    /**
     * A local .tar.gz, .tar or .tgz file
     */
    DependencySpecifierType["File"] = "File";
    /**
     * A local directory
     */
    DependencySpecifierType["Directory"] = "Directory";
    /**
     * An HTTP url to a .tar.gz, .tar or .tgz file
     */
    DependencySpecifierType["Remote"] = "Remote";
    /**
     * A package alias, e.g. "npm:other-package@^1.2.3"
     */
    DependencySpecifierType["Alias"] = "Alias";
    /**
     * A package specified using workspace protocol, e.g. "workspace:^1.2.3"
     */
    DependencySpecifierType["Workspace"] = "Workspace";
    /**
     * A package specified using catalog protocol, e.g. "catalog:" or "catalog:react18"
     */
    DependencySpecifierType["Catalog"] = "Catalog";
})(DependencySpecifierType || (exports.DependencySpecifierType = DependencySpecifierType = {}));
const dependencySpecifierParseCache = new Map();
/**
 * An NPM "version specifier" is a string that can appear as a package.json "dependencies" value.
 * Example version specifiers: `^1.2.3`, `file:./blah.tgz`, `npm:other-package@~1.2.3`, and so forth.
 * A "dependency specifier" is the version specifier information, combined with the dependency package name.
 */
class DependencySpecifier {
    constructor(packageName, versionSpecifier) {
        this.packageName = packageName;
        this.versionSpecifier = versionSpecifier;
        // Catalog protocol is a feature from PNPM for centralized version management
        const catalogSpecResult = CatalogSpec.tryParse(versionSpecifier);
        if (catalogSpecResult) {
            this.specifierType = DependencySpecifierType.Catalog;
            this.versionSpecifier = catalogSpecResult.catalogName;
            this.aliasTarget = undefined;
            return;
        }
        // Workspace ranges are a feature from PNPM and Yarn. Set the version specifier
        // to the trimmed version range.
        const workspaceSpecResult = WorkspaceSpec.tryParse(versionSpecifier);
        if (workspaceSpecResult) {
            this.specifierType = DependencySpecifierType.Workspace;
            this.versionSpecifier = workspaceSpecResult.versionSpecifier;
            if (workspaceSpecResult.alias) {
                // "workspace:some-package@^1.2.3" should be resolved as alias
                this.aliasTarget = DependencySpecifier.parseWithCache(workspaceSpecResult.alias, workspaceSpecResult.version);
            }
            else {
                this.aliasTarget = undefined;
            }
            return;
        }
        const result = npm_package_arg_1.default.resolve(packageName, versionSpecifier);
        this.specifierType = DependencySpecifier.getDependencySpecifierType(result.type);
        if (this.specifierType === DependencySpecifierType.Alias) {
            const aliasResult = result;
            if (!aliasResult.subSpec || !aliasResult.subSpec.name) {
                throw new node_core_library_1.InternalError('Unexpected result from npm-package-arg');
            }
            this.aliasTarget = DependencySpecifier.parseWithCache(aliasResult.subSpec.name, aliasResult.subSpec.rawSpec);
        }
        else {
            this.aliasTarget = undefined;
        }
    }
    /**
     * Clears the dependency specifier parse cache.
     */
    static clearCache() {
        dependencySpecifierParseCache.clear();
    }
    /**
     * Parses a dependency specifier with caching.
     * @param packageName - The name of the package the version specifier corresponds to
     * @param versionSpecifier - The version specifier to parse
     * @returns The parsed dependency specifier
     */
    static parseWithCache(packageName, versionSpecifier) {
        const cacheKey = `${packageName}\0${versionSpecifier}`;
        let result = dependencySpecifierParseCache.get(cacheKey);
        if (!result) {
            result = new DependencySpecifier(packageName, versionSpecifier);
            dependencySpecifierParseCache.set(cacheKey, result);
        }
        return result;
    }
    static getDependencySpecifierType(specifierType) {
        switch (specifierType) {
            case 'git':
                return DependencySpecifierType.Git;
            case 'tag':
                return DependencySpecifierType.Tag;
            case 'version':
                return DependencySpecifierType.Version;
            case 'range':
                return DependencySpecifierType.Range;
            case 'file':
                return DependencySpecifierType.File;
            case 'directory':
                return DependencySpecifierType.Directory;
            case 'remote':
                return DependencySpecifierType.Remote;
            case 'alias':
                return DependencySpecifierType.Alias;
            default:
                throw new node_core_library_1.InternalError(`Unexpected npm-package-arg result type "${specifierType}"`);
        }
    }
}
exports.DependencySpecifier = DependencySpecifier;
//# sourceMappingURL=DependencySpecifier.js.map