#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const CartoApp_js_1 = require("./../app/CartoApp.js");
const process = require("process");
const NodeStorage_js_1 = require("../local/NodeStorage.js");
const StorageUtilities_js_1 = require("../storage/StorageUtilities.js");
const Constants_js_1 = require("../core/Constants.js");
const ProjectInfoSet_js_1 = require("../info/ProjectInfoSet.js");
const IInfoItemData_js_1 = require("../info/IInfoItemData.js");
const LocalEnvironment_js_1 = require("../local/LocalEnvironment.js");
const IProjectItemData_js_1 = require("../app/IProjectItemData.js");
const Log_js_1 = require("../core/Log.js");
const ClUtils_js_1 = require("./ClUtils.js");
const threads_1 = require("threads");
const ContentIndex_js_1 = require("../core/ContentIndex.js");
const inquirer = require("inquirer");
const IGalleryItem_js_1 = require("../app/IGalleryItem.js");
const ProjectUtilities_js_1 = require("../app/ProjectUtilities.js");
const Project_js_1 = require("../app/Project.js");
const ProjectExporter_js_1 = require("../app/ProjectExporter.js");
const IProjectInfoData_js_1 = require("../info/IProjectInfoData.js");
if (typeof btoa === "undefined") {
    // @ts-ignore
    global.btoa = function (str) {
        return Buffer.from(str, "binary").toString("base64");
    };
}
if (typeof atob === "undefined") {
    // @ts-ignore
    global.atob = function (b64Encoded) {
        return Buffer.from(b64Encoded, "base64").toString("binary");
    };
}
CartoApp_js_1.default.hostType = CartoApp_js_1.HostType.toolsNodejs;
const MAX_LINES_PER_CSV_FILE = 500000;
const ERROR_INIT_ERROR = 44;
const ERROR_VALIDATION_INTERNALPROCESSINGERROR = 53;
const ERROR_VALIDATION_TESTFAIL = 56;
const ERROR_VALIDATION_ERROR = 57;
const program = new commander_1.Command();
let carto;
const projectStarts = [];
let localEnv;
let mode;
let type;
let newName;
let newDescription;
let template;
let creator;
let suite;
let outputType;
let aggregateReportsAfterValidation = false;
let threads = 8;
let errorLevel;
let force = false;
let executionTaskType = ClUtils_js_1.TaskType.noCommand;
program
    .name("mct")
    .description("Minecraft Creator Tools v" + Constants_js_1.constants.version)
    .option("-i, --input-folder [path to folder]", "Path to the input folder. If not specified, the current working directory is used.")
    .option("-if, --input-file [path to file]", "Path to the input MCWorld, MCTemplate, MCPack, MCAddon or other zip file.")
    .option("-o, --output-folder <path to folder>", "Path to the output project folder. If not specified, the current working directory + 'out' is used.", "out")
    .option("-of, --output-file [path to file]", "Path to the export file, if applicable for the command you are using.")
    .option("-ot, --output-type [output type]", "Type of output, if applicable for the command you are using.")
    .option("-bp, --behavior-pack <behavior pack uuid>", "Adds a set of behavior pack UUIDs as references for any worlds that are updated.")
    .option("-rp, --resource-pack <resource pack uuid>", "Adds a set of resources pack UUIDs as references for any worlds that are updated.")
    .option("-betaapis, --beta-apis", "Ensures that the Beta APIs experiment is set for any worlds that are updated.")
    .option("-no-betaapis, --no-beta-apis", "Removes the Beta APIs experiment if set.")
    .option("-f, --force", "Force any updates.")
    .option("-editor", "Ensures that the world is an Editor world.")
    .option("-no-editor", "Removes that the world is an editor.")
    .option("--threads [thread count]", "Targeted number of threads to use.")
    .option("-show, --display-only", "Whether to only show messages, vs. output report files.")
    .option("-lv, --log-verbose", "Whether to show verbose log messages.");
