"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const micromatch_1 = tslib_1.__importDefault(require("micromatch"));
const shortid_1 = tslib_1.__importDefault(require("shortid"));
const nodes_1 = require("./nodes");
const collection_1 = require("./collection");
const constants_1 = require("../constants");
const git_1 = require("./git");
/**
 * Compare if two commit hashes are equal.
 */
function isCommitEqual(hash1, hash2) {
    return hash1.startsWith(hash2) || hash2.startsWith(hash1);
}
exports.isCommitEqual = isCommitEqual;
/**
 * Remove all Tuture-related files.
 */
async function removeTutureSuite() {
    await fs_extra_1.default.remove(constants_1.TUTURE_ROOT);
}
exports.removeTutureSuite = removeTutureSuite;
function getHiddenLines(diffItem) {
    // Number of context normal lines to show for each diff.
    const context = 3;
    if (diffItem.chunks.length === 0) {
        return [];
    }
    // An array to indicate whether a line should be shown.
    const shownArr = diffItem.chunks[0].changes.map((change) => change.type !== 'normal');
    let contextCounter = -1;
    for (let i = 0; i < shownArr.length; i++) {
        if (shownArr[i]) {
            contextCounter = context;
        }
        else {
            contextCounter--;
            if (contextCounter >= 0) {
                shownArr[i] = true;
            }
        }
    }
    contextCounter = -1;
    for (let i = shownArr.length - 1; i >= 0; i--) {
        if (shownArr[i]) {
            contextCounter = context;
        }
        else {
            contextCounter--;
            if (contextCounter >= 0) {
                shownArr[i] = true;
            }
        }
    }
    return shownArr
        .map((elem, index) => (elem ? null : index))
        .filter((elem) => elem !== null);
}
function convertFile(commit, file, display = false) {
    const diffBlock = {
        type: 'diff-block',
        file: file.to,
        commit,
        hiddenLines: getHiddenLines(file),
        children: nodes_1.emptyChildren,
    };
    const fileObj = {
        type: 'file',
        file: file.to,
        display,
        children: [nodes_1.emptyExplain, diffBlock, nodes_1.emptyExplain],
    };
    return fileObj;
}
/**
 * Store diff data of all commits and return corresponding steps.
 */
async function makeSteps(ignoredFiles) {
    if (!(await git_1.git.branchLocal()).current) {
        // No commits yet.
        return [];
    }
    const logs = (await git_1.git.log({ '--no-merges': true })).all
        .map(({ message, hash }) => ({ message, hash }))
        .reverse()
        // filter out commits whose commit message starts with 'tuture:'
        .filter(({ message }) => !message.startsWith('tuture:'));
    // Store all diff into .tuture/diff.json
    const commits = logs.map(({ hash }) => hash);
    const diffs = await git_1.storeDiff(commits);
    const stepProms = logs.map(async ({ message, hash }) => {
        const diff = diffs.filter((diff) => isCommitEqual(diff.commit, hash))[0];
        const files = diff.diff;
        return {
            type: 'step',
            id: shortid_1.default.generate(),
            articleId: null,
            commit: hash,
            children: [
                {
                    type: 'heading-two',
                    commit: hash,
                    id: shortid_1.default.generate(),
                    fixed: true,
                    children: [{ text: message }],
                },
                nodes_1.emptyExplain,
                ...files.map((diffFile) => {
                    const display = ignoredFiles &&
                        !ignoredFiles.some((pattern) => micromatch_1.default.isMatch(diffFile.to, pattern));
                    return convertFile(hash, diffFile, display);
                }),
                nodes_1.emptyExplain,
            ],
        };
    });
    const steps = await Promise.all(stepProms);
    return steps;
}
exports.makeSteps = makeSteps;
/**
 * Merge previous and current steps. All previous explain will be kept.
 * If any step is rebased out, it will be marked outdated.
 */
function mergeSteps(prevSteps, currentSteps) {
    // Mark steps not included in latest steps as outdated.
    prevSteps.forEach((prevStep) => {
        if (!currentSteps.find(({ commit }) => isCommitEqual(commit, prevStep.commit))) {
            prevStep.outdated = true; /* eslint no-param-reassign: "off"  */
        }
    });
    let [i, j] = [0, 0];
    const mergedSteps = [];
    while (i < prevSteps.length || j < currentSteps.length) {
        if (i >= prevSteps.length) {
            mergedSteps.push(currentSteps[j]);
            j += 1;
        }
        else if (j >= currentSteps.length || prevSteps[i].outdated) {
            mergedSteps.push(prevSteps[i]);
            i += 1;
        }
        else if (isCommitEqual(prevSteps[i].commit, currentSteps[j].commit)) {
            mergedSteps.push(prevSteps[i]);
            i += 1;
            j += 1;
        }
        else {
            mergedSteps.push(currentSteps[j]);
            j += 1;
        }
    }
    return mergedSteps;
}
exports.mergeSteps = mergeSteps;
/**
 * Detect if tuture is initialized.
 */
async function isInitialized() {
    if ((await git_1.git.checkIsRepo()) && (await git_1.git.branchLocal()).current) {
        const workspaceExists = fs_extra_1.default.existsSync(collection_1.collectionPath);
        const branchExists = (await git_1.git.branch({ '-a': true })).all
            .map((branch) => branch.split('/').slice(-1)[0])
            .includes(constants_1.TUTURE_BRANCH);
        return workspaceExists || branchExists;
    }
    return false;
}
exports.isInitialized = isInitialized;
