"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const error_1 = require("@pnpm/error");
const lockfile_file_1 = require("@pnpm/lockfile-file");
const outdated_1 = require("@pnpm/outdated");
const semver_diff_1 = require("@pnpm/semver-diff");
const store_path_1 = require("@pnpm/store-path");
const chalk_1 = require("chalk");
const common_tags_1 = require("common-tags");
const R = require("ramda");
const strip_ansi_1 = require("strip-ansi");
const table_1 = require("table");
const wrapAnsi = require("wrap-ansi");
const createLatestManifestGetter_1 = require("../createLatestManifestGetter");
const readImporterManifest_1 = require("../readImporterManifest");
exports.TABLE_OPTIONS = {
    border: {
        topBody: '─',
        topJoin: '┬',
        topLeft: '┌',
        topRight: '┐',
        bottomBody: '─',
        bottomJoin: '┴',
        bottomLeft: '└',
        bottomRight: '┘',
        bodyJoin: '│',
        bodyLeft: '│',
        bodyRight: '│',
        joinBody: '─',
        joinJoin: '┼',
        joinLeft: '├',
        joinRight: '┤'
    },
    columns: {},
};
for (let [key, value] of Object.entries(exports.TABLE_OPTIONS.border)) {
    exports.TABLE_OPTIONS.border[key] = chalk_1.default.grey(value);
}
/**
 * Default comparators used as the argument to `ramda.sortWith()`.
 */
exports.DEFAULT_COMPARATORS = [
    sortBySemverChange,
    (o1, o2) => o1.packageName.localeCompare(o2.packageName),
];
async function default_1(args, opts, command) {
    const packages = [
        {
            manifest: await readImporterManifest_1.readImporterManifestOnly(opts.prefix, opts),
            path: opts.prefix,
        },
    ];
    const { outdatedPackages } = (await outdatedDependenciesOfWorkspacePackages(packages, args, opts))[0];
    if (!outdatedPackages.length)
        return;
    if (opts.table !== false) {
        return renderOutdatedTable(outdatedPackages, opts);
    }
    else {
        return renderOutdatedList(outdatedPackages, opts);
    }
}
exports.default = default_1;
function renderOutdatedTable(outdatedPackages, opts) {
    let columnNames = [
        'Package',
        'Current',
        'Latest'
    ];
    let columnFns = [
        renderPackageName,
        renderCurrent,
        renderLatest,
    ];
    if (opts.long) {
        columnNames.push('Details');
        columnFns.push(renderDetails);
    }
    // Avoid the overhead of allocating a new array caused by calling `array.map()`
    for (let i = 0; i < columnNames.length; i++)
        columnNames[i] = chalk_1.default.blueBright(columnNames[i]);
    return table_1.table([
        columnNames,
        ...sortOutdatedPackages(outdatedPackages)
            .map((outdatedPkg) => columnFns.map((fn) => fn(outdatedPkg))),
    ], exports.TABLE_OPTIONS);
}
function renderOutdatedList(outdatedPackages, opts) {
    return sortOutdatedPackages(outdatedPackages)
        .map((outdatedPkg) => {
        let info = common_tags_1.stripIndent `
        ${chalk_1.default.bold(renderPackageName(outdatedPkg))}
        ${renderCurrent(outdatedPkg)} ${chalk_1.default.grey('=>')} ${renderLatest(outdatedPkg)}`;
        if (opts.long) {
            const details = renderDetails(outdatedPkg);
            if (details) {
                info += `\n${details}`;
            }
        }
        return info;
    })
        .join('\n\n') + '\n';
}
function sortOutdatedPackages(outdatedPackages) {
    return R.sortWith(exports.DEFAULT_COMPARATORS, outdatedPackages.map(toOutdatedWithVersionDiff));
}
function getCellWidth(data, columnNumber, maxWidth) {
    const maxCellWidth = data.reduce((cellWidth, row) => {
        const cellLines = strip_ansi_1.default(row[columnNumber]).split('\n');
        const currentCellWidth = cellLines.reduce((lineWidth, line) => {
            return Math.max(lineWidth, line.length);
        }, 0);
        return Math.max(cellWidth, currentCellWidth);
    }, 0);
    return Math.min(maxWidth, maxCellWidth);
}
exports.getCellWidth = getCellWidth;
function toOutdatedWithVersionDiff(outdated) {
    if (outdated.latestManifest) {
        return Object.assign(Object.assign({}, outdated), semver_diff_1.default(outdated.wanted, outdated.latestManifest.version));
    }
    return Object.assign(Object.assign({}, outdated), { change: 'unknown' });
}
exports.toOutdatedWithVersionDiff = toOutdatedWithVersionDiff;
function renderPackageName({ belongsTo, packageName }) {
    switch (belongsTo) {
        case 'devDependencies': return `${packageName} ${chalk_1.default.dim('(dev)')}`;
        case 'optionalDependencies': return `${packageName} ${chalk_1.default.dim('(optional)')}`;
        default: return packageName;
    }
}
exports.renderPackageName = renderPackageName;
function renderCurrent({ current, wanted }) {
    let output = current || 'missing';
    if (current === wanted)
        return output;
    return `${output} (wanted ${wanted})`;
}
exports.renderCurrent = renderCurrent;
const DIFF_COLORS = {
    feature: chalk_1.default.yellowBright.bold,
    fix: chalk_1.default.greenBright.bold,
};
function renderLatest(outdatedPkg) {
    const { latestManifest, change, diff } = outdatedPkg;
    if (!latestManifest)
        return '';
    if (change === null || !diff) {
        return latestManifest.deprecated
            ? chalk_1.default.redBright.bold('Deprecated')
            : latestManifest.version;
    }
    const highlight = DIFF_COLORS[change] || chalk_1.default.redBright.bold;
    const same = joinVersionTuples(diff[0], 0);
    const other = highlight(joinVersionTuples(diff[1], diff[0].length));
    if (!same)
        return other;
    if (!other) {
        // Happens when current is 1.0.0-rc.0 and latest is 1.0.0
        return same;
    }
    return diff[0].length === 3 ? `${same}-${other}` : `${same}.${other}`;
}
exports.renderLatest = renderLatest;
function joinVersionTuples(versionTuples, startIndex) {
    const neededForSemver = 3 - startIndex;
    if (versionTuples.length <= neededForSemver || neededForSemver === 0) {
        return versionTuples.join('.');
    }
    return `${versionTuples.slice(0, neededForSemver).join('.')}-${versionTuples.slice(neededForSemver).join('.')}`;
}
function sortBySemverChange(outdated1, outdated2) {
    return pkgPriority(outdated1) - pkgPriority(outdated2);
}
exports.sortBySemverChange = sortBySemverChange;
function pkgPriority(pkg) {
    switch (pkg.change) {
        case null: return 0;
        case 'fix': return 1;
        case 'feature': return 2;
        case 'breaking': return 3;
        default: return 4;
    }
}
function renderDetails({ latestManifest }) {
    if (!latestManifest)
        return '';
    const outputs = [];
    if (latestManifest.deprecated) {
        outputs.push(wrapAnsi(chalk_1.default.redBright(latestManifest.deprecated), 40));
    }
    if (latestManifest.homepage) {
        outputs.push(chalk_1.default.underline(latestManifest.homepage));
    }
    return outputs.join('\n');
}
exports.renderDetails = renderDetails;
async function outdatedDependenciesOfWorkspacePackages(pkgs, args, opts) {
    const lockfileDirectory = opts.lockfileDirectory || opts.prefix;
    const currentLockfile = await lockfile_file_1.readCurrentLockfile(lockfileDirectory, { ignoreIncompatible: false });
    const wantedLockfile = await lockfile_file_1.readWantedLockfile(lockfileDirectory, { ignoreIncompatible: false }) || currentLockfile;
    if (!wantedLockfile) {
        throw new error_1.default('OUTDATED_NO_LOCKFILE', 'No lockfile in this directory. Run `pnpm install` to generate one.');
    }
    const store = await store_path_1.default(opts.prefix, opts.store);
    const getLatestManifest = createLatestManifestGetter_1.default(Object.assign(Object.assign({}, opts), { lockfileDirectory,
        store }));
    return Promise.all(pkgs.map(async ({ manifest, path }) => {
        const optsForOutdated = {
            currentLockfile,
            getLatestManifest,
            lockfileDirectory,
            manifest,
            prefix: path,
            wantedLockfile,
        };
        return {
            manifest,
            outdatedPackages: args.length
                ? await outdated_1.forPackages(args, optsForOutdated)
                : await outdated_1.default(optsForOutdated),
            prefix: lockfile_file_1.getLockfileImporterId(lockfileDirectory, path),
        };
    }));
}
exports.outdatedDependenciesOfWorkspacePackages = outdatedDependenciesOfWorkspacePackages;
