"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const logger_1 = require("@pnpm/logger");
const child_process = require("child_process");
const makeDir = require("make-dir");
const fs = require("mz/fs");
const ncpCB = require("ncp");
const p_limit_1 = require("p-limit");
const path = require("path");
const exists = require("path-exists");
const pathTemp = require("path-temp");
const renameOverwrite = require("rename-overwrite");
const util_1 = require("util");
const linkIndexedDir_1 = require("../fs/linkIndexedDir");
const execFilePromise = util_1.promisify(child_process.execFile);
const ncp = util_1.promisify(ncpCB);
const limitLinking = p_limit_1.default(16);
exports.default = (packageImportMethod) => {
    const importPackage = createImportPackage(packageImportMethod);
    return (filesResponse, dependency, opts) => limitLinking(() => importPackage(filesResponse, dependency, opts));
};
function createImportPackage(packageImportMethod) {
    let fallbackToCopying = false;
    // this works in the following way:
    // - hardlink: hardlink the packages, no fallback
    // - reflink: reflink the packages, no fallback
    // - auto: try to hardlink the packages, if it fails, fallback to copy
    // - copy: copy the packages, do not try to link them first
    switch (packageImportMethod || 'auto') {
        case 'reflink':
            return reflinkPkg;
        case 'hardlink':
            return hardlinkPkg;
        case 'auto':
            return async function importPackage(from, to, opts) {
                if (fallbackToCopying) {
                    await copyPkg(from, to, opts);
                    return;
                }
                try {
                    await hardlinkPkg(from, to, opts);
                }
                catch (err) {
                    if (!err.message.startsWith('EXDEV: cross-device link not permitted'))
                        throw err;
                    logger_1.storeLogger.warn(err.message);
                    logger_1.storeLogger.info('Falling back to copying packages from store');
                    fallbackToCopying = true;
                    await importPackage(from, to, opts);
                }
            };
        case 'copy':
            return copyPkg;
        default:
            throw new Error(`Unknown package import method ${packageImportMethod}`);
    }
}
async function reflinkPkg(from, to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.filesResponse.fromStore || opts.force || !await exists(pkgJsonPath)) {
        core_loggers_1.importingLogger.debug({ from, to, method: 'reflink' });
        const staging = pathTemp(path.dirname(to));
        await makeDir(staging);
        await execFilePromise('cp', ['-r', '--reflink', from + '/.', staging]);
        await renameOverwrite(staging, to);
    }
}
async function hardlinkPkg(from, to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.filesResponse.fromStore || opts.force || !await exists(pkgJsonPath) || !await pkgLinkedToStore(pkgJsonPath, from, to)) {
        core_loggers_1.importingLogger.debug({ from, to, method: 'hardlink' });
        await linkIndexedDir_1.default(from, to, opts.filesResponse.filenames);
    }
}
async function pkgLinkedToStore(pkgJsonPath, from, to) {
    const pkgJsonPathInStore = path.join(from, 'package.json');
    if (await isSameFile(pkgJsonPath, pkgJsonPathInStore))
        return true;
    logger_1.storeLogger.info(`Relinking ${to} from the store`);
    return false;
}
async function isSameFile(file1, file2) {
    const stats = await Promise.all([fs.stat(file1), fs.stat(file2)]);
    return stats[0].ino === stats[1].ino;
}
async function copyPkg(from, to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.filesResponse.fromStore || opts.force || !await exists(pkgJsonPath)) {
        core_loggers_1.importingLogger.debug({ from, to, method: 'copy' });
        const staging = pathTemp(path.dirname(to));
        await makeDir(staging);
        await ncp(from + '/.', staging);
        await renameOverwrite(staging, to);
    }
}
exports.copyPkg = copyPkg;
