"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = require("@pnpm/logger");
const got = require("got");
const git = require("graceful-git");
const semver = require("semver");
const parsePref_1 = require("./parsePref");
const gitLogger = logger_1.default; // TODO: add namespace 'git-logger'
let tryGitHubApi = true;
function default_1(opts) {
    return function resolveGit(wantedDependency) {
        return __awaiter(this, void 0, void 0, function* () {
            const parsedSpec = parsePref_1.default(wantedDependency.pref);
            if (!parsedSpec)
                return null;
            const isGitHubHosted = parsedSpec.hosted && parsedSpec.hosted.type === 'github';
            if (!isGitHubHosted || isSsh(wantedDependency.pref)) {
                const commit = yield resolveRef(parsedSpec.fetchSpec, parsedSpec.gitCommittish || 'master', parsedSpec.gitRange);
                return {
                    id: parsedSpec.fetchSpec
                        .replace(/^.*:\/\/(git@)?/, '')
                        .replace(/:/g, '+')
                        .replace(/\.git$/, '') + '/' + commit,
                    normalizedPref: parsedSpec.normalizedPref,
                    resolution: {
                        commit,
                        repo: parsedSpec.fetchSpec,
                        type: 'git',
                    },
                };
            }
            const parts = normalizeRepoUrl(parsedSpec).split('#');
            const repo = parts[0];
            const ghSpec = {
                project: parsedSpec.hosted.project,
                ref: parsedSpec.hosted.committish || 'master',
                user: parsedSpec.hosted.user,
            };
            let commitId;
            if (tryGitHubApi) {
                try {
                    commitId = resolveRefFromRefs(yield tryResolveViaGitHubApi(ghSpec), repo, ghSpec.ref, parsedSpec.gitRange);
                }
                catch (err) {
                    gitLogger.warn({
                        err,
                        message: `Error while trying to resolve ${parsedSpec.fetchSpec} via GitHub API`,
                    });
                    // if it fails once, don't bother retrying for other packages
                    tryGitHubApi = false;
                    commitId = yield resolveRef(repo, parsedSpec.gitCommittish || 'master', parsedSpec.gitRange);
                }
            }
            else {
                commitId = yield resolveRef(repo, parsedSpec.gitCommittish || 'master', parsedSpec.gitRange);
            }
            const tarballResolution = {
                tarball: `https://codeload.github.com/${ghSpec.user}/${ghSpec.project}/tar.gz/${commitId}`,
            };
            return {
                id: ['github.com', ghSpec.user, ghSpec.project, commitId].join('/'),
                normalizedPref: parsedSpec.normalizedPref,
                resolution: tarballResolution,
            };
        });
    };
}
exports.default = default_1;
function resolveVTags(vTags, range) {
    return semver.maxSatisfying(vTags, range, true);
}
function getRepoRefs(repo) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = yield git(['ls-remote', '--refs', repo]);
        const refs = result.stdout.split('\n').reduce((obj, line) => {
            const commitAndRef = line.split('\t');
            const commit = commitAndRef[0];
            const ref = commitAndRef[1];
            obj[ref] = commit;
            return obj;
        }, {});
        return refs;
    });
}
function resolveRef(repo, ref, range) {
    return __awaiter(this, void 0, void 0, function* () {
        const refs = yield getRepoRefs(repo);
        return resolveRefFromRefs(refs, repo, ref, range);
    });
}
function resolveRefFromRefs(refs, repo, ref, range) {
    if (!range) {
        const commitId = refs[ref] ||
            refs[`refs/tags/${ref}^{}`] || // prefer annotated tags
            refs[`refs/tags/${ref}`] ||
            refs[`refs/heads/${ref}`] ||
            (ref.match(/^[0-9a-f]{40}/) || [])[0];
        if (!commitId) {
            throw new Error(`Could not resolve ${ref} to a commit of ${repo}.`);
        }
        return commitId;
    }
    else {
        const vTags = Object.keys(refs)
            .filter((key) => /^refs\/tags\/v?(\d+\.\d+\.\d+(?:[-+].+)?)(\^{})?$/.test(key))
            .map((key) => {
            return key
                .replace(/^refs\/tags\//, '')
                .replace(/\^{}$/, ''); // accept annotated tags
        })
            .filter((key) => semver.valid(key, true));
        const refVTag = resolveVTags(vTags, range);
        const commitId = refVTag &&
            (refs[`refs/tags/${refVTag}^{}`] || // prefer annotated tags
                refs[`refs/tags/${refVTag}`]);
        if (!commitId) {
            throw new Error(`Could not resolve ${range} to a commit of ${repo}. Available versions are: ${vTags.join(', ')}`);
        }
        return commitId;
    }
}
function normalizeRepoUrl(parsedSpec) {
    const hosted = parsedSpec.hosted; // tslint:disable-line
    return hosted.getDefaultRepresentation() === 'shortcut' ? hosted.git() : hosted.toString();
}
function isSsh(gitSpec) {
    return gitSpec.substr(0, 10) === 'git+ssh://'
        || gitSpec.substr(0, 4) === 'git@';
}
/**
 * Resolves a 'hosted' package hosted on 'github'.
 */
function tryResolveViaGitHubApi(spec) {
    return __awaiter(this, void 0, void 0, function* () {
        const url = `https://api.github.com/repos/${spec.user}/${spec.project}/git/refs`;
        const response = yield got(url, { json: true });
        return response.body.reduce((acc, refInfo) => {
            acc[refInfo.ref] = refInfo.object.sha;
            return acc;
        }, {});
    });
}
//# sourceMappingURL=index.js.map