program.addHelpText("before", "\x1b[32m┌─────┐\x1b[0m");
program.addHelpText("before", "\x1b[32m│ ▄ ▄ │\x1b[0m Minecraft Creator Tools (preview) command line");
program.addHelpText("before", "\x1b[32m│ ┏▀┓ │\x1b[0m See " + Constants_js_1.constants.homeUrl + " for more info.");
program.addHelpText("before", "\x1b[32m└─────┘\x1b[0m");
program.addHelpText("before", " ");
program
    .command("world")
    .alias("w")
    .description("Displays/sets world settings within a folder.")
    .addArgument(new commander_1.Argument("[mode]", "If set to 'set', will update the world with a set of properties that are specified."))
    .action((modeIn) => {
    mode = modeIn;
    executionTaskType = ClUtils_js_1.TaskType.world;
});
program
    .command("create")
    .alias("c")
    .description("Creates a new Minecraft project")
    .addArgument(new commander_1.Argument("[name]", "Desired project name"))
    .addArgument(new commander_1.Argument("[template]", "Template name"))
    .addArgument(new commander_1.Argument("[creator]", "Creator name"))
    .addArgument(new commander_1.Argument("[description]", "Project description"))
    .action((nameIn, templateIn, creatorIn, descriptionIn) => {
    newName = nameIn;
    template = templateIn;
    creator = creatorIn;
    newDescription = descriptionIn;
    executionTaskType = ClUtils_js_1.TaskType.create;
});
program
    .command("info")
    .alias("i")
    .description("Displays information about the current project.")
    .action(() => {
    executionTaskType = ClUtils_js_1.TaskType.info;
});
program
    .command("validate")
    .alias("val")
    .description("Validate the current project.")
    .addArgument(new commander_1.Argument("[suite]", "Specifies the type of validation suite to run.")
    .choices(["addon", "currentplatform", "main"])
    .default("main", "main - runs most available validation tests."))
    .addArgument(new commander_1.Argument("[aggregateReports]", "Whether to aggregate reports across projects at the end of the run.")
    .choices(["true", "false"])
    .default("false", "false - does not aggregate reports at the end of processing."))
    .action((suiteIn, aggregateReportsIn) => {
    suite = suiteIn;
    executionTaskType = ClUtils_js_1.TaskType.validate;
    if (aggregateReportsIn === "aggregate" ||
        aggregateReportsIn === "true" ||
        aggregateReportsIn === "1" ||
        aggregateReportsIn === "t") {
        aggregateReportsAfterValidation = true;
    }
    else {
        aggregateReportsAfterValidation = false;
    }
});
program
    .command("aggregatereports")
    .alias("aggr")
    .description("Aggregates exported metadata about projects.")
    .action(() => {
    executionTaskType = ClUtils_js_1.TaskType.aggregateReports;
});
program
    .command("version")
    .alias("ver")
    .description("Shows version and general application information")
    .action(() => {
    executionTaskType = ClUtils_js_1.TaskType.version;
});
program.parse(process.argv);
const options = program.opts();
localEnv = new LocalEnvironment_js_1.default(true);
if (options.force) {
    force = true;
}
if (options.threads) {
    try {
        let tc = parseInt(options.threads);
        if (tc > 0) {
            threads = tc;
            console.log("Using " + threads + " threads.");
        }
    }
    catch (e) {
        Log_js_1.default.error("Could not process the threads parameter: " + e);
    }
}
if (options.outputType && typeof options.outputType === "string") {
    switch (options.outputType.toLowerCase().trim()) {
        case "noreports":
            outputType = ClUtils_js_1.OutputType.noReports;
    }
}
if (options.displayOnly) {
    localEnv.displayInfo = true;
    if (options.outputFolder === "out") {
        options.outputFolder = undefined;
    }
}
else if (options.outputFolder === "out") {
    if (executionTaskType !== ClUtils_js_1.TaskType.create) {
        Log_js_1.default.message("Outputting results to the `out` folder.\r\n");
    }
    localEnv.displayInfo = true;
}
if (options.logVerbose) {
    localEnv.displayVerbose = true;
}
(async () => {
    carto = ClUtils_js_1.default.getCarto(localEnv);
    if (!carto) {
        return;
    }
    await carto.load();
    carto.onStatusAdded.subscribe(ClUtils_js_1.default.handleStatusAdded);
    await loadProjects();
    switch (executionTaskType) {
        case ClUtils_js_1.TaskType.info:
            await displayInfo();
            break;
        case ClUtils_js_1.TaskType.version:
            await displayVersion();
            break;
        case ClUtils_js_1.TaskType.validate:
            await validate();
            break;
        case ClUtils_js_1.TaskType.aggregateReports:
            await aggregateReports();
            break;
        case ClUtils_js_1.TaskType.create:
            try {
                for (const projectStart of projectStarts) {
                    if (projectStart) {
                        await create(ClUtils_js_1.default.createProject(carto, projectStart), projectStarts.length <= 1);
                    }
                }
            }
            catch (e) {
                errorLevel = ERROR_INIT_ERROR;
                console.error("Error creating a project. " + e.toString());
            }
            break;
        case ClUtils_js_1.TaskType.world:
            await setAndDisplayAllWorlds();
            break;
    }
})();
async function loadProjects() {
    if (!carto || !carto.ensureLocalFolder) {
        throw new Error("Not properly configured.");
    }
    if (options.inputFile && options.inputFolder) {
        throw new Error("Cannot specify both an input file and an input folder.");
    }
    if (options.inputFile) {
        const inputFolderPath = StorageUtilities_js_1.default.getFolderPath(options.inputFile);
        const inputFileName = StorageUtilities_js_1.default.getLeafName(options.inputFile);
        if (!inputFileName || inputFileName.length < 2 || !inputFolderPath || inputFolderPath.length < 2) {
            throw new Error("Could not process file with path: `" + options.inputFile + "`");
        }
        const containingFolder = carto.ensureLocalFolder(inputFolderPath);
        const file = containingFolder.ensureFile(inputFileName);
        const fileExists = await file.exists();
        if (!fileExists) {
            throw new Error("Could not find file with path: `" + options.inputFile + "`.");
        }
        const fileName = StorageUtilities_js_1.default.getLeafName(options.inputFile);
        const mainProject = { ctorProjectName: fileName, localFilePath: options.inputFile };
        projectStarts.push(mainProject);
        return;
    }
    const workFolder = await ClUtils_js_1.default.getMainWorkFolder(executionTaskType, options.inputFolder, options.outputFolder);
    const name = StorageUtilities_js_1.default.getLeafName(workFolder.fullPath);
    let isMultiLevelMultiProject = true;
    let foundASubProject = false;
    // multilevel multi project is a one-level folder hierarchy of subfolders with zip and
    // metadata files in them.
    // root folder must be empty
    // foo\coolgame.zip <--- this is the project
    // foo\coolgame.data.json
    // bar\swellgame.zip <--- this is another project
    // bar\swellgame.data.json
    let storageItemCount = 0;
    if (workFolder.fileCount > 0) {
        for (const subFileName in workFolder.files) {
            const file = workFolder.files[subFileName];
            if (file && !StorageUtilities_js_1.default.isFileStorageItem(file)) {
                isMultiLevelMultiProject = false;
                break;
            }
        }
    }
    if (isMultiLevelMultiProject) {
        for (const subFolderName in workFolder.folders) {
            const subFolder = workFolder.folders[subFolderName];
            if (subFolder) {
                await subFolder.load();
                for (const subFileName in subFolder.files) {
                    const subFile = subFolder.files[subFileName];
                    if (subFile) {
                        if (StorageUtilities_js_1.default.isFileStorageItem(subFile)) {
                            storageItemCount++;
                        }
                        let typeFromName = StorageUtilities_js_1.default.getTypeFromName(subFileName);
                        if (!StorageUtilities_js_1.default.isFileStorageItem(subFile) &&
                            typeFromName !== "json" &&
                            typeFromName !== "csv" &&
                            typeFromName !== "" &&
                            typeFromName !== "html") {
                            isMultiLevelMultiProject = false;
                            continue;
                        }
                    }
                }
            }
        }
    }
    if (storageItemCount < 2) {
        isMultiLevelMultiProject = false;
    }
    if (isMultiLevelMultiProject) {
        console.log("Working across subfolders with projects at '" + workFolder.fullPath + "'");
        for (const subFolderName in workFolder.folders) {
            const subFolder = workFolder.folders[subFolderName];
            if (subFolder && !subFolder.errorStatus) {
                await subFolder.load();
                for (const fileName in subFolder.files) {
                    const file = subFolder.files[fileName];
                    if (file && StorageUtilities_js_1.default.isFileStorageItem(file)) {
                        const ps = { ctorProjectName: file.name };
                        let baseName = StorageUtilities_js_1.default.getBaseFromName(file.name);
                        if (subFolder.files[baseName + ".data.json"]) {
                            ps.accessoryFiles = [baseName + ".data.json"];
                        }
                        let lastDash = baseName.lastIndexOf("-");
                        if (lastDash > 0) {
                            baseName = baseName.substring(0, lastDash);
                            if (subFolder.files[baseName + ".data.json"]) {
                                ps.accessoryFiles = [baseName + ".data.json"];
                            }
                        }
                        ps.localFilePath = file.fullPath;
                        projectStarts.push(ps);
                    }
                }
            }
        }
        if (projectStarts.length > 0) {
            return;
        }
    }
    else {
        // "children of folder multi project" scans for either zip files in the root folder and/or
        // every subfolder is a separate project
        let isChildrenOfFolderMultiProject = true;
        for (const fileName in workFolder.files) {
            const file = workFolder.files[fileName];
            if (file) {
                if (!StorageUtilities_js_1.default.isFileStorageItem(file)) {
                    isChildrenOfFolderMultiProject = false;
                    continue;
                }
                else {
                    foundASubProject = true;
                }
            }
        }
        if (isChildrenOfFolderMultiProject) {
            for (const folderName in workFolder.folders) {
                const folder = workFolder.folders[folderName];
                if (folder && !folder.errorStatus) {
                    await folder.load(true);
                    if (folder.files["manifest.json"] ||
                        folder.files["pack_manifest.json"] ||
                        folder.folders["content"] ||
                        folder.folders["Content"] ||
                        folder.folders["world_template"] ||
                        folder.folders["behavior_packs"]) {
                        foundASubProject = true;
                    }
                    if (StorageUtilities_js_1.default.isMinecraftInternalFolder(folder)) {
                        isChildrenOfFolderMultiProject = false;
                        continue;
                    }
                }
            }
        }
        if (!foundASubProject) {
            isChildrenOfFolderMultiProject = false;
        }
        if (isChildrenOfFolderMultiProject) {
            console.log("Working across subfolders/packages at '" + workFolder.fullPath + "'");
            for (const fileName in workFolder.files) {
                const file = workFolder.files[fileName];
                if (file && StorageUtilities_js_1.default.isFileStorageItem(file)) {
                    const mainProject = { ctorProjectName: file.name };
                    mainProject.localFilePath = file.fullPath;
                    projectStarts.push(mainProject);
                }
            }
            for (const folderName in workFolder.folders) {
                const folder = workFolder.folders[folderName];
                if (folder && !folder.errorStatus && folder.name !== "out") {
                    await folder.load();
                    if (folder.folderCount > 0) {
                        const mainProject = { ctorProjectName: folder.name };
                        mainProject.localFolderPath = folder.fullPath;
                        projectStarts.push(mainProject);
                    }
                }
            }
            if (projectStarts.length > 0) {
                return;
            }
        }
    }
    // OK, just assume this folder is a big single project then.
    const mainProject = { ctorProjectName: name };
    mainProject.localFolderPath = workFolder.fullPath;
    projectStarts.push(mainProject);
}
async function displayVersion() {
    console.log("\n" + Constants_js_1.constants.name + " Tools");
    console.log("Version: " + Constants_js_1.constants.version);
    if (carto && carto.local) {
        const local = carto.local;
        console.log("\n");
        console.log("Machine user data path: " + local.userDataPath);
        console.log("Machine app data path: " + local.localAppDataPath);
        console.log("Minecraft path: " + local.minecraftPath);
        console.log("Server working path: " + local.serversPath);
        console.log("Environment prefs path: " + local.envPrefsPath);
    }
    console.log("\n");
    console.log(Constants_js_1.constants.copyright);
    console.log(Constants_js_1.constants.disclaimer);
    console.log("\n");
}
async function displayInfo() {
    for (const projectStart of projectStarts) {
        if (projectStart && carto) {
            const project = ClUtils_js_1.default.createProject(carto, projectStart);
            await project.inferProjectItemsFromFiles();
            console.log("Project name: " + project.name);
            console.log("Project description: " + project.description);
            const bpFolder = await project.getDefaultBehaviorPackFolder();
            if (bpFolder === null) {
                console.log("No default behavior pack.");
            }
            else {
                console.log("Default behavior pack folder: " + bpFolder.storageRelativePath);
            }
            const rpFolder = await project.getDefaultResourcePackFolder();
            if (rpFolder === null) {
                console.log("No default resource pack.");
            }
            else {
                console.log("Default resource pack folder: " + rpFolder.storageRelativePath);
            }
            for (let i = 0; i < project.items.length; i++) {
                const item = project.items[i];
                console.log("=== " + item.typeTitle + ": " + item.projectPath);
                if (item.isWorld) {
                    await setAndDisplayWorld(item, false);
                }
            }
            const pis = project.infoSet;
            await pis.generateForProject();
            for (let i = 0; i < pis.items.length; i++) {
                const item = pis.items[i];
                if (item.itemType !== IInfoItemData_js_1.InfoItemType.testCompleteFail && item.itemType !== IInfoItemData_js_1.InfoItemType.testCompleteSuccess) {
                    console.log(pis.itemToString(item));
                }
            }
        }
    }
}
async function setAndDisplayAllWorlds() {
    const isEnsure = mode === "set";
    let ofName;
    if (options.outputFolder) {
        ofName = StorageUtilities_js_1.default.getLeafName(options.outputFolder);
        if (ofName) {
            ofName = StorageUtilities_js_1.default.canonicalizeName(ofName);
        }
    }
    for (const projectStart of projectStarts) {
        if (projectStart && carto) {
            const project = ClUtils_js_1.default.createProject(carto, projectStart);
            await project.inferProjectItemsFromFiles();
            let setWorld = false;
            for (const item of project.items) {
                if (item.isWorld) {
                    let shouldProcess = true;
                    if (item.projectPath && ofName) {
                        const name = StorageUtilities_js_1.default.canonicalizeName(StorageUtilities_js_1.default.getBaseFromName(StorageUtilities_js_1.default.getLeafName(item.projectPath)));
                        shouldProcess = ofName === name;
                    }
                    if (shouldProcess) {
                        await setAndDisplayWorld(item, isEnsure);
                        setWorld = true;
                    }
                    else {
                        await setAndDisplayWorld(item, false);
                    }
                }
            }
            // create a new world
            if (isEnsure && !setWorld && options.outputFolder) {
                const wcf = await project.ensureWorldContainer();
                if (wcf && project.projectFolder) {
                    // create a new folder for the world.
                    let destF = project.projectFolder;
                    const targetName = destF.name;
                    // if only an output folder is specified, put the world there
                    // if an input and an output folder is specified, put the world at a subfolder of the input folder.
                    if (options.outputFolder) {
                        let targetFolder = options.outputFolder;
                        if (options.inputFolder && targetFolder.startsWith(options.inputFolder)) {
                            targetFolder = targetFolder.substring(options.inputFolder.length);
                        }
                        if (targetFolder.length > 2) {
                            destF = await wcf.ensureFolderFromRelativePath(StorageUtilities_js_1.default.ensureEndsDelimited(targetFolder));
                        }
                    }
                    if (destF) {
                        let path = destF.getFolderRelativePath(project.projectFolder);
                        if (path) {
                            path = StorageUtilities_js_1.default.ensureEndsWithDelimiter(StorageUtilities_js_1.default.absolutize(path));
                            const pi = project.ensureItemByProjectPath(path, IProjectItemData_js_1.ProjectItemStorageType.folder, targetName, IProjectItemData_js_1.ProjectItemType.worldFolder);
                            await pi.ensureFolderStorage();
                            await setAndDisplayWorld(pi, true);
                        }
                    }
                }
            }
        }
    }
}
async function setAndDisplayWorld(item, isSettable) {
    if (item.isWorld) {
        const mcworld = await item.getManager();
        if (mcworld) {
            await mcworld.load(false);
            if (isSettable) {
                if (mcworld.name === "" && mcworld.storageFullPath) {
                    mcworld.name = StorageUtilities_js_1.default.getBaseFromName(StorageUtilities_js_1.default.getLeafName(mcworld.storageFullPath));
                }
                console.log("Updating mcworld at '" + mcworld.storageFullPath + "'");
                const levelDat = mcworld.ensureLevelData();
                let hasSet = false;
                if (options.betaApis !== undefined && options.betaApis !== levelDat.betaApisExperiment) {
                    levelDat.betaApisExperiment = options.betaApis;
                    console.log("Set beta APIs to " + options.betaApis);
                    hasSet = true;
                }
                if (options.editor !== undefined && options.editor !== levelDat.isCreatedInEditor) {
                    levelDat.isCreatedInEditor = options.editor;
                    console.log("Set is editor to " + options.editor);
                    hasSet = true;
                }
                if (options.dataDrivenItems !== undefined && options.dataDrivenItems !== levelDat.dataDrivenItemsExperiment) {
                    levelDat.dataDrivenItemsExperiment = options.dataDrivenItems;
                    console.log("Set data driven items to " + options.dataDrivenItems);
                    hasSet = true;
                }
                if (options.behaviorPack !== undefined) {
                    mcworld.ensureBehaviorPacksFromString(options.behaviorPack);
                }
                if (options.resourcePack !== undefined) {
                    mcworld.ensureBehaviorPacksFromString(options.resourcePack);
                }
                if (hasSet) {
                    await mcworld.save();
                }
            }
            console.log("World name: " + mcworld.name);
            console.log("World path: " + item.projectPath);
            if (mcworld.betaApisExperiment !== undefined) {
                console.log("Beta APIs: " + mcworld.betaApisExperiment);
            }
            if (mcworld.levelData) {
                if (mcworld.levelData.dataDrivenItemsExperiment !== undefined) {
                    console.log("Data Driven items (holiday experimental): " + mcworld.levelData.dataDrivenItemsExperiment);
                }
            }
        }
    }
}
async function validate() {
    if (!carto || !localEnv) {
        return;
    }
    const projectList = [];
    const pool = (0, threads_1.Pool)(() => (0, threads_1.spawn)(new threads_1.Worker("./TaskWorker"), {
        timeout: 25000,
    }), threads);
    const suiteConst = suite;
    const aggregateReportsAfterValidationConst = aggregateReportsAfterValidation;
    const localEnvConst = localEnv;
    for (let i = 0; i < projectStarts.length; i++) {
        const ps = projectStarts[i];
        pool.queue(async (doTask) => {
            const result = await doTask({
                task: ClUtils_js_1.TaskType.validate,
                project: ps,
                arguments: {
                    suite: suiteConst,
                    outputMci: aggregateReportsAfterValidationConst || outputType === ClUtils_js_1.OutputType.noReports ? true : false,
                    outputType: outputType,
                },
                outputFolder: options.outputFolder,
                inputFolder: options.inputFolder,
                displayInfo: localEnvConst.displayInfo,
                displayVerbose: localEnvConst.displayVerbose,
                force: force,
            });
            if (result !== undefined) {
                if (typeof result === "string") {
                    if (ps) {
                        Log_js_1.default.error(ps.ctorProjectName + " error: " + result);
                    }
                }
                else {
                    for (const metaState of result) {
                        // clear out icons since the aggregation won't need them, and it should save memory.
                        if (metaState.infoSetData && metaState.infoSetData.info && metaState.infoSetData.info["defaultIcon"]) {
                            metaState.infoSetData.info["defaultIcon"] = undefined;
                        }
                        projectList.push(metaState);
                        const infoSet = metaState.infoSetData;
                        if (infoSet) {
                            const items = infoSet.items;
                            if (items) {
                                for (const item of items) {
                                    if (item.iTp === IInfoItemData_js_1.InfoItemType.internalProcessingError) {
                                        console.error("Internal Processing Error: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
                                        setErrorLevel(ERROR_VALIDATION_INTERNALPROCESSINGERROR);
                                    }
                                    else if (item.iTp === IInfoItemData_js_1.InfoItemType.testCompleteFail && !options.outputFolder) {
                                        console.error("Test Fail: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
                                        setErrorLevel(ERROR_VALIDATION_TESTFAIL);
                                    }
                                    else if (item.iTp === IInfoItemData_js_1.InfoItemType.error && !options.outputFolder) {
                                        console.error("Error: " + ProjectInfoSet_js_1.default.getEffectiveMessageFromData(infoSet, item));
                                        setErrorLevel(ERROR_VALIDATION_ERROR);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        });
    }
    await pool.settled();
    await pool.terminate();
    if (aggregateReportsAfterValidation) {
        Log_js_1.default.message("Aggregating reports across " + projectList.length + " projects.");
        await saveAggregatedReports(projectList);
    }
}
function setErrorLevel(newErrorLevel) {
    if (errorLevel === undefined || newErrorLevel < errorLevel) {
        errorLevel = newErrorLevel;
        process.exitCode = newErrorLevel;
    }
}
async function aggregateReports() {
    let inpFolder = await ClUtils_js_1.default.getMainWorkFolder(executionTaskType, options.inputFolder, options.outputFolder);
    const allFeatureSets = {};
    const allFields = {};
    const projectList = [];
    if (!inpFolder) {
        Log_js_1.default.message("No main input folder was specified.");
    }
    await inpFolder.load();
    let projectsLoaded = 0;
    for (const fileName in inpFolder.files) {
        let file = inpFolder.files[fileName];
        if (file && StorageUtilities_js_1.default.getTypeFromName(fileName) === "json") {
            await file.loadContent(true);
            let jsonO = await StorageUtilities_js_1.default.getJsonObject(file);
            if (jsonO.info && jsonO.items && jsonO.generatorName !== undefined && jsonO.generatorVersion !== undefined) {
                const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, jsonO.info, jsonO.items);
                let suite = undefined;
                let baseName = StorageUtilities_js_1.default.getBaseFromName(fileName);
                if (baseName.endsWith(".mcr")) {
                    baseName = baseName.substring(0, baseName.length - 4);
                }
                if (baseName.endsWith("addon")) {
                    suite = IProjectInfoData_js_1.ProjectInfoSuite.addOn;
                    baseName = baseName.substring(0, baseName.length - 6);
                }
                if (baseName.endsWith("currentplatform")) {
                    suite = IProjectInfoData_js_1.ProjectInfoSuite.currentPlatformVersions;
                    baseName = baseName.substring(0, baseName.length - 16);
                }
                let title = StorageUtilities_js_1.default.getBaseFromName(fileName);
                let firstDash = title.indexOf("-");
                let lastDash = title.lastIndexOf("-");
                if (firstDash > 0 && lastDash > firstDash + 1) {
                    title = title.substring(firstDash + 1, lastDash);
                }
                if (projectsLoaded > 0 && projectsLoaded % 500 === 0) {
                    console.warn("Loaded " + projectsLoaded + " reports, @ " + title);
                }
                pis.mergeFeatureSetsAndFieldsTo(allFeatureSets, allFields);
                // clear out icons since the aggregation won't need them, and it should save memory.
                if (jsonO.info && jsonO.info["defaultIcon"]) {
                    jsonO.info["defaultIcon"] = undefined;
                }
                projectList.push({
                    projectContainerName: baseName,
                    projectPath: file.parentFolder.storageRelativePath,
                    projectName: baseName,
                    projectTitle: title,
                    infoSetData: jsonO,
                    suite: suite,
                });
                projectsLoaded++;
            }
        }
    }
    if (projectList.length > 0) {
        await saveAggregatedReports(projectList);
    }
    else {
        Log_js_1.default.message("Did not find any report JSON files.");
    }
}
async function saveAggregatedReports(projectList) {
    let outputStorage;
    const csvHeader = ProjectInfoSet_js_1.default.CommonCsvHeader;
    let sampleProjectInfoSets = {};
    const featureSetsByName = {};
    const fieldsByName = {};
    const issueLines = {};
    const summaryLines = {};
    const mciFileList = { files: [], folders: [] };
    const megaContentIndex = new ContentIndex_js_1.default();
    const measures = {};
    const dataMeasures = {};
    if (options.outputFolder) {
        outputStorage = new NodeStorage_js_1.default(options.outputFolder, "");
    }
    let projectsConsidered = 0;
    for (const projectSet of projectList) {
        let suiteName = "all";
        if (projectSet.suite !== undefined) {
            suiteName = ProjectInfoSet_js_1.default.getSuiteString(projectSet.suite);
        }
        const pisData = projectSet.infoSetData;
        const contentIndex = new ContentIndex_js_1.default();
        if (pisData.index) {
            contentIndex.loadFromData(pisData.index);
        }
        const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, pisData.info, pisData.items, contentIndex);
        const projectBaseName = StorageUtilities_js_1.default.removeContainerExtension(projectSet.projectContainerName);
        if (pis.contentIndex) {
            megaContentIndex.mergeFrom(pis.contentIndex, projectBaseName);
        }
        if (projectSet.projectName) {
            mciFileList.files.push(projectBaseName.toLowerCase() + ".mci.json");
        }
        if (projectsConsidered > 0 && projectsConsidered % 500 === 0) {
            console.warn("Processed " + projectsConsidered + " reports, @ " + projectBaseName);
        }
        if (featureSetsByName[suiteName] === undefined) {
            featureSetsByName[suiteName] = {};
        }
        const featureSets = featureSetsByName[suiteName];
        if (fieldsByName[suiteName] === undefined) {
            fieldsByName[suiteName] = {};
        }
        const fields = fieldsByName[suiteName];
        pis.mergeFeatureSetsAndFieldsTo(featureSets, fields);
        projectsConsidered++;
    }
    for (const setName in featureSetsByName) {
        const featureSets = featureSetsByName[setName];
        if (featureSets) {
            for (const featureSetName in featureSets) {
                const featureSet = featureSets[featureSetName];
                if (featureSet) {
                    measures[featureSetName] = {
                        name: featureSetName,
                        items: {},
                    };
                }
            }
        }
    }
    for (const projectSet of projectList) {
        let suiteName = "all";
        if (projectSet.suite !== undefined) {
            suiteName = ProjectInfoSet_js_1.default.getSuiteString(projectSet.suite);
        }
        const pisData = projectSet.infoSetData;
        const projectBaseName = StorageUtilities_js_1.default.removeContainerExtension(projectSet.projectContainerName);
        const pis = new ProjectInfoSet_js_1.default(undefined, undefined, undefined, pisData.info, pisData.items);
        if (featureSetsByName[suiteName] === undefined) {
            featureSetsByName[suiteName] = {};
        }
        const featureSets = featureSetsByName[suiteName];
        if (fieldsByName[suiteName] === undefined) {
            fieldsByName[suiteName] = {};
        }
        const fields = fieldsByName[suiteName];
        pis.mergeFeatureSetsAndFieldsTo(featureSets, fields);
        sampleProjectInfoSets[suiteName] = pis;
        if (projectSet.suite === undefined || projectSet.suite === IProjectInfoData_js_1.ProjectInfoSuite.allExceptAddOn) {
            if (projectSet.infoSetData.info) {
                for (const memberName in projectSet.infoSetData.info) {
                    if (ProjectInfoSet_js_1.default.isAggregableFieldName(memberName)) {
                        let data = dataMeasures[memberName];
                        if (data === undefined) {
                            data = { name: memberName, items: {} };
                            dataMeasures[memberName] = data;
                        }
                        if (projectSet.infoSetData.info[memberName] !== undefined) {
                            data.items[projectBaseName] = projectSet.infoSetData.info[memberName];
                        }
                    }
                }
            }
            if (projectSet.infoSetData.info && projectSet.infoSetData.info.featureSets) {
                for (const featureSetName in featureSets) {
                    const featureSet = projectSet.infoSetData.info.featureSets[featureSetName];
                    if (featureSet) {
                        measures[featureSetName].items[projectBaseName] = featureSet;
                    }
                }
            }
        }
        if (issueLines[suiteName] === undefined) {
            issueLines[suiteName] = [];
        }
        if (issueLines[suiteName].length <= MAX_LINES_PER_CSV_FILE) {
            const pisLines = pis.getItemCsvLines();
            for (let j = 0; j < pisLines.length; j++) {
                issueLines[suiteName].push('"' + projectBaseName + '",' + pisLines[j]);
            }
        }
        if (summaryLines[suiteName] === undefined) {
            summaryLines[suiteName] = [];
        }
        if (outputStorage) {
            summaryLines[suiteName].push(pis.getSummaryCsvLine(projectBaseName, projectSet.projectTitle, featureSets));
        }
    }
    if (outputStorage) {
        for (const issueLinesName in issueLines) {
            if (sampleProjectInfoSets[issueLinesName]) {
                const issueLinesSet = issueLines[issueLinesName];
                if (issueLinesSet.length < MAX_LINES_PER_CSV_FILE) {
                    let allCsvFile = outputStorage.rootFolder.ensureFile(issueLinesName + ".csv");
                    let allCsvContent = "Project," + csvHeader + "\r\n" + issueLinesSet.join("\n");
                    allCsvFile.setContent(allCsvContent);
                    await allCsvFile.saveContent();
                }
            }
        }
        for (const summaryLinesName in summaryLines) {
            if (featureSetsByName[summaryLinesName] === undefined) {
                featureSetsByName[summaryLinesName] = {};
            }
            if (sampleProjectInfoSets[summaryLinesName]) {
                const featureSets = featureSetsByName[summaryLinesName];
                const summaryLinesSet = summaryLines[summaryLinesName];
                const projectsCsvFile = outputStorage.rootFolder.ensureFile(summaryLinesName + "projects.csv");
                let projectsCsvContent = ProjectInfoSet_js_1.default.getSummaryCsvHeaderLine(sampleProjectInfoSets[summaryLinesName].info, featureSets);
                projectsCsvContent += "\r\n" + summaryLinesSet.join("\n");
                projectsCsvFile.setContent(projectsCsvContent);
                await projectsCsvFile.saveContent();
            }
        }
    }
}
async function create(project, isSingleFolder) {
    outputLogo("Minecraft Creator Tools (preview)");
    if (!carto) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("Not configured correctly to create a project (no mctools core).");
        return;
    }
    await carto.loadGallery();
    if (!carto.gallery) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("Not configured correctly to create a project (no gallery).");
        return;
    }
    let title = newName;
    if (!newName) {
        const titleQuestions = [];
        titleQuestions.push({
            type: "input",
            name: "title",
            default: "My Project",
            message: "What's your preferred project title?",
        });
        const titleAnswer = await inquirer.prompt(titleQuestions);
        if (titleAnswer["title"]) {
            title = titleAnswer["title"];
        }
    }
    if (!title) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("No title for your project was selected.");
        return;
    }
    let applyDescription = newDescription;
    if (applyDescription === undefined) {
        applyDescription = title;
        const descriptionQuestions = [];
        descriptionQuestions.push({
            type: "input",
            name: "description",
            default: applyDescription,
            message: "What's your preferred project description?",
        });
        const descriptionAnswer = await inquirer.prompt(descriptionQuestions);
        if (descriptionAnswer["description"]) {
            applyDescription = descriptionAnswer["description"];
        }
        if (applyDescription === undefined) {
            applyDescription = title;
        }
    }
    if (!creator) {
        const creatorQuestions = [];
        creatorQuestions.push({
            type: "input",
            name: "creator",
            default: "Creator",
            message: "What's your creator name?",
        });
        const creatorAnswer = await inquirer.prompt(creatorQuestions);
        if (creatorAnswer["creator"]) {
            creator = creatorAnswer["creator"];
        }
    }
    const questions = [];
    if (!newName) {
        newName = title?.replace(/ /gi, "-").toLowerCase();
        questions.push({
            type: "input",
            name: "name",
            default: newName,
            message: "What's your preferred project short name? (<20 chars, no spaces)",
        });
    }
    if (!options.inputFolder && (!options.outputFolder || options.outputFolder === "out") && isSingleFolder) {
        const folderNameQuestions = [];
        folderNameQuestions.push({
            type: "input",
            name: "folderName",
            default: newName,
            message: "What's your preferred folder name?",
        });
        const folderNameAnswer = await inquirer.prompt(folderNameQuestions);
        if (folderNameAnswer["folderName"]) {
            const folderName = folderNameAnswer["folderName"];
            if (folderName) {
                const path = NodeStorage_js_1.default.ensureEndsWithDelimiter(process.cwd()) + NodeStorage_js_1.default.ensureEndsWithDelimiter(folderName);
                const outputStorage = new NodeStorage_js_1.default(path, "");
                const outFolder = outputStorage.rootFolder;
                await outFolder.ensureExists();
                //project = new Project(carto, project.name, null);
                project.localFolderPath = path;
                project.autoDeploymentMode = Project_js_1.ProjectAutoDeploymentMode.noAutoDeployment;
            }
        }
    }
    const galProjects = carto.gallery.items;
    let galProject;
    if (template) {
        for (let i = 0; i < galProjects.length; i++) {
            const galProjectCand = galProjects[i];
            if (galProjectCand && galProjectCand.id && galProjectCand.id.toLowerCase() === template.toLowerCase()) {
                galProject = galProjectCand;
            }
        }
    }
    if (!galProject) {
        const projectTypeChoices = [];
        for (let i = 0; i < galProjects.length; i++) {
            const galProjectCand = galProjects[i];
            if (galProjectCand.type === IGalleryItem_js_1.GalleryItemType.project)
                projectTypeChoices.push({
                    name: galProjectCand.id + ": " + galProjectCand.title,
                    value: i,
                });
        }
        questions.push({
            type: "list",
            name: "projectSource",
            message: "What template should we use?",
            choices: projectTypeChoices,
        });
    }
    if (!galProject || !newName) {
        const answers = await inquirer.prompt(questions);
        if (answers) {
            if (answers["name"]) {
                newName = answers["name"];
            }
            if (!galProject) {
                galProject = galProjects[answers["projectSource"]];
            }
        }
    }
    if (!newName) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("Not configured correctly to create a project.");
        return;
    }
    if (!galProject) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("No project was selected.");
        return;
    }
    project = await ProjectExporter_js_1.default.syncProjectFromGitHub(true, carto, galProject.gitHubRepoName, galProject.gitHubOwner, galProject.gitHubBranch, galProject.gitHubFolder, newName, project, galProject.fileList, async (message) => {
        Log_js_1.default.message(message);
    }, true);
    let suggestedShortName = undefined;
    if (newName && creator) {
        suggestedShortName = ProjectUtilities_js_1.default.getSuggestedProjectShortName(creator, newName);
    }
    if (creator) {
        await ProjectUtilities_js_1.default.applyCreator(project, creator);
    }
    await ProjectUtilities_js_1.default.processNewProject(project, title, applyDescription, suggestedShortName);
    console.log("\nAll done! Now run \x1b[47m\x1b[30mnpm i\x1b[37m\x1b[40m in the \x1b[47m\x1b[30m" +
        project.projectFolder?.fullPath +
        "\x1b[37m\x1b[40m folder to install dependencies, if any, from npm.");
}
async function add(project) {
    let typeDesc = "Item";
    if (type) {
        switch (type) {
            case "entity":
                typeDesc = "Entity";
                break;
            case "item":
                typeDesc = "Item";
                break;
            case "block":
                typeDesc = "Block";
                break;
            default:
                console.log("Unknown item type '" + typeDesc + "' specified.");
                type = undefined;
                break;
        }
    }
    outputLogo("Minecraft Add " + typeDesc);
    if (!carto) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("Not configured correctly to add an item (no mctools core).");
        return;
    }
    await carto.loadGallery();
    if (!carto.gallery) {
        errorLevel = ERROR_INIT_ERROR;
        console.error("Not configured correctly to add an item (no gallery).");
        return;
    }
    const typeQuestions = [];
    const questions = [];
    const galProjects = carto.gallery.items;
    let galProject;
    if (template) {
        for (let i = 0; i < galProjects.length; i++) {
            const galProjectCand = galProjects[i];
            if (galProjectCand && galProjectCand.id && galProjectCand.id.toLowerCase() === template.toLowerCase()) {
                galProject = galProjectCand;
            }
        }
    }
    if (type === undefined) {
        typeQuestions.push({
            type: "list",
            name: "type",
            message: "What type of item should we add?",
            choices: [
                { name: "Entity Type", value: "entity" },
                { name: "Block Type", value: "block" },
                { name: "Item Type", value: "item" },
            ],
        });
        const typeAnswers = await inquirer.prompt(typeQuestions);
        type = typeAnswers["type"];
    }
    if (type === "entity") {
        const gallery = await carto.loadGallery();
        if (gallery) {
            const entityTypeChoices = [];
            for (const proj of gallery.items) {
                if (proj.type === IGalleryItem_js_1.GalleryItemType.entityType) {
                    entityTypeChoices.push({
                        name: proj.title,
                        value: proj.id,
                    });
                }
            }
            questions.push({
                type: "list",
                name: "entityType",
                message: "Based on which entity type?",
                choices: entityTypeChoices,
            });
            if (!newName) {
                questions.push({
                    type: "input",
                    name: "name",
                    message: "What's your preferred new item name? (<20 chars, no spaces)",
                });
            }
            const answers = await inquirer.prompt(questions);
            if (!newName) {
                newName = answers["name"];
            }
            const entityType = answers["entityType"];
            if (entityType) {
                for (const proj of gallery.items) {
                    if (proj.id === entityType) {
                        await ProjectUtilities_js_1.default.addEntityTypeFromGallery(project, proj, newName, ProjectUtilities_js_1.NewEntityTypeAddMode.baseId, async (message) => {
                            Log_js_1.default.message(message);
                        });
                    }
                }
            }
        }
        else {
            errorLevel = ERROR_INIT_ERROR;
            console.error("Could not find any entity types.");
        }
    }
    else {
        if (!galProject) {
            const projectTypeChoices = [];
            for (let i = 0; i < galProjects.length; i++) {
                const galProjectCand = galProjects[i];
                if (galProjectCand.type === IGalleryItem_js_1.GalleryItemType.chunk) {
                    projectTypeChoices.push({
                        name: galProjectCand.id + ": " + galProjectCand.title,
                        value: i,
                    });
                }
            }
            questions.push({
                type: "list",
                name: "projectSource",
                message: "What type of item should we add?",
                choices: projectTypeChoices,
            });
        }
        if (!newName) {
            questions.push({
                type: "input",
                name: "name",
                message: "What's your preferred new item name? (<20 chars, no spaces)",
            });
        }
        const answers = await inquirer.prompt(questions);
        if (!newName) {
            newName = answers["name"];
        }
        if (!galProject) {
            galProject = galProjects[answers["projectSource"]];
        }
        await project.ensureDefaultBehaviorPackFolder();
        project = await ProjectExporter_js_1.default.syncProjectFromGitHub(false, carto, galProject.gitHubRepoName, galProject.gitHubOwner, galProject.gitHubBranch, galProject.gitHubFolder, project.name, project, galProject.fileList, async (message) => {
            Log_js_1.default.message(message);
        }, true);
    }
    await project.save();
}
function outputLogo(message) {
    console.log("\x1b[32m┌─────┐\x1b[0m");
    console.log("\x1b[32m│ ▄ ▄ │\x1b[0m " + message);
    console.log("\x1b[32m│ ┏▀┓ │\x1b[0m");
    console.log("\x1b[32m└─────┘\x1b[0m");
    console.log("");
}

//# sourceMappingURL=../maps/cli/index.js.map
