"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.trimAtKey = trimAtKey;
exports.expandDepName = expandDepName;
exports.extractFromVectors = extractFromVectors;
exports.extractVariables = extractVariables;
exports.extractPackageFile = extractPackageFile;
const array_1 = require("../../../util/array");
const regex_1 = require("../../../util/regex");
const clojure_1 = require("../../datasource/clojure");
function trimAtKey(str, kwName) {
    const regex = new RegExp(`:${kwName}(?=\\s)`); // TODO #12872 lookahead
    const keyOffset = str.search(regex);
    if (keyOffset < 0) {
        return null;
    }
    const withSpaces = str.slice(keyOffset + kwName.length + 1);
    const valueOffset = withSpaces.search((0, regex_1.regEx)(/[^\s]/));
    if (valueOffset < 0) {
        return null;
    }
    return withSpaces.slice(valueOffset);
}
function expandDepName(name) {
    return name.includes('/') ? name.replace('/', ':') : `${name}:${name}`;
}
function extractFromVectors(str, ctx = {}, vars = {}, dimensions = 2) {
    if (!str.startsWith('[')) {
        return [];
    }
    let balance = 0;
    const result = [];
    let idx = 0;
    let vecPos = 0;
    let artifactId = '';
    let version = '';
    // Are we currently parsing a comment? If so, at what depth?
    let commentLevel = null;
    const isSpace = (ch) => !!ch && (0, regex_1.regEx)(/[\s,]/).test(ch);
    const cleanStrLiteral = (s) => s.replace((0, regex_1.regEx)(/^"/), '').replace((0, regex_1.regEx)(/"$/), '');
    const yieldDep = () => {
        if (!commentLevel && artifactId && version) {
            const depName = expandDepName(cleanStrLiteral(artifactId));
            if (version.startsWith('~')) {
                const varName = version.replace((0, regex_1.regEx)(/^~\s*/), '');
                const currentValue = vars[varName];
                if (currentValue) {
                    result.push({
                        ...ctx,
                        datasource: clojure_1.ClojureDatasource.id,
                        depName,
                        currentValue,
                        groupName: varName,
                    });
                }
            }
            else {
                result.push({
                    ...ctx,
                    datasource: clojure_1.ClojureDatasource.id,
                    depName,
                    currentValue: cleanStrLiteral(version),
                });
            }
        }
        artifactId = '';
        version = '';
    };
    let prevChar = null;
    while (idx < str.length) {
        const char = str.charAt(idx);
        if (str.substring(idx).startsWith('#_[')) {
            commentLevel = balance;
        }
        if (char === '[') {
            balance += 1;
            if (balance === dimensions) {
                vecPos = 0;
            }
        }
        else if (char === ']') {
            balance -= 1;
            if (commentLevel === balance) {
                artifactId = '';
                version = '';
                commentLevel = null;
            }
            if (balance === dimensions - 1) {
                yieldDep();
            }
            if (balance === 0) {
                break;
            }
        }
        else if (balance === dimensions) {
            if (isSpace(char)) {
                if (!isSpace(prevChar)) {
                    vecPos += 1;
                }
            }
            else if (vecPos === 0) {
                artifactId += char;
            }
            else if (vecPos === 1) {
                version += char;
            }
        }
        prevChar = char;
        idx += 1;
    }
    return result;
}
function extractLeinRepos(content) {
    const result = [];
    const repoContent = trimAtKey(content.replace(/;;.*(?=[\r\n])/g, ''), // get rid of comments // TODO #12872 lookahead
    'repositories');
    if (repoContent) {
        let balance = 0;
        let endIdx = 0;
        for (let idx = 0; idx < repoContent.length; idx += 1) {
            const char = repoContent.charAt(idx);
            if (char === '[') {
                balance += 1;
            }
            else if (char === ']') {
                balance -= 1;
                if (balance <= 0) {
                    endIdx = idx;
                    break;
                }
            }
        }
        const repoSectionContent = repoContent.slice(0, endIdx);
        const matches = (0, array_1.coerceArray)(repoSectionContent.match((0, regex_1.regEx)(/"https?:\/\/[^"]*"/g)));
        const urls = matches.map((x) => x.replace((0, regex_1.regEx)(/^"/), '').replace((0, regex_1.regEx)(/"$/), ''));
        urls.forEach((url) => result.push(url));
    }
    return result;
}
const defRegex = (0, regex_1.regEx)(/^[\s,]*\([\s,]*def[\s,]+(?<varName>[-+*=<>.!?#$%&_|a-zA-Z][-+*=<>.!?#$%&_|a-zA-Z0-9']+)[\s,]*"(?<stringValue>[^"]*)"[\s,]*\)[\s,]*$/);
function extractVariables(content) {
    const result = {};
    const lines = content.split(regex_1.newlineRegex);
    for (let idx = 0; idx < lines.length; idx += 1) {
        const line = lines[idx];
        const match = defRegex.exec(line);
        if (match?.groups) {
            const { varName: key, stringValue: val } = match.groups;
            result[key] = val;
        }
    }
    return result;
}
function collectDeps(content, key, registryUrls, vars, options = {
    nested: true,
}) {
    const ctx = {
        depType: options.depType ?? key,
        registryUrls,
    };
    // A vector like [["dep-1" "1.0.0"] ["dep-2" "0.0.0"]] is nested
    // A vector like ["dep-1" "1.0.0"] is not
    const dimensions = options.nested ? 2 : 1;
    let result = [];
    let restContent = trimAtKey(content, key);
    while (restContent) {
        result = [
            ...result,
            ...extractFromVectors(restContent, ctx, vars, dimensions),
        ];
        restContent = trimAtKey(restContent, key);
    }
    return result;
}
function extractPackageFile(content) {
    const registryUrls = extractLeinRepos(content);
    const vars = extractVariables(content);
    const deps = [
        ...collectDeps(content, 'dependencies', registryUrls, vars),
        ...collectDeps(content, 'managed-dependencies', registryUrls, vars),
        ...collectDeps(content, 'plugins', registryUrls, vars),
        ...collectDeps(content, 'pom-plugins', registryUrls, vars),
        // 'coords' is used in lein parent, and specifies zero or one
        // dependencies. These are not wrapped in a vector in the way other
        // dependencies are. The project.clj fragment looks like
        //
        // :parent-project {... :coords ["parent" "version"] ...}
        //
        // - https://github.com/achin/lein-parent
        ...collectDeps(content, 'coords', registryUrls, vars, {
            nested: false,
            // The top-level key is 'parent-project', but we skip directly to 'coords'.
            // So fix the dep type label
            depType: 'parent-project',
        }),
    ];
    return { deps };
}
//# sourceMappingURL=extract.js.map