"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const template_1 = require("../create/template");
const templateProcessor_1 = require("../create/templateProcessor");
const fs = (0, tslib_1.__importStar)(require("fs-extra"));
const inquirer_1 = (0, tslib_1.__importDefault)(require("inquirer"));
const path_1 = require("path");
const child_process_1 = (0, tslib_1.__importDefault)(require("child_process"));
const abiToScript_1 = (0, tslib_1.__importDefault)(require("../create/abiToScript"));
class Create extends core_1.Command {
    constructor() {
        super(...arguments);
        this.dir = ".";
    }
    async catch(error) {
        // do something or
        // re-throw to be handled globally
        throw error;
    }
    async run() {
        const { flags, args } = await this.parse(Create);
        this.dir = args.directory.indexOf("/") !== 0 ? (0, path_1.resolve)(process.cwd(), args.directory) : args.directory;
        let hardHat = flags.hardHat;
        let template = flags.template;
        if (fs.existsSync(this.dir)) {
            let numFiles = fs.readdirSync(this.dir).length;
            if (numFiles && fs.existsSync((0, path_1.join)(this.dir, "tstemplate.json"))) {
                await this.handleExistingProject();
                return;
            }
        }
        else {
            let proceed = await core_1.CliUx.ux.confirm("Create new workspace at " + this.dir + "?");
            if (!proceed) {
                return;
            }
            fs.mkdirSync(this.dir, { recursive: true });
        }
        if (hardHat) {
            template = Create.hardHatTemplate;
        }
        else if (!template) {
            template = await this.showTemplateSelection();
        }
        let templateDef = template_1.Templates.getTemplateDescriptor(template);
        let values = await this.collectFieldValues(templateDef.templateFields);
        let tokenAddress = this.getContract(values, templateDef);
        let contractABI;
        let abiEncoder = new abiToScript_1.default(this.dir, tokenAddress);
        // If user selected 'Fetch ABI' then attempt to fetch contractABI from etherscan etc
        if (abiEncoder.getABIFetchOption(templateDef.templateFields, values)) {
            core_1.CliUx.ux.action.start("Fetching ABI from Block Scanner...");
            [contractABI, template] = await this.checkFetchABI(template, tokenAddress);
            if (JSON.stringify(contractABI).length <= 2) {
                core_1.CliUx.ux.info("Failed to fetch contract ABI. Generating standard default TokenScript.");
            }
            core_1.CliUx.ux.action.stop();
        }
        core_1.CliUx.ux.action.start("Initializing template...");
        template_1.Templates.copyTemplate(template, this.dir);
        await this.processTemplateUpdate(templateDef, values);
        core_1.CliUx.ux.action.stop();
        // If this is a node-based project, run "npm i" to install dependencies
        if (fs.existsSync((0, path_1.join)(this.dir, "package.json"))) {
            try {
                core_1.CliUx.ux.action.start("Running NPM install...");
                child_process_1.default.execSync("cd " + this.dir + " && npm i");
            }
            catch (e) {
                core_1.CliUx.ux.error(e);
                core_1.CliUx.ux.info("Failed to run 'npm i', please perform this step manually");
            }
        }
        core_1.CliUx.ux.action.stop();
        core_1.CliUx.ux.info("Project successfully initialized!\r\n");
        await this.handleABItoScript(hardHat, templateDef, contractABI, tokenAddress);
        template_1.Templates.cleanBuildFiles(this.dir);
    }
    async checkFetchABI(template, tokenAddress) {
        // pull contract address and chain
        let abiEncoder = new abiToScript_1.default(this.dir, tokenAddress);
        tokenAddress.address = await abiEncoder.getLogicAddress(tokenAddress);
        let contractABIResponse = await abiEncoder.getABI(tokenAddress);
        let contractABI = abiEncoder.convertABI(contractABIResponse);
        if (JSON.stringify(contractABI).length > 2) {
            // handle ABI from block scan
            template = Create.hardHatTemplate;
        }
        return [contractABI, template];
    }
    async handleABItoScript(hardHat, templateDef, contractABI, tokenAddress) {
        let abiEncoder = new abiToScript_1.default(this.dir, tokenAddress);
        if (hardHat) {
            if (!fs.existsSync((0, path_1.join)(hardHat, "hardhat.config.ts"))) {
                core_1.CliUx.ux.error("-h <directory> must point to HardHat project");
            }
            if (!fs.existsSync((0, path_1.join)(hardHat, "artifacts", "contracts"))) {
                core_1.CliUx.ux.error("linked HardHat project must be built");
            }
            let filesToHandle = [];
            let contractABIs = fs.readdirSync((0, path_1.join)(hardHat, "artifacts", "contracts"));
            // Don't ask user if there's only one contract
            if (contractABIs.length > 1) {
                let selection = [];
                for (let thisFile of contractABIs) {
                    selection.push({ name: thisFile, value: thisFile });
                }
                const fileChoice = await inquirer_1.default.prompt([{
                        name: 'fileSelection',
                        message: `Add which files to TokenScript?`,
                        type: 'checkbox',
                        choices: selection,
                    }]);
                filesToHandle = fileChoice.fileSelection;
            }
            else {
                filesToHandle.push(contractABIs[0]);
            }
            await abiEncoder.initFromHardHat(hardHat, filesToHandle);
            let templateProcessor = new templateProcessor_1.TemplateProcessor(templateDef, this.dir);
            await templateProcessor.updateHardHat(hardHat);
            core_1.CliUx.ux.info("Project generation complete!\r\n");
        }
        else if (JSON.stringify(contractABI).length > 2) { // Generate from ABI
            await abiEncoder.start(contractABI, "Token");
            core_1.CliUx.ux.info("Project generation complete!\r\n");
        }
    }
    getContract(values, templateDef) {
        const addressIndex = templateDef.templateFields.findIndex((element) => element.token === 'CONTRACT_ADDRESS');
        const chainIndex = templateDef.templateFields.findIndex((element) => element.token === 'CONTRACT_CHAIN');
        const nameIndex = templateDef.templateFields.findIndex((element) => element.token === 'TOKENSCRIPT_NAME');
        const addressVal = values[addressIndex];
        const chainIdVal = values[chainIndex];
        const nameVal = values[nameIndex];
        const tokenAddress = { chainId: chainIdVal, address: addressVal, name: nameVal };
        return tokenAddress;
    }
    async processTemplateUpdate(templateDef, values) {
        let templateProcessor = new templateProcessor_1.TemplateProcessor(templateDef, this.dir);
        await templateProcessor.processTemplateUpdate(values);
    }
    async handleExistingProject() {
        let templateDef = fs.readJsonSync(this.dir + "/tstemplate.json");
        if (!templateDef)
            throw new Error("Template file is invalid or not readable");
        let proceed = await core_1.CliUx.ux.confirm("Existing workspace detected, would you like to try and update template values?\r\nThis may not work correctly, please save your current workspace before continuing");
        if (!proceed)
            return;
        let values = await this.collectFieldValues(templateDef.templateFields);
        await this.processTemplateUpdate(templateDef, values);
    }
    async showTemplateSelection() {
        let template;
        while (!template) {
            let responses = await inquirer_1.default.prompt([{
                    name: 'template',
                    message: 'Select project template',
                    type: 'list',
                    choices: template_1.Templates.templatesList.map((template) => {
                        return { name: template.name + ": " + template.description, value: template.id };
                    }),
                }]);
            template = responses.template;
            if (!template)
                console.log("\r\nInvalid selection!\r\n");
        }
        return template;
    }
    async collectFieldValues(templateDef) {
        let responses = await inquirer_1.default.prompt(templateDef.map((fieldDef) => {
            const type = fieldDef.options?.length ? "list" : "input";
            const choices = fieldDef.options?.length ? fieldDef.options.map((option) => {
                return { name: option, value: option };
            }) : undefined;
            return {
                name: fieldDef.name,
                message: fieldDef.prompt + ":",
                type,
                choices,
                validate: (val) => {
                    return val != "";
                }
            };
        }));
        let values = [];
        for (let i = 0; i < templateDef.length; i++) {
            let value = responses[templateDef[i].name];
            if (value == undefined)
                throw new Error("Value not provided");
            values.push(responses[templateDef[i].name]);
        }
        return values;
    }
}
exports.default = Create;
Create.description = 'Create a new TokenScript project';
Create.SRC_XML_FILE = "tokenscript.xml";
Create.hardHatTemplate = "hardhat";
Create.flags = {
    // can pass either --force or -f
    template: core_1.Flags.string({ char: "t", options: template_1.Templates.templatesList.map((template) => template.id) }),
    hardHat: core_1.Flags.string({ char: "h", description: 'Directory of HardHat project' }), //TODO: Ask user for HardHat directory if not given
};
Create.args = [
    { name: 'directory', default: process.cwd() }
];
