"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());
    });
};
const debug_1 = require('../debug');
const debug = debug_1.default('pnpm:install');
const npa = require('npm-package-arg');
const fs = require('mz/fs');
const logger = require('@zkochan/logger');
const path = require('path');
const rimraf = require('rimraf-then');
const resolve_1 = require('../resolve');
const mkdirp_1 = require('../fs/mkdirp');
const requireJson_1 = require('../fs/requireJson');
const link_dir_1 = require('link-dir');
const exists = require('exists-file');
const isAvailable_1 = require('./isAvailable');
const memoize_1 = require('../memoize');
const env_1 = require('../env');
/**
 * Installs a package.
 *
 * What it does:
 *
 * - resolve() - resolve from registry.npmjs.org
 * - fetch() - download tarball into node_modules/.store/{name}@{version}
 * - recurse into its dependencies
 * - symlink node_modules/{name}
 *
 * @param {Object} ctx - the context.
 * @param {Object} pkgMeta - meta info about the package to install.
 *
 * @example
 *     install(ctx, 'rimraf@2', './node_modules')
 */
function fetch(ctx, pkgRawSpec, modules, options) {
    return __awaiter(this, void 0, void 0, function* () {
        debug('installing ' + pkgRawSpec);
        // Preliminary spec data
        // => { raw, name, scope, type, spec, rawSpec }
        const spec = npa(pkgRawSpec);
        // Dependency path to the current package. Not actually needed anmyore
        // outside getting its length
        // => ['babel-core@6.4.5', 'babylon@6.4.5', 'babel-runtime@5.8.35']
        const keypath = (options && options.keypath || []);
        const log = logger.fork(spec).log.bind(null, 'progress', pkgRawSpec);
        try {
            // it might be a bundleDependency, in which case, don't bother
            const available = !options.force && (yield isAvailable_1.default(spec, modules));
            if (available) {
                const fetchedPkg = yield saveCachedResolution();
                fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg));
                fetchedPkg.fetchingFiles.then(() => log('done'));
                return fetchedPkg;
            }
            const res = yield resolve_1.default(spec, {
                log,
                got: options.got,
                root: options.root,
                linkLocal: options.linkLocal,
                tag: options.tag
            });
            log('resolved', res);
            const target = path.join(options.storePath, res.id);
            const pkgPath = path.join(target, '_');
            const justFetched = !ctx.fetchLocks[res.id] &&
                (options.force || !(yield exists(target)) || !ctx.store.packages[res.id]);
            const fetchingFiles = !justFetched && !ctx.fetchLocks[res.id]
                ? Promise.resolve()
                : fetchToStoreCached({
                    fetchLocks: ctx.fetchLocks,
                    target,
                    resolution: res,
                    log,
                    keypath,
                    force: options.force,
                });
            const fetchingPkg = res.pkg
                ? Promise.resolve(res.pkg)
                : fetchingFiles.then(() => requireJson_1.default(path.join(pkgPath, 'package.json')));
            const fetchedPkg = {
                fetchingPkg,
                fetchingFiles,
                id: res.id,
                name: spec.name,
                fromCache: false,
                path: pkgPath,
                srcPath: res.root,
                justFetched,
                abort: function () {
                    return __awaiter(this, void 0, void 0, function* () {
                        try {
                            yield fetchingFiles;
                        }
                        finally {
                            return rimraf(target);
                        }
                    });
                },
            };
            fetchedPkg.fetchingPkg.then(pkg => log('package.json', pkg));
            fetchedPkg.fetchingFiles.then(() => log('done'));
            return fetchedPkg;
        }
        catch (err) {
            log('error', err);
            throw err;
        }
        function saveCachedResolution() {
            return __awaiter(this, void 0, void 0, function* () {
                const target = path.join(modules, spec.name);
                const stat = yield fs.lstat(target);
                if (stat.isSymbolicLink()) {
                    const linkPath = yield fs.readlink(target);
                    return save(path.resolve(linkPath, target));
                }
                return save(target);
                function save(fullpath) {
                    return __awaiter(this, void 0, void 0, function* () {
                        const data = yield requireJson_1.default(path.join(fullpath, 'package.json'));
                        return {
                            fetchingPkg: Promise.resolve(data),
                            fetchingFiles: Promise.resolve(),
                            id: path.basename(fullpath),
                            name: spec.name,
                            fromCache: true,
                            path: fullpath,
                            justFetched: false,
                            abort: () => Promise.resolve(),
                        };
                    });
                }
            });
        }
    });
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = fetch;
/**
 * Fetch to `.store/lodash@4.0.0`
 * If an ongoing build is already working, use it. Also, if that ongoing build
 * is part of the dependency chain (ie, it's a circular dependency), use its stub
 */
function fetchToStoreCached(opts) {
    return memoize_1.default(opts.fetchLocks, opts.resolution.id, function () {
        return __awaiter(this, void 0, void 0, function* () {
            opts.log('download-queued');
            yield opts.resolution.fetch(path.join(opts.target, '_'));
            const pkg = yield requireJson_1.default(path.resolve(path.join(opts.target, '_', 'package.json')));
            opts.log('package.json', pkg);
            // this is not needed on Node.js >= 6.3.0
            if (!env_1.preserveSymlinks) {
                // symlink itself; . -> node_modules/lodash@4.0.0
                // this way it can require itself
                yield symlinkSelf(opts.target, pkg, opts.keypath.length);
            }
        });
    });
}
/**
 * Symlink a package into its own node_modules. this way, babel-runtime@5 can
 * require('babel-runtime') within itself.
 */
function symlinkSelf(target, pkg, depth) {
    return __awaiter(this, void 0, void 0, function* () {
        debug(`symlinkSelf ${pkg.name}`);
        if (depth === 0) {
            return;
        }
        yield mkdirp_1.default(path.join(target, 'node_modules'));
        const src = isScoped(pkg.name)
            ? path.join('..', '..', '_')
            : path.join('..', '_');
        yield link_dir_1.default(src, path.join(target, 'node_modules', pkg.name));
    });
}
function isScoped(pkgName) {
    return pkgName.indexOf('/') !== -1;
}
//# sourceMappingURL=fetch.js.map