"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePropsFile = exports.parseLockFile = exports.parseGcv = exports.isGcvLockFile = exports.isGcvPropsFile = exports.usesGcv = exports.VERSIONS_LOCK = exports.VERSIONS_PROPS = void 0;
const tslib_1 = require("tslib");
const logger_1 = require("../../../../logger");
const fs = tslib_1.__importStar(require("../../../../util/fs"));
const regex_1 = require("../../../../util/regex");
const utils_1 = require("../utils");
exports.VERSIONS_PROPS = 'versions.props';
exports.VERSIONS_LOCK = 'versions.lock';
const LOCKFILE_HEADER_TEXT = '# Run ./gradlew --write-locks to regenerate this file';
/**
 * Determines if Palantir gradle-consistent-versions is in use, https://github.com/palantir/gradle-consistent-versions.
 * Both `versions.props` and `versions.lock` must exist and the special header line of lock file must match
 *
 * @param versionsPropsFilename is the full file name path of `versions.props`
 * @param fileContents map with file contents of all files
 */
function usesGcv(versionsPropsFilename, fileContents) {
    const versionsLockFile = fs.getSiblingFileName(versionsPropsFilename, exports.VERSIONS_LOCK);
    return (fileContents[versionsLockFile]?.startsWith(LOCKFILE_HEADER_TEXT) ?? false);
}
exports.usesGcv = usesGcv;
/**
 * Confirms whether the provided file name is the props file
 */
function isGcvPropsFile(fileName) {
    return fileName === exports.VERSIONS_PROPS || fileName.endsWith(`/${exports.VERSIONS_PROPS}`);
}
exports.isGcvPropsFile = isGcvPropsFile;
/**
 * Confirms whether the provided file name is the lock file
 */
function isGcvLockFile(fileName) {
    return fileName === exports.VERSIONS_LOCK || fileName.endsWith(`/${exports.VERSIONS_LOCK}`);
}
exports.isGcvLockFile = isGcvLockFile;
/**
 * Parses Gradle-Consistent-Versions files to figure out what dependencies, versions
 * and groups they contain. The parsing goes like this:
 * - Parse `versions.props` into deps (or groups) and versions, remembering file offsets
 * - Parse `versions.lock` into deps and lock-versions
 * - For each exact dep in props file, lookup the lock-version from lock file
 * - For each group/regex dep in props file, lookup the set of exact deps and versions in lock file
 *
 * @param propsFileName name and path of the props file
 * @param fileContents text content of all files
 */
function parseGcv(propsFileName, fileContents) {
    const propsFileContent = fileContents[propsFileName] ?? '';
    const lockFileName = fs.getSiblingFileName(propsFileName, exports.VERSIONS_LOCK);
    const lockFileContent = fileContents[lockFileName] ?? '';
    const lockFileMap = parseLockFile(lockFileContent);
    const [propsFileExactMap, propsFileRegexMap] = parsePropsFile(propsFileContent);
    const extractedDeps = [];
    // For each exact dep in props file
    for (const [propDep, versionAndPosition] of propsFileExactMap) {
        if (lockFileMap.has(propDep)) {
            const newDep = {
                managerData: {
                    packageFile: propsFileName,
                    fileReplacePosition: versionAndPosition.filePos,
                },
                depName: propDep,
                currentValue: versionAndPosition.version,
                lockedVersion: lockFileMap.get(propDep)?.version,
                depType: lockFileMap.get(propDep)?.depType,
            };
            extractedDeps.push(newDep);
            // Remove from the lockfile map so the same exact lib will not be included in globbing
            lockFileMap.delete(propDep);
        }
    }
    // For each regular expression dep in props file (starting with the longest glob string)...
    for (const [propDepGlob, propVerAndPos] of propsFileRegexMap) {
        const globRegex = globToRegex(propDepGlob);
        for (const [exactDep, lockVersionAndDepType] of lockFileMap) {
            if (globRegex.test(exactDep)) {
                const newDep = {
                    managerData: {
                        packageFile: propsFileName,
                        fileReplacePosition: propVerAndPos.filePos,
                    },
                    depName: exactDep,
                    currentValue: propVerAndPos.version,
                    lockedVersion: lockVersionAndDepType.version,
                    depType: lockVersionAndDepType.depType,
                    groupName: propDepGlob,
                };
                extractedDeps.push(newDep);
                // Remove from the lockfile map so the same lib will not be included in more generic globs later
                lockFileMap.delete(exactDep);
            }
        }
    }
    return extractedDeps;
}
exports.parseGcv = parseGcv;
// Translate glob syntax to a regex that does the same. Note that we cannot use replaceAll as it does not exist in Node14
// Loosely borrowed mapping to regex from https://github.com/palantir/gradle-consistent-versions/blob/develop/src/main/java/com/palantir/gradle/versions/FuzzyPatternResolver.java
function globToRegex(depName) {
    return (0, regex_1.regEx)(depName
        .replace(/\*/g, '_WC_CHAR_')
        .replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
        .replace('_WC_CHAR_', '.*?'));
}
/**
 * Parses `versions.lock`
 */
function parseLockFile(input) {
    const lockLineRegex = (0, regex_1.regEx)(`^(?<depName>[^:]+:[^:]+):(?<lockVersion>[^ ]+) \\(\\d+ constraints: [0-9a-f]+\\)$`);
    const depVerMap = new Map();
    let isTestDepType = false;
    for (const line of input.split(regex_1.newlineRegex)) {
        const lineMatch = lockLineRegex.exec(line);
        if (lineMatch?.groups) {
            const { depName, lockVersion } = lineMatch.groups;
            if ((0, utils_1.isDependencyString)(`${depName}:${lockVersion}`)) {
                depVerMap.set(depName, {
                    version: lockVersion,
                    depType: isTestDepType ? 'test' : 'dependencies',
                });
            }
        }
        else if (line === '[Test dependencies]') {
            isTestDepType = true; // We know that all lines below this header are test dependencies
        }
    }
    logger_1.logger.trace(`Found ${depVerMap.size} locked dependencies in ${exports.VERSIONS_LOCK}.`);
    return depVerMap;
}
exports.parseLockFile = parseLockFile;
/**
 * Parses `versions.props`, this is CR/LF safe
 * @param input the entire property file from file system
 * @return two maps, first being exact matches, second regex matches
 */
function parsePropsFile(input) {
    const propsLineRegex = (0, regex_1.regEx)(`^(?<depName>[^:]+:[^=]+?) *= *(?<propsVersion>.*)$`);
    const depVerExactMap = new Map();
    const depVerRegexMap = new Map();
    let startOfLineIdx = 0;
    const isCrLf = input.indexOf('\r\n') > 0;
    const validGlob = /^[a-zA-Z][-_a-zA-Z0-9.:*]+$/;
    for (const line of input.split(regex_1.newlineRegex)) {
        const lineMatch = propsLineRegex.exec(line);
        if (lineMatch?.groups) {
            const { depName, propsVersion } = lineMatch.groups;
            if (validGlob.test(depName) &&
                (0, utils_1.versionLikeSubstring)(propsVersion) !== null) {
                const startPosInLine = line.lastIndexOf(propsVersion);
                const propVersionPos = startOfLineIdx + startPosInLine;
                if (depName.includes('*')) {
                    depVerRegexMap.set(depName, {
                        version: propsVersion,
                        filePos: propVersionPos,
                    });
                }
                else {
                    depVerExactMap.set(depName, {
                        version: propsVersion,
                        filePos: propVersionPos,
                    });
                }
            }
        }
        startOfLineIdx += line.length + (isCrLf ? 2 : 1);
    }
    logger_1.logger.trace(`Found ${depVerExactMap.size} dependencies and ${depVerRegexMap.size} wildcard dependencies in ${exports.VERSIONS_PROPS}.`);
    return [depVerExactMap, new Map([...depVerRegexMap].sort().reverse())];
}
exports.parsePropsFile = parsePropsFile;
//# sourceMappingURL=consistent-versions-plugin.js.map