"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const chalk_1 = require("chalk");
const logUpdate = require("log-update");
const os = require("os");
const prettyBytes = require("pretty-bytes");
const R = require("ramda");
const xstream_1 = require("xstream");
const dropRepeats_1 = require("xstream/extra/dropRepeats");
const flattenConcurrently_1 = require("xstream/extra/flattenConcurrently");
const fromEvent_1 = require("xstream/extra/fromEvent");
const pkgsDiff_1 = require("./pkgsDiff");
const reportError_1 = require("./reportError");
const EOL = os.EOL;
const BIG_TARBALL_SIZE = 1024 * 1024 * 5; // 5 MB
const addedSign = chalk_1.default.green('+');
const removedSign = chalk_1.default.red('-');
const hlValue = chalk_1.default.blue;
const hlPkgId = chalk_1.default['whiteBright'];
function default_1(streamParser) {
    toOutput$(streamParser)
        .subscribe({
        complete() { },
        error: (err) => logUpdate(err.message),
        next: logUpdate,
    });
}
exports.default = default_1;
function toOutput$(streamParser) {
    const obs = fromEvent_1.default(streamParser, 'data');
    const log$ = xstream_1.default.fromObservable(obs);
    const progressLog$ = log$
        .filter((log) => log.name === 'pnpm:progress');
    const resolutionDone$ = log$
        .filter((log) => log.name === 'pnpm:stage' && log.message === 'resolution_done')
        .mapTo(true)
        .take(1)
        .startWith(false);
    const resolvingContentLog$ = progressLog$
        .filter((log) => log.status === 'resolving_content')
        .fold(R.inc, 0)
        .drop(1)
        .endWhen(resolutionDone$.last());
    const fedtchedLog$ = progressLog$
        .filter((log) => log.status === 'fetched')
        .fold(R.inc, 0);
    const foundInStoreLog$ = progressLog$
        .filter((log) => log.status === 'found_in_store')
        .fold(R.inc, 0);
    const alreadyUpToDate$ = xstream_1.default.of(resolvingContentLog$
        .take(1)
        .mapTo(false)
        .startWith(true)
        .last()
        .filter(R.equals(true))
        .mapTo({
        fixed: false,
        msg: 'Already up-to-date',
    }));
    const progressSummaryOutput$ = xstream_1.default.of(xstream_1.default.combine(resolvingContentLog$, fedtchedLog$, foundInStoreLog$, resolutionDone$)
        .map(R.apply((resolving, fetched, foundInStore, resolutionDone) => {
        const msg = `Resolving: total ${hlValue(resolving.toString())}, reused ${hlValue(foundInStore.toString())}, downloaded ${hlValue(fetched.toString())}`;
        if (resolving === foundInStore + fetched && resolutionDone) {
            return {
                fixed: false,
                msg: `${msg}, done`,
            };
        }
        return {
            fixed: true,
            msg,
        };
    })));
    const tarballsProgressOutput$ = progressLog$
        .filter((log) => log.status === 'fetching_started' &&
        typeof log.size === 'number' && log.size >= BIG_TARBALL_SIZE)
        .map((startedLog) => {
        const size = prettyBytes(startedLog['size']);
        return progressLog$
            .filter((log) => log.status === 'fetching_progress' && log.pkgId === startedLog['pkgId'])
            .map((log) => log['downloaded'])
            .startWith(0)
            .map((downloadedRaw) => {
            const done = startedLog['size'] === downloadedRaw;
            const downloaded = prettyBytes(downloadedRaw);
            return {
                fixed: !done,
                msg: `Downloading ${hlPkgId(startedLog['pkgId'])}: ${hlValue(downloaded)}/${hlValue(size)}${done ? ', done' : ''}`,
            };
        });
    });
    const deprecationLog$ = log$
        .filter((log) => log.name === 'pnpm:deprecation');
    const pkgsDiff$ = pkgsDiff_1.default(log$, deprecationLog$);
    const summaryLog$ = log$
        .filter((log) => log.name === 'pnpm:summary')
        .take(1);
    const summaryOutput$ = xstream_1.default.combine(pkgsDiff$, summaryLog$)
        .map(R.apply((pkgsDiff) => {
        let msg = '';
        for (const depType of ['prod', 'optional', 'dev']) {
            const diffs = R.values(pkgsDiff[depType]);
            if (diffs.length) {
                msg += EOL;
                msg += chalk_1.default.blue(`${pkgsDiff_1.propertyByDependencyType[depType]}:`);
                msg += EOL;
                msg += printDiffs(diffs);
                msg += EOL;
            }
        }
        return { msg };
    }))
        .take(1)
        .map(xstream_1.default.of);
    const deprecationOutput$ = deprecationLog$
        .filter((log) => log.depth === 0)
        .map((log) => {
        return {
            msg: formatWarn(`${chalk_1.default.red('deprecated')} ${log.pkgName}@${log.pkgVersion}: ${log.deprecated}`),
        };
    })
        .map(xstream_1.default.of);
    const lifecycleMessages = {};
    const lifecycleOutput$ = xstream_1.default.of(log$
        .filter((log) => log.name === 'pnpm:lifecycle')
        .map((log) => {
        const key = `${log.script}:${log.pkgId}`;
        const keep = lifecycleMessages[key] && lifecycleMessages[key].keep || log.level === 'error';
        const formattedLine = formatLifecycle(log);
        const output = keep && lifecycleMessages[key]
            ? `${lifecycleMessages[key].output}${EOL}${formattedLine}`
            : formattedLine;
        lifecycleMessages[key] = {
            keep,
            output,
        };
        return R.values(lifecycleMessages).map((lm) => lm['output']).join(EOL);
    })
        .map((msg) => ({ msg })));
    const installCheckOutput$ = log$
        .filter((log) => log.name === 'pnpm:install-check')
        .map(formatInstallCheck)
        .filter(Boolean)
        .map((msg) => ({ msg }))
        .map(xstream_1.default.of);
    const registryOutput$ = log$
        .filter((log) => log.name === 'pnpm:registry' && log.level === 'warn')
        .map((log) => ({ msg: formatWarn(log.message) }))
        .map(xstream_1.default.of);
    const miscOutput$ = log$
        .filter((log) => log.name === 'pnpm' || log.name === 'pnpm:link')
        .map((obj) => {
        if (obj.level === 'debug')
            return;
        if (obj.level === 'warn') {
            return formatWarn(obj['message']);
        }
        if (obj.level === 'error') {
            return reportError_1.default(obj);
        }
        return obj['message'];
    })
        .map((msg) => ({ msg }))
        .map(xstream_1.default.of);
    let blockNo = 0;
    let fixedBlockNo = 0;
    let started = false;
    return flattenConcurrently_1.default(xstream_1.default.merge(summaryOutput$, progressSummaryOutput$, registryOutput$, installCheckOutput$, lifecycleOutput$, deprecationOutput$, miscOutput$, tarballsProgressOutput$, alreadyUpToDate$)
        .map((log) => {
        let currentBlockNo = -1;
        let currentFixedBlockNo = -1;
        let calculated = false;
        let fixedCalculated = false;
        return log
            .map((msg) => {
            if (msg['fixed']) {
                if (!fixedCalculated) {
                    fixedCalculated = true;
                    currentFixedBlockNo = fixedBlockNo++;
                }
                return {
                    blockNo: currentFixedBlockNo,
                    fixed: true,
                    msg: msg.msg,
                };
            }
            if (!calculated) {
                calculated = true;
                currentBlockNo = blockNo++;
            }
            return {
                blockNo: currentBlockNo,
                fixed: false,
                msg: typeof msg === 'string' ? msg : msg.msg,
                prevFixedBlockNo: currentFixedBlockNo,
            };
        });
    }))
        .fold((acc, log) => {
        if (log.fixed === true) {
            acc.fixedBlocks[log.blockNo] = log.msg;
        }
        else {
            delete acc.fixedBlocks[log['prevFixedBlockNo']];
            acc.blocks[log.blockNo] = log.msg;
        }
        return acc;
    }, { fixedBlocks: [], blocks: [] })
        .map((sections) => {
        const fixedBlocks = sections.fixedBlocks.filter(Boolean);
        const nonFixedPart = sections.blocks.filter(Boolean).join(EOL);
        if (!fixedBlocks.length) {
            return nonFixedPart;
        }
        const fixedPart = fixedBlocks.join(EOL);
        if (!nonFixedPart) {
            return fixedPart;
        }
        return chalk_1.default.dim(nonFixedPart) + EOL + fixedPart;
    })
        .filter((msg) => {
        if (started) {
            return true;
        }
        if (msg === '')
            return false;
        started = true;
        return true;
    })
        .compose(dropRepeats_1.default());
}
exports.toOutput$ = toOutput$;
function printDiffs(pkgsDiff) {
    // Sorts by alphabet then by removed/added
    // + ava 0.10.0
    // - chalk 1.0.0
    // + chalk 2.0.0
    pkgsDiff.sort((a, b) => (a.name.localeCompare(b.name) * 10 + (Number(!b.added) - Number(!a.added))));
    const msg = pkgsDiff.map((pkg) => {
        let result = pkg.added ? addedSign : removedSign;
        result += ` ${pkg.name}`;
        if (pkg.version) {
            result += ` ${chalk_1.default.grey(pkg.version)}`;
        }
        if (pkg.deprecated) {
            result += ` ${chalk_1.default.red('deprecated')}`;
        }
        return result;
    }).join(EOL);
    return msg;
}
function formatLifecycle(logObj) {
    const prefix = `Running ${hlValue(logObj.script)} for ${hlPkgId(logObj.pkgId)}`;
    if (logObj['exitCode'] === 0) {
        return `${prefix}, done`;
    }
    const line = formatLine(logObj);
    if (logObj.level === 'error') {
        return `${prefix}! ${line}`;
    }
    return `${prefix}: ${line}`;
}
function formatLine(logObj) {
    if (typeof logObj['exitCode'] === 'number')
        return chalk_1.default.red(`Exited with ${logObj['exitCode']}`);
    const color = logObj.level === 'error' ? chalk_1.default.red : chalk_1.default.gray;
    return color(logObj['line']);
}
function formatInstallCheck(logObj) {
    switch (logObj.code) {
        case 'EBADPLATFORM':
            return formatWarn(`Unsupported system. Skipping dependency ${logObj.pkgId}`);
        case 'ENOTSUP':
            return logObj.toString();
        default:
            return;
    }
}
function formatWarn(message) {
    // The \u2009 is the "thin space" unicode character
    // It is used instead of ' ' because chalk (as of version 2.1.0)
    // trims whitespace at the beginning
    return `${chalk_1.default.bgYellow.black('\u2009WARN\u2009')} ${message}`;
}
//# sourceMappingURL=index.js.map