"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const YAML = require("yaml");
const _ = require("lodash");
const contracts_1 = require("@codiac.io/codiac-common/contracts");
const codiac_common_1 = require("@codiac.io/codiac-common");
const codiac_domain_1 = require("@codiac.io/codiac-domain");
const ops_1 = require("../../../ops");
const contracts_2 = require("../../../apis/codiac-api/contracts");
const command_base_enterprise_1 = require("../../../command-base-enterprise");
const render = require("../../../uilogic/render-methods");
const render_methods_1 = require("../../../uilogic/render-methods");
const config_ui_utils_1 = require("../../../uilogic/config-ui-utils");
const INTERFACES_CLI_1 = require("../../../ops/INTERFACES_CLI");
const type_safety_utils_1 = require("../../../uilogic/type-safety-utils");
class ConfigSettingsGet extends command_base_enterprise_1.CommandBaseEnterprise {
    constructor() {
        super(...arguments);
        // protected takeDefaults: boolean = false;
        /** CLI Command-line string to rerun this execution as a non-TTY script. */
        this.cmd = ["cod", "config", "settings", "get", "--silent"];
    }
    // --------------------------------------------------------------------
    // Lazy-loaded properties
    get configUtils() {
        if (this._configUtils_backer === undefined) {
            this._configUtils_backer = this.ioc.get(config_ui_utils_1.ConfigUiUtils);
        }
        return this._configUtils_backer;
    }
    // --------------------------------------------------------------------
    bootstrap() {
        super.bootstrap();
        this.ioc.bind(config_ui_utils_1.ConfigUiUtils).toSelf();
    }
    run() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            this.bootstrap();
            yield this.readStdin();
            try {
                // 1. CAPTURE ARGS
                let [partial, interactionMode, outputFormat, takeDefaults] = yield this.captureArgs();
                // 0. SET DEFAULTS
                if (interactionMode == config_ui_utils_1.InteractionModes.silent) {
                    // this.ioc.rebind<IUserInputProvider>(INTERFACES_CLI.IUserInputProvider).to(ThrowingUserInputProvider);  // Need to write output from diff method than UserMessage
                    this.ioc.rebind(INTERFACES_CLI_1.INTERFACES_CLI.IOutputStyler).to(ops_1.NoStyleOutputStyler);
                }
                this._userCntx = yield this.establishTenantContext(this._userCntx);
                let rootMap = yield this.getRootMap();
                let currentEnterprise = (yield this.establishEnterpriseContext(rootMap, partial.enterprise, interactionMode)).enterprise; // We only need this bc the RootMap requires it.
                _.remove(rootMap.enterprises, e => e.name != currentEnterprise.name); // trim down to match legacy conditions
                partial.enterprise = currentEnterprise.name;
                // 2. VALIDATE AND ACQUIRE INPUT
                let candidate;
                if (interactionMode == config_ui_utils_1.InteractionModes.silent) {
                    candidate = this.buildCandidate_silently(partial, rootMap);
                }
                else {
                    candidate = yield this.buildCandidate_interactively(partial, rootMap, takeDefaults);
                }
                // In case we're hanging waiting for piped-in data that doesnt exist... 
                if (!process.stdin.destroyed)
                    process.stdin.destroy();
                if (candidate == undefined) {
                    this.logger.notice(`Operation cancelled. No work was done.`);
                    this._ui.userMessage(`Operation cancelled. No work was done.`);
                    this.exit(0);
                }
                else {
                    // 3. RENDER COMMAND STRING
                    if (interactionMode != undefined && [config_ui_utils_1.InteractionModes.echo, config_ui_utils_1.InteractionModes.toscript].includes(interactionMode)) {
                        this.cmd = this.buildCommandString(candidate, outputFormat, this.cmd);
                        this._ui.userMessage(this.cmd.join(" "));
                    }
                    if (interactionMode != config_ui_utils_1.InteractionModes.toscript) {
                        // 4. EXECUTE THE MAIN TASK
                        yield this.executeTask(candidate, outputFormat);
                    }
                }
                return;
            }
            catch (err) {
                // throw err;
                this.handleError(err);
                return;
            }
            finally {
                yield this.closeConnections();
            }
        });
    }
    /** Captures command args and flags and converts them to valid, operative variables.
     *
     * @returns `[query obj, interactionMode, output format, take-defaults]` */
    captureArgs() {
        var _a, _b, _c, _d, _e, _f;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { args, flags } = yield this.parse(ConfigSettingsGet);
            let partial = {};
            partial.enterprise = flags.enterprise;
            partial.asset = flags.asset;
            if (flags.type) {
                partial.configType = type_safety_utils_1.TypeSafetyUtils.toEnum(codiac_domain_1.SpecialConfigTypesEnum, flags.type);
                if (partial.configType == undefined)
                    delete partial.configType;
            }
            else if (flags.file && !_.isEmpty(flags.file)) {
                partial.targetFile = flags.file;
            }
            partial.scope =
                (_b = (_a = (flags['enterprise-scope'] ? contracts_2.ScopeLevel.enterprise : undefined)) !== null && _a !== void 0 ? _a : (flags.enviro ? contracts_2.ScopeLevel.environment : undefined)) !== null && _b !== void 0 ? _b : (flags.cabinet ? contracts_2.ScopeLevel.cabinet : undefined);
            partial.target =
                (_d = (_c = (flags['enterprise-scope'] ? partial.enterprise : undefined)) !== null && _c !== void 0 ? _c : flags.enviro) !== null && _d !== void 0 ? _d : flags.cabinet;
            if (flags.setting)
                partial.setting = flags.setting;
            if (flags.value)
                partial.value = flags.value;
            const interactionMode = (_f = (_e = (flags.echo ? config_ui_utils_1.InteractionModes.echo : undefined)) !== null && _e !== void 0 ? _e : (flags["to-script"] ? config_ui_utils_1.InteractionModes.toscript : undefined)) !== null && _f !== void 0 ? _f : (flags.silent ? config_ui_utils_1.InteractionModes.silent : undefined);
            const takeDefaults = flags['take-defaults'];
            // this.takeDefaults = flags["--take-defaults"] ?? false;
            let outputFormat = (flags.output) ? type_safety_utils_1.TypeSafetyUtils.toEnum(render_methods_1.OutputFormatsEnum, flags.output) : render_methods_1.OutputFormatsEnum.json;
            if (outputFormat == undefined)
                throw new Error("Unrecognized output format.");
            return [partial, interactionMode, outputFormat, takeDefaults];
        });
    }
    buildCandidate_silently(partial, rootMap) {
        let output;
        try {
            if (partial.enterprise == undefined)
                throw new Error("Must specify the enterprise of the asset.");
            let ent = rootMap.enterprises.find(x => x.name === partial.enterprise);
            if (ent == undefined)
                throw new Error(`No enterprise was found with that name.`);
            if (partial.asset == undefined)
                throw new Error("Must specify the asset.");
            let asset = ent.assets.find(x => x.name === partial.asset);
            if (asset === undefined)
                throw new Error(`No asset was found with that name.`);
            if (partial.targetFile == undefined && partial.configType == undefined)
                throw new Error("Must specify the file or type.");
            partial.thisMachine = this.configUtils.validateScope(rootMap, partial.enterprise, partial.scope, partial.target);
            // output = {
            //   enterprise: partial.enterprise,
            //   asset: partial.asset,
            //   targetFile: partial.targetFile,
            //   scope: partial.scope,
            //   target: partial.target
            // };
            output = partial;
            return output;
        }
        catch (err) {
            throw err;
        }
    }
    buildCandidate_interactively(partial, rootMap, takeDefaults = false) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let output;
            try {
                // Enterprise
                let ent = yield this.configUtils.promptForEnterprise(partial, rootMap);
                partial.enterprise = ent.name; // This is just for TS context (the method above already set this).
                // Asset
                let assetChanged;
                let assetType;
                [partial.asset, assetChanged, assetType] = yield this.configUtils.promptForAsset(ent.assets, partial.asset);
                // Type/File
                // TODO: Can we include the configDefs in the RootMap?
                let typeFileChanged;
                let existingSettings;
                [partial.configType, partial.targetFile, existingSettings, typeFileChanged] = yield this.configUtils.promptForTypeAndFile(partial.enterprise, partial.asset, assetType, partial.configType, partial.targetFile, undefined, undefined, takeDefaults);
                // Scope
                let targetChanged = false;
                [partial.scope, partial.target, targetChanged, partial.thisMachine] = yield this.configUtils.validateAndPromptForScope(rootMap, partial.enterprise, partial.scope, partial.target, existingSettings, false);
                // Setting names?
                // Setting values?
                // value vs Secret/Ref?
                output = partial;
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    buildCommandString(ultimate, outputFormat, cmd) {
        cmd.push("-o", outputFormat);
        cmd.push("--enterprise", ultimate.enterprise);
        cmd.push("-a", ultimate.asset);
        if (ultimate.configType) {
            cmd.push("-t", ultimate.configType);
        }
        else {
            cmd.push("-f", ultimate.targetFile); // assertion is ok bc we forced one or the other just above ^.
        }
        if (ultimate.scope) {
            if (ultimate.scope == contracts_2.ScopeLevel.enterprise) {
                cmd.push("--enterprise-scope");
            }
            else if (ultimate.target) {
                if (ultimate.scope == contracts_2.ScopeLevel.environment)
                    cmd.push("-e", ultimate.target);
                if (ultimate.scope == contracts_2.ScopeLevel.cabinet)
                    cmd.push("-c", ultimate.target);
            }
        }
        if (ultimate.setting)
            cmd.push("--setting", ultimate.setting);
        if (ultimate.value)
            cmd.push("--value", ultimate.value);
        return cmd;
    }
    executeTask(candidate, outputFormat) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // 4. RETRIEVE 
            let settings = yield this.fetch(candidate, outputFormat);
            // 5. RENDER
            this.render(settings, outputFormat);
        });
    }
    fetch(candidate, outputFormat) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                if ([render_methods_1.OutputFormatsEnum.json, render_methods_1.OutputFormatsEnum.yaml].includes(outputFormat) === false)
                    this._ui.startSpinner("Retrieving");
                let crit = this.convertToCriteria(candidate);
                let prjxn = { pathsIncluded: ["setting", "value", "source"], pathsExcluded: [] };
                const options = { throwNotFound: false, projection: prjxn, thisMachine: candidate.thisMachine };
                let settings = yield this._codiacApi.ScopedSetting.readMany(crit, options);
                settings = _.sortBy(settings, x => x.enterprise, x => x.asset, x => x.targetFile, x => x.setting); // TODO: sort in the crit
                // TODO: COnsider moving this into the api?
                settings.forEach(s => {
                    delete s.id;
                    if (s.source != undefined && !_.isEmpty(s.source)) {
                        s.value = this.configUtils.serializeSourcedValue(s.source);
                        delete s.source;
                    }
                    else if (_.isEmpty(s.source))
                        delete s.source;
                });
                if ([render_methods_1.OutputFormatsEnum.json, render_methods_1.OutputFormatsEnum.yaml].includes(outputFormat) === false)
                    this._ui.stopSpinner(`found ${settings.length}`);
                return settings;
            }
            catch (err) {
                throw err;
            }
        });
    }
    convertToCriteria(candidate) {
        var _a;
        let crit = {};
        crit.enterprise = new contracts_1.StringFilter();
        crit.enterprise.$eq = candidate.enterprise;
        crit.asset = new contracts_1.StringFilter();
        crit.asset.$eq = candidate.asset;
        crit.targetFile = new contracts_1.StringFilter();
        crit.targetFile.$eq = (_a = (candidate.configType ? `[${candidate.configType}]` : undefined)) !== null && _a !== void 0 ? _a : candidate.targetFile;
        if (candidate.scope && !_.isEmpty(candidate.scope)) {
            crit.scope = candidate.scope;
            if (candidate.target && !_.isEmpty(candidate.target))
                crit.target = candidate.target;
        }
        if ((candidate.setting != undefined) && (!_.isEmpty(candidate.setting))) {
            crit.setting = candidate.setting;
        }
        // if ((candidate.value != undefined) && (!_.isEmpty(candidate.value))) {
        //   crit.value = candidate.value;
        // }
        // crit.sort = ["enterprise", "asset", "targetFile", "setting", "scope"];
        return crit;
    }
    /** Renders the given settings as a hierarchical config doc (NOT a list of key-value pairs) */
    render(settings, outputFormat) {
        let output = "";
        // Unflatten into a config doc
        let doc = {};
        for (let setting of settings) {
            doc[setting.setting] = setting.value;
        }
        doc = codiac_common_1.ObjectFlattener.unflatten(doc);
        switch (outputFormat) {
            case render_methods_1.OutputFormatsEnum.yaml:
                output = YAML.stringify(doc, { indent: 2 });
                break;
            case render_methods_1.OutputFormatsEnum.json:
                output = JSON.stringify(doc, undefined, 2);
                break;
            case render_methods_1.OutputFormatsEnum.text:
                let list = settings.map(s => `${this._styler.dim(s.setting)}:  ${s.value}`);
                output = render.renderStringList(list);
                break;
            case render_methods_1.OutputFormatsEnum.tree:
            default:
                let tree = this.configUtils.buildConfigTree(settings);
                output = render.renderStringTree(tree);
                break;
        }
        this._ui.userMessage(output);
    }
}
exports.default = ConfigSettingsGet;
ConfigSettingsGet.description = 'Shows the list of clusters.';
ConfigSettingsGet.aliases = [];
ConfigSettingsGet.flags = {
    enterprise: core_1.Flags.string({ char: 'n', description: '(required only when more than one enterprise exists) The name of the enterprise containing the asset.' }),
    asset: core_1.Flags.string({ char: 'a', description: 'The name of the asset to which the settings belong.' }),
    file: core_1.Flags.string({ char: 'f', exclusive: ["type"], description: 'The path-name to the file to which the settings belong, relative to the root of the container image.' }),
    type: core_1.Flags.string({ char: 't', exclusive: ["file"], options: Object.keys(codiac_domain_1.SpecialConfigTypesEnum), description: 'Filters for the settings types that do not land in a config file (such as... env: OS environment variables, footprint: container footprint specs, helm: helm chart settings, label: container meta labels, annotation: container platform key-value pairs).' }),
    "enterprise-scope": core_1.Flags.boolean({ char: 's', exclusive: ["enviro", "cabinet"], description: 'Filters for settings scoped at the enterprise level.' }),
    enviro: core_1.Flags.string({ char: 'e', exclusive: ["enterprise-scope", "cabinet"], description: 'Filters for settings scoped to the given environment name.' }),
    cabinet: core_1.Flags.string({ char: 'c', exclusive: ["enterprise-scope", "enviro"], description: 'Filters for settings scoped to the given cabinet name.' }),
    setting: core_1.Flags.string({ description: 'Glob for settings names. Filters for individual settings with matching names.' }),
    value: core_1.Flags.string({ description: 'Glob for settings values. Filters for individual settings with matching values.' }),
    output: core_1.Flags.string({ char: 'o', default: render_methods_1.OutputFormatsEnum.json, description: "format in which to render the data.", options: Object.keys(render_methods_1.OutputFormatsEnum) }),
    silent: core_1.Flags.boolean({ exclusive: ["echo", "to-script"], description: 'Non-Interactive mode; executes without any user interaction and fails on any missing or invalid arguments.' }),
    echo: core_1.Flags.boolean({ exclusive: ["to-script", "silent"], description: 'Echo interaction mode; renders the equivalent non-interactive cli command for future use before final execution.' }),
    "to-script": core_1.Flags.boolean({ exclusive: ["echo", "silent"], description: 'Toscript interaction mode; renders the equivalent non-interactive cli command WITHOUT any execution.' }),
    "take-defaults": core_1.Flags.boolean({ exclusive: ["silent"], description: "Setting this to true prevents prompting for confirmation on parameters that were passed in or were automatically set to default values (Irrelevant in --silent mode).  This behavior does NOT apply to scenarios that the command needs to override invalid user input; such cases will always prompt for confirmation.  eg: when no argument was provided for file or type, and only one exists, the system will render the autoselected file unless this flag is set." }),
    help: core_1.Flags.help({ char: 'h', description: 'Displays the help document for this command.' })
};
ConfigSettingsGet.args = {};
//# sourceMappingURL=get.js.map