"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PROMPT_ME_DESIGNATOR = void 0;
const tslib_1 = require("tslib");
//import * as path from 'path';
const core_1 = require("@oclif/core");
const _ = require("lodash");
// import { Asset, ConfigDef, ConfigDefCriteria, ConfigDefTypesEnum, RootMap, ScopedSetting, ScopedSettingCriteria } from '@codiac.io/codiac-domain';
const contracts_1 = require("../../apis/codiac-api/contracts");
const command_base_enterprise_1 = require("../../command-base-enterprise");
const CodiacConstants = require("../../codiac-constants");
const path = require("path");
const render = require("../../uilogic/render-methods");
const config_ui_utils_1 = require("../../uilogic/config-ui-utils");
const codiac_domain_1 = require("@codiac.io/codiac-domain");
const utils_1 = require("@codiac.io/codiac-domain/utils");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
// const { selectFiles, COMPLETED, CANCELLED } = require('select-files-cli');  // gd js wo ts...
// const { selectFiles, COMPLETED, CANCELLED } = require('../../uilogic/file-selector');  // gd js wo ts...
/** The value provided as input that indicates
 * a user's desire to be prompted with a list from which choose the value(s),
 * instead of entering them manually or taking a default. */
exports.PROMPT_ME_DESIGNATOR = "?";
/** Reads the given stream directly without assigning listeners to its events. */
function read(stream) {
    var _a, stream_1, stream_1_1;
    var _b, e_1, _c, _d;
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        try {
            const chunks = [];
            try {
                for (_a = true, stream_1 = tslib_1.__asyncValues(stream); stream_1_1 = yield stream_1.next(), _b = stream_1_1.done, !_b;) {
                    _d = stream_1_1.value;
                    _a = false;
                    try {
                        const chunk = _d;
                        chunks.push(chunk);
                    }
                    finally {
                        _a = true;
                    }
                }
            }
            catch (e_1_1) { e_1 = { error: e_1_1 }; }
            finally {
                try {
                    if (!_a && !_b && (_c = stream_1.return)) yield _c.call(stream_1);
                }
                finally { if (e_1) throw e_1.error; }
            }
            const out = Buffer.concat(chunks).toString('utf8');
            return out;
        }
        catch (err) {
            throw err;
        }
    });
}
class ConfigAdd extends command_base_enterprise_1.CommandBaseEnterprise {
    constructor() {
        super(...arguments);
        /** CLI Command-line string to rerun this execution as a non-TTY script. */
        this.cmd = ["cod", "config", "add", "--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() {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                this.bootstrap();
                yield this.readStdin();
                (_a = this._relayContainer) === null || _a === void 0 ? void 0 : _a.ready$.subscribe((ready) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                    try {
                        // 1. CAPTURE ARGS
                        let [partial, interactionMode, takeDefaults] = yield this.captureArgs();
                        // 0. SET DEFAULTS
                        this._userCntx = yield this.establishTenantContext(this._userCntx);
                        let rootMap = yield this.getRootMap();
                        partial.enterprise = (yield this.establishEnterpriseContext(rootMap, partial.enterprise, interactionMode)).enterprise.name;
                        _.remove(rootMap.enterprises, e => e.name != partial.enterprise); // trim down to match legacy conditions
                        // 2. 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, 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);
                            }
                        }
                    }
                    catch (err) {
                        this.handleError(err);
                        // dont rethrow - err handling in the observable isnt yet user-friendly.
                    }
                    finally {
                        yield this.closeConnections();
                        return;
                    }
                }));
            }
            catch (err) {
                this.handleError(err);
                throw err;
            }
            finally {
                yield this.closeConnections();
                return;
            }
        });
    }
    captureArgs() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // TODO: Move this into the base class
            const { args, flags } = yield this.parse(ConfigAdd);
            // TODO: Consider making this parse a separate abstract base method
            // stdin
            let inputDoc;
            if (this.pipedInput) {
                if (flags['value-stdin']) {
                    // we'll pick this up a few lines down when we capture setting/value...
                }
                else {
                    inputDoc = this.configUtils.parseInputString(this.pipedInput.trim());
                    this._ui.userMessage(`Found ${inputDoc.length} record(s) to add in the stdin pipeline.`);
                }
            }
            let partial = {};
            if (flags.enterprise)
                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) {
                partial.targetFile = flags.file;
            }
            partial.scope =
                (_b = (_a = (flags['enterprise-scope'] ? contracts_1.ScopeLevel.enterprise : undefined)) !== null && _a !== void 0 ? _a : (flags.enviro ? contracts_1.ScopeLevel.environment : undefined)) !== null && _b !== void 0 ? _b : (flags.cabinet ? contracts_1.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["entire-file"]) {
                partial.settings = [{ setting: utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE, value: (_e = this.pipedInput) !== null && _e !== void 0 ? _e : flags.value }];
            }
            else if (flags.setting) {
                partial.settings = [{ setting: flags.setting, value: (_f = this.pipedInput) !== null && _f !== void 0 ? _f : flags.value }];
            }
            if (inputDoc != undefined) {
                partial.settings = ((_g = partial.settings) !== null && _g !== void 0 ? _g : []).concat(inputDoc);
            }
            // partial.description = flags.description;
            // TODO: move this into the base class
            const interactionMode = (this.pipedInput) ? config_ui_utils_1.InteractionModes.silent : (_j = (_h = undefined !== null && undefined !== void 0 ? undefined : (flags.echo ? config_ui_utils_1.InteractionModes.echo : undefined)) !== null && _h !== void 0 ? _h : (flags["to-script"] ? config_ui_utils_1.InteractionModes.toscript : undefined)) !== null && _j !== void 0 ? _j : (flags.silent ? config_ui_utils_1.InteractionModes.silent : undefined);
            const takeDefaults = flags['take-defaults'];
            return [partial, interactionMode, takeDefaults];
        });
    }
    parseTable(input, separator = ",") {
        let output = [];
        let lines = input
            .split("\n")
            .map(x => x.trim());
        lines.shift();
        lines.forEach(x => {
            let parts = x.split(",").map(x => x.trim());
            output.push({ setting: parts[0], value: parts[1] });
        });
        return output;
    }
    buildCandidate_silently(partial, rootMap) {
        var _a;
        try {
            // VALIDATE / SET DEFAULTS
            let ent;
            let asset;
            if (partial.asset == undefined || _.isEmpty(partial.asset))
                throw new Error(`Invalid input: missing asset name.`);
            // Enterprise
            if (partial.enterprise == undefined || _.isEmpty(partial.enterprise)) {
                // Derive the one OR throw if it's indeterminate
                let assetParents = rootMap.enterprises.filter(e => e.assets.find(a => a.name == partial.asset));
                if (assetParents.length == 1) {
                    ent = assetParents[0];
                    partial.enterprise = ent.name;
                }
                else if (assetParents.length > 1) {
                    throw new Error(`Invalid input: missing enterprise.`);
                }
                else {
                    throw new Error(`Invalid input: no enterprise was found containing that asset.`);
                }
            }
            else {
                ent = rootMap.enterprises.find(e => e.name == partial.enterprise);
                if (!ent)
                    throw new Error("Enterprise not found.");
                partial.enterprise = ent.name;
            }
            // Asset
            asset = ent.assets.find(a => a.name == partial.asset);
            if (asset == undefined)
                throw new Error("Asset was not found.");
            // configType
            if (partial.targetFile == undefined && partial.configType == undefined)
                throw new Error("Invalid input: Must specify the file or type.");
            if (asset.type == contracts_1.AssetTypesEnum.helm) {
                if (partial.configType != codiac_domain_1.SpecialConfigTypesEnum.helm && partial.targetFile != "[".concat(codiac_domain_1.SpecialConfigTypesEnum.helm, "]")) {
                    throw new Error("Invalid config type: Helm assets can only set configs of type: helm.");
                }
            }
            partial.thisMachine = this.configUtils.validateScope(rootMap, partial.enterprise, partial.scope, partial.target);
            if (partial.settings == undefined || partial.settings.length == 0)
                throw new Error(`Invalid input: missing setting name/value.`);
            // if (partial.setting == undefined || _.isEmpty(partial.setting)) throw new Error(`Invalid input: missing setting name.`)
            if (partial.settings[0].value == undefined || _.isEmpty(partial.settings[0].value))
                throw new Error(`Invalid input: missing setting value.`);
            let output = Object.assign({ enterprise: partial.enterprise, asset: partial.asset, scope: (_a = partial.scope) !== null && _a !== void 0 ? _a : contracts_1.ScopeLevel.enterprise, target: partial.target, thisMachine: partial.thisMachine, targetFile: (partial.targetFile) ? partial.targetFile : `[${partial.configType}]`, settings: partial.settings, 
                // setting: partial.setting,
                // value: partial.value,
                description: partial.description }, this.createAuditable());
            return output;
        }
        catch (err) {
            throw err;
        }
    }
    buildCandidate_interactively(partial, rootMap, takeDefaults = false) {
        var _a, _b, _c;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let output;
            try {
                let partialConfigDef = {};
                partialConfigDef.enterprise = partial.enterprise;
                let enterprise = rootMap.enterprises.find(e => e.name == partial.enterprise);
                if (enterprise == undefined)
                    throw new Error("Invalid enterprise"); // <--- here just for TS - shd never get hit (if it hit, itd be guaranteed to confuse the user).
                let confirmed = false;
                do {
                    // 1.  Select Asset
                    let assetType;
                    let assetChanged;
                    [partialConfigDef.asset, assetChanged, assetType] = yield this.configUtils.promptForAsset(enterprise.assets, partial.asset);
                    // 2. Set Target File
                    // let selectedFile = (await this.promptForTargetFile(partial.enterprise, partialConfigDef.asset))!;
                    // partialConfigDef.targetFile = selectedFile.targetFile;
                    // partialConfigDef.settings = selectedFile.settings;
                    const setEntireFile = Boolean((_a = partial.settings) === null || _a === void 0 ? void 0 : _a.find(s => s.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE));
                    let typeFileChanged;
                    // let existingSettings: ScopedSetting[] | undefined;
                    [partial.configType, partialConfigDef.targetFile, partialConfigDef.settings, typeFileChanged] = yield this.configUtils.promptForTypeAndFile(partial.enterprise, partialConfigDef.asset, assetType, partial.configType, partial.targetFile, undefined, true, takeDefaults, setEntireFile); // TakeDefaults will never apply bc we're allowing addNew (are these always mutally exclusive?)
                    let configType = (_b = (partial.configType ? type_safety_utils_1.TypeSafetyUtils.toEnum(contracts_1.ConfigDefTypesEnum, partial.configType) : undefined)) !== null && _b !== void 0 ? _b : contracts_1.ConfigDefTypesEnum.file;
                    // 3. Setting
                    let settingName;
                    let value;
                    let instances;
                    let changed;
                    let settingSchema;
                    if (partial.settings != undefined && partial.settings.length > 0) {
                        settingName = partial.settings[0].setting;
                        value = partial.settings[0].value;
                    }
                    let predefs;
                    let schema = yield this.getConfigSchema(configType, partial.enterprise, partialConfigDef.asset);
                    if (schema) {
                        predefs = this.flatten(schema);
                    }
                    if (setEntireFile) {
                        settingName = utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE; // We shdnt have to do this - we shd get this from the partial
                    }
                    else {
                        [settingName, instances, changed, settingSchema] = yield this.configUtils.promptForOneSetting(configType, (_c = partialConfigDef.settings) !== null && _c !== void 0 ? _c : [], "write", predefs, settingName);
                    }
                    // if ([ConfigDefTypesEnum.label, ConfigDefTypesEnum.annotation, ConfigDefTypesEnum.env, ConfigDefTypesEnum].includes(configType)) {
                    //   settingName = settingName.replace(".", "\\.");  // Auto-escape dots for flat config docs
                    // }
                    // 4. Scope
                    let targetChanged = false;
                    [partial.scope, partial.target, targetChanged, partial.thisMachine] = yield this.configUtils.validateAndPromptForScope(rootMap, partialConfigDef.enterprise, partial.scope, partial.target, instances, false);
                    partial.scope = partial.scope; // assertion operator is ok here bc we disabled AllowEverywhere in the method above
                    partial.target = partial.target; // assertion operator is ok here bc we disabled AllowEverywhere in the method above
                    // 5. Value
                    value = yield this.promptForValueAndValidate(partial.configType, { setting: settingName, value: value, scope: partial.scope, target: partial.target, thisMachine: partial.thisMachine, enterprise: partial.enterprise }, instances, rootMap, settingSchema);
                    partial.settings = [{ setting: settingName, value: value }];
                    // 6. Confirm or loop back around to enterprise
                    confirmed = yield this._ui.promptConfirm("confirm", "Apply? ");
                } while (!confirmed);
                output = Object.assign({ 
                    // setting: partial.setting,
                    // value: partial.value,
                    settings: partial.settings, targetFile: partialConfigDef.targetFile, configType: partial.configType, asset: partialConfigDef.asset, enterprise: partialConfigDef.enterprise, scope: partial.scope, target: partial.target, thisMachine: partial.thisMachine, description: partial.description }, this.createAuditable());
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Fails silently; Logs any exceptions and returns undefined. */
    getConfigSchema(configType, enterprise, asset) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let output;
            try {
                let result = yield this.genRelayClient.Call.get_asset_config_schema({ enterprise: enterprise, asset: asset, configType: configType });
                if (result.error)
                    throw result.error;
                else if (result.success != true && result.message)
                    this.logger.debug("Response from config schema request: ".concat(result.message));
                output = result.output;
            }
            catch (err) {
                this.logger.error("Error retrieving config schema for ".concat(configType, ": ", (_a = err.message) !== null && _a !== void 0 ? _a : err.stack));
            }
            return output;
        });
    }
    /** Converts the given json schema into a dictionary keyed by dotpath-ed property names with the property schema as the value.
     * eg:
     * ```
     * {
     *    "db.name": { type: "string" },
     *    "db.server": { type: "string" },
     *    "fav-color": { type: "string" },
     *    ...
     * }
     * ```
     */
    flatten(schema) {
        var _a, _b, _c;
        let flattened = {};
        for (let prop in schema.properties) {
            let propDef = schema.properties[prop];
            if (propDef.properties) {
                let subs = this.flatten(propDef);
                for (let subProp in subs) {
                    let subSch = subs[subProp];
                    let fullProp = prop.concat(".", subProp);
                    flattened[fullProp] = subSch;
                }
                // subs.forEach(s => flattened.push(prop.concat(".", s)))
            }
            else if (propDef.additionalProperties) {
                // Capture the .* if it supports custom properties (aka: dictionary)
                if (typeof propDef.additionalProperties == 'boolean') {
                    // flattened.push(prop.concat(".*"));  // nothing to recurse; next step is just a freeform entry
                    let fullProp = prop.concat(".*");
                    flattened[fullProp] = {}; // no specific schema defined for the additional values, so its empty/open. 
                }
                else {
                    let subs = this.flatten(propDef.additionalProperties);
                    let prefix = prop.concat(".*");
                    // subs.forEach(s => flattened.push(prefix.concat(".", s)));  // eg: `scaling.metrics.*.targetValue`
                    for (let subProp in subs) {
                        let subSch = subs[subProp];
                        let fullProp = prefix.concat(".", subProp);
                        flattened[fullProp] = subSch;
                    }
                }
            }
            else {
                flattened[prop] = propDef;
            }
        }
        // no explicit properties, but subschemas exist 
        // (we land here for dictionary values)
        if (schema.oneOf || schema.anyOf || schema.allOf) {
            // No matter which kind, we are adding all available
            for (let subSch of [...(_a = schema.oneOf) !== null && _a !== void 0 ? _a : [], ...(_b = schema.anyOf) !== null && _b !== void 0 ? _b : [], ...(_c = schema.allOf) !== null && _c !== void 0 ? _c : []]) {
                let subs = this.flatten(subSch);
                // subs.forEach(s => flattened.push(s))
                for (let subProp in subs) {
                    let subSch = subs[subProp];
                    flattened[subProp] = subSch;
                }
            }
        }
        // flattened.sort();
        return flattened;
    }
    buildCommandString(ultimate, cmd) {
        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_1.ScopeLevel.enterprise) {
                cmd.push("--enterprise-scope");
            }
            else if (ultimate.target) {
                if (ultimate.scope == contracts_1.ScopeLevel.environment)
                    cmd.push("-e", ultimate.target);
                if (ultimate.scope == contracts_1.ScopeLevel.cabinet)
                    cmd.push("-c", ultimate.target);
            }
        }
        // NOTE:  We can't provide for multiple settings in the cli.
        // Unless: 
        // a) we allow multple --setting args and use the --setting "key=value" syntax 
        // OR b) we send them back a command string that pipes the settings in
        if (ultimate.settings && ultimate.settings.length > 0) {
            // for (let setting of ultimate.settings) {
            let setting = ultimate.settings[0];
            if (setting.setting) {
                if (setting.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE) {
                    cmd.push("--entire-file");
                }
                else {
                    cmd.push("--setting", setting.setting);
                }
            }
            if (setting.value) {
                cmd.push("--value", setting.value);
            }
            // }
        }
        return cmd;
    }
    // private async promptForAsset(assets: AtLeast<Asset, "name">[]): Promise<string> {
    //   try {
    //     let choices = assets.map(a => { return { name: a.name, value: a.name }; })
    //     let output = (await this._ui.promptSelect("asset", "Asset: ", choices))!;
    //     return output;
    //   } catch (err) {
    //     throw err;
    //   }
    // }
    promptForValueAndValidate(configType, candidate, existingValues, rootMap, valueSchema) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // let existingValues = partialConfigDef.settings?.filter(s => s.setting === setting.setting);
            let pedigree = this.buildPedigreeFor(candidate, existingValues, rootMap);
            let renderPedigree = (pedigree) => {
                if (pedigree === null || pedigree === void 0 ? void 0 : pedigree.parent)
                    renderPedigree(pedigree.parent);
                if (pedigree.value !== undefined) {
                    this._ui.userMessage(`[in ${pedigree.scope}: ${pedigree.target}] = ${(pedigree.value === "") ? "\"\"" : pedigree.value}`);
                }
            };
            if (candidate.description)
                this._ui.userMessage(candidate.description);
            if (pedigree)
                renderPedigree(pedigree);
            let existing = existingValues === null || existingValues === void 0 ? void 0 : existingValues.find(s => s.scope === candidate.scope && s.target === candidate.target);
            let output = candidate.value; // start with what they gave us
            let isValid;
            do {
                isValid = (output == undefined) ? false : config_ui_utils_1.ConfigUiUtils.settingValueIsValid(output, configType);
                if (!isValid) {
                    if (output != undefined)
                        this._ui.userMessage(`The value you provided is invalid`);
                    output = yield this._ui.prompt("value", `Value in ${candidate.scope} ${candidate.target}: `, false, (_a = output !== null && output !== void 0 ? output : candidate.value) !== null && _a !== void 0 ? _a : existing === null || existing === void 0 ? void 0 : existing.value);
                }
            } while (!isValid);
            return output;
        });
    }
    /** Returns the recursive parent path of values for the given setting
     *  up through the enterprise scope.
     *  Returns undefined if the given setting is already scoped to enterprise.
     *
     * @param existingValues This MUST be filtered for this current setting (existing.setting == setting.setting).
     * */
    buildPedigreeFor(setting, existingValues, rootMap) {
        try {
            let pedigree;
            if (setting.scope !== contracts_1.ScopeLevel.enterprise) {
                let ent = rootMap.enterprises.find(e => e.name === setting.enterprise);
                if (ent == undefined) {
                    throw new Error(`No enterprise found named [${setting.enterprise}]`);
                }
                let entSetting = existingValues === null || existingValues === void 0 ? void 0 : existingValues.find(s => s.scope === contracts_1.ScopeLevel.enterprise && s.target === ent.name);
                let entPedigree = { scope: contracts_1.ScopeLevel.enterprise, target: setting.enterprise };
                if (entSetting === null || entSetting === void 0 ? void 0 : entSetting.value) {
                    entPedigree.value = entSetting.value;
                }
                if (setting.scope === contracts_1.ScopeLevel.cabinet) {
                    let env = ent === null || ent === void 0 ? void 0 : ent.environments.find(e => e.cabinets.find(c => c.name === setting.target));
                    if (!env) {
                        throw new Error(`No environment found containing the cabinet [${setting.target}]`);
                    }
                    pedigree = { scope: contracts_1.ScopeLevel.environment, target: env.name, parent: entPedigree };
                    let envSetting = existingValues === null || existingValues === void 0 ? void 0 : existingValues.find(s => s.scope === contracts_1.ScopeLevel.environment && s.target === (env === null || env === void 0 ? void 0 : env.name));
                    if (envSetting) {
                        pedigree.value = envSetting.value;
                    }
                }
                else if (setting.scope === contracts_1.ScopeLevel.environment) {
                    pedigree = entPedigree;
                }
            }
            return pedigree;
        }
        catch (err) {
            throw err;
        }
    }
    promptForScope(enterprise, rootMap, existings) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let output;
                // render cab tree with all lines selectable
                let cabTree = this.buildCabinetTree(rootMap, existings);
                let choices = this.buildSelectChoicesFrom(cabTree);
                let delimited = (yield this._ui.promptSelect("scope", "Apply to: ", choices, undefined, false));
                let parts = delimited.split("|");
                output = { scope: parts[0], target: parts[1] };
                return output;
            }
            catch (err) {
                throw (err);
            }
        });
    }
    buildSelectChoicesFrom(tree) {
        try {
            let choices = [];
            // the value is no good here - need scope and target both available
            render.renderStringTree(tree, (txt, item) => choices.push({ name: txt, value: item.value }));
            // // This is one way to do it
            // for (let item of tree) {
            // 
            //   let thisChoice: PromptChoice = { name: item.text, value: item.text };
            //   choices.push(thisChoice);
            // 
            //   if (item.children && item.children.length > 0) {
            //     let children = this.buildSelectChoicesFrom(item.children);
            //     choices.push(...children);
            //   }
            // }
            return choices;
        }
        catch (err) {
            throw err;
        }
    }
    /** sets each value to "scope|target".
     *
     * This is MOSTLY a COPY-PASTE from the CabinetList command!!!  This view is prob gonna be pretty common - consider promoting it to a shared utility. */
    buildCabinetTree(rootMap, existings) {
        var _a, _b, _c, _d;
        let tree = [];
        for (let ent of rootMap.enterprises) {
            let entBranch = { text: `${ent.name} enterprise:`, value: `${contracts_1.ScopeLevel.enterprise}|${ent.name}` };
            let entSetting = existings === null || existings === void 0 ? void 0 : existings.find(s => s.scope === contracts_1.ScopeLevel.enterprise && s.target === ent.name);
            if (entSetting !== undefined) {
                let valueSuffix = this.ellipsis(35, (_a = entSetting.value) !== null && _a !== void 0 ? _a : "[set to undefined]");
                valueSuffix = `(${valueSuffix})`;
                entBranch.text = entBranch.text.concat(" ", this._styler.dim(valueSuffix));
            }
            for (let env of _.sortBy(ent.environments, e => e.sequence, e => e.name)) {
                let envBranch = { text: `${env.name}`, value: `${contracts_1.ScopeLevel.environment}|${env.name}` };
                let envSetting = existings === null || existings === void 0 ? void 0 : existings.find(s => s.scope === contracts_1.ScopeLevel.environment && s.target === env.name);
                if (envSetting !== undefined) {
                    let valueSuffix = this.ellipsis(35, (_b = envSetting.value) !== null && _b !== void 0 ? _b : "[set to undefined]");
                    valueSuffix = `(${valueSuffix})`;
                    envBranch.text = envBranch.text.concat(" ", this._styler.dim(valueSuffix));
                }
                for (let cab of env.cabinets) {
                    // let cwg = rootMap.clusters.find(c => c.name === cab.cluster);
                    // let clusterDesc = cwg ? ` (${cab.cluster}, ${cwg.provider} ${cwg.locationName})` : "";
                    let cabBranch = {
                        text: `${cab.name}`,
                        value: `${contracts_1.ScopeLevel.cabinet}|${cab.name}`
                    };
                    let cabSetting = existings === null || existings === void 0 ? void 0 : existings.find(s => s.scope === contracts_1.ScopeLevel.cabinet && s.target === cab.name);
                    if (cabSetting !== undefined) {
                        let valueSuffix = this.ellipsis(35, (_c = cabSetting.value) !== null && _c !== void 0 ? _c : "[set to undefined]");
                        valueSuffix = `(${valueSuffix})`;
                        cabBranch.text = cabBranch.text.concat(" ", this._styler.dim(valueSuffix));
                    }
                    if (envBranch.children === undefined)
                        envBranch.children = [];
                    (_d = envBranch.children) === null || _d === void 0 ? void 0 : _d.push(cabBranch);
                }
                if (entBranch.children === undefined)
                    entBranch.children = [];
                entBranch.children.push(envBranch);
            }
            tree.push(entBranch);
        }
        return tree;
    }
    /** Truncates the string to the given length with an ellipses as suffix */
    ellipsis(maxLength, value) {
        let output = value;
        if (output.length > maxLength) {
            let ellipsis = "..."; // Rather use the ellipsis character but String.fromCharCode(133) is returning a problematic character.
            output = output.slice(0, maxLength - 1 - ellipsis.length).concat(ellipsis);
        }
        return output;
    }
    /** Copy of this method is in config:view */
    promptForTargetFile(enterprise, asset) {
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let crit = {
                    asset: asset,
                    enterprise: enterprise,
                    type: contracts_1.ConfigDefTypesEnum.file
                };
                let cfgResult = yield this._codiacApi.Call.getConfigDefs(crit, { projection: { pathsExcluded: [], pathsIncluded: ["settings", "targetFile", "format"] } });
                if (cfgResult.error)
                    throw cfgResult.error;
                let configs = (_a = cfgResult.output) !== null && _a !== void 0 ? _a : [];
                let choices = configs.map(c => { return { name: c.targetFile, value: c.targetFile }; });
                choices.push({ name: CodiacConstants.CliConstants.CHOICE_VALUE_FOR_NEW, value: CodiacConstants.CliConstants.CHOICE_VALUE_FOR_NEW });
                let output; // Pick<ConfigDef, "targetFile" | "settings">; 
                let settingsInLocalFile;
                let selected = (yield this._ui.promptSelect("fileExisting", "File: ", choices));
                if (selected === CodiacConstants.CliConstants.CHOICE_VALUE_FOR_NEW) {
                    selected = (yield this._ui.prompt("filefreehand", "Path from image root: ('?' to browse): "));
                    if (selected === "?") {
                        let localPath = (yield this._ui.promptSelectFile("filebrowse", "Select file: ", { asRelative: true }));
                        settingsInLocalFile = yield this.extractSettingsFromFile(localPath);
                        let filename = path.basename(localPath);
                        let pathInImage = path.dirname(localPath);
                        this._ui.userMessage(`Target file: ${this._styler.hilite(filename)}`);
                        pathInImage = (yield this._ui.prompt("pathinimage", "Path from image root: ", true, pathInImage));
                        selected = path.posix.join(pathInImage, filename);
                    }
                    else {
                        // the user free-handed the targetFile field
                        // this._ui.userMessage(`Target file: ${this._styler.hilite(selected)}`);
                    }
                }
                output = {
                    targetFile: selected,
                    settings: (_b = configs.find(c => c.targetFile === selected)) === null || _b === void 0 ? void 0 : _b.settings, // in case it matches a config we pulled down 
                };
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    extractSettingsFromFile(relativeFilePath) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let output = [];
                // gotta determmine the file format and parse it.  
                // This logic prob needs to come from the domain or from the relay.
                /** let format = getDefaultDataFormat(localPath);
                 *  let extracted: Object | undefined;
                 *  do {
                 *    try {
                 *      format = format ?? (await this.promptForFormat());
                 *      extracted = tryDeserialize(file, format);
                 *    } catch(err) {
                 *      this._ui.UserMessage(`Error parsing as ${format}: ${err.message ?? ""}`);
                 *      format = undefined;
                 *    }
                 *  } while (!extracted);
                 *
                 *  output = this.convertToStringParents(extracted);
                 */
                return output;
            }
            catch (err) {
                throw err;
            }
        });
    }
    // async writeSetting_Api(enterprise: string, assetName: string, configName: string, setting: string, scopeLevel: ScopeLevel, scopeName: string, val: any): Promise<void> {
    //   try {
    //     let settingObj: ScopedSetting = {
    //       setting: setting,
    //       value: val,
    //       targetFile: configName,
    //       asset: assetName,
    //       enterprise: enterprise,
    //       scope: scopeLevel,
    //       target: scopeName,
    //       ...this.createAuditable()
    //     };
    //     return this.upsertSetting(settingObj);
    //   } catch (err) {
    //     throw err;
    //   }
    // }
    executeTask(candidate) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // TODO: Do this in a single batch call
            for (let setting of candidate.settings) {
                if (this.configUtils.isSourced(setting.value)) {
                    this.configUtils.deserializeSourcedValue(setting);
                }
                let ultimate = Object.assign(Object.assign({}, _.omit(candidate, "settings")), setting);
                if (ultimate.configType) {
                    ultimate.targetFile = `[${ultimate.configType}]`;
                    delete ultimate.configType;
                }
                yield this.upsertSetting(ultimate); // casting here bc TS isnt recognizing that we've defined the targetFile property.
            }
        });
    }
    upsertSetting(settingObj) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                try {
                    let crit = {
                        enterprise: settingObj.enterprise,
                        asset: settingObj.asset,
                        targetFile: settingObj.targetFile,
                        scope: settingObj.scope,
                        target: settingObj.target,
                        setting: settingObj.setting
                    };
                    const resultSetting = yield this._codiacApi.ScopedSetting.tryUpsert(settingObj);
                    // // TODO:  Replace this with an upsert based on:  enterprise, asset, configName, scopeLevel, scopeName, and setting
                    // let check = await this._codiacApi.ScopedSetting.tryReadMany(crit);
                    // let resultSetting: SingleResult<ScopedSetting>;
                    // if (check.success && check.output && check.output.length > 0) {
                    //   settingObj.id = check.output[0].id;
                    //   resultSetting = await this._codiacApi.ScopedSetting.tryUpdate(settingObj);
                    // } else {
                    //   resultSetting = await this._codiacApi.ScopedSetting.tryCreate(settingObj);
                    // }
                    if (resultSetting.success) {
                        this.logger.notice(`Successfully saved ${(settingObj.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE) ? "entire file content" : "setting [" + settingObj.setting + "]"} for ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                        this._ui.userMessage(`Successfully saved ${(settingObj.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE) ? "entire file content" : "setting [" + settingObj.setting + "]"} for ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                    }
                    else if (resultSetting.error) {
                        this.logger.error(`Error saving setting [${settingObj.setting}]: ${resultSetting.message || ""}; ${resultSetting.error.message || resultSetting.error.stack}; For ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                        this._ui.userMessage(`Error saving setting [${settingObj.setting}]: ${resultSetting.message || ""}; ${resultSetting.error.message || resultSetting.error.stack}; For ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                    }
                    else {
                        this.logger.warn(`Failed without error to save ${(settingObj.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE) ? "entire file content" : "setting [" + settingObj.setting + "]"}: ${resultSetting.message || ""}; For ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                        this._ui.userMessage(`Failed without error to save ${(settingObj.setting == utils_1.ConfigDefUtils.CONFIG_SETTING_NAME_ENTIRE_FILE) ? "entire file content" : "setting [" + settingObj.setting + "]"}: ${resultSetting.message || ""}; For ${settingObj.scope} [${settingObj.target}] in ${settingObj.enterprise}/${settingObj.asset} config [${settingObj.targetFile}]`);
                    }
                    resolve();
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    writeSetting_local(setting, value) {
        // Write the available setting
        if (!this._appConfig) {
            this._appConfig = {};
        }
        _.set(this._appConfig, setting, value);
        // this._appConfig[setting] = val.settingValue;
        this._ops.writeConfig(this._appConfig);
    }
    /** Retrieves the list of app config settings that are not yet explicitly declared in the app config file itself */
    getAvailableSettings() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let avails = this._ops.loadConfigKeys(this.config.root);
            let unset = avails;
            // Remove any settings already explicitly called from the app-config.
            if (this._appConfig) {
                let keys = _.keys(this._appConfig);
                unset = _.pull(avails, ...keys);
            }
            return unset;
        });
    }
}
exports.default = ConfigAdd;
ConfigAdd.description = 'Adds a key value pair to the app config';
ConfigAdd.aliases = ['config:set', 'cfg:add'];
ConfigAdd.flags = {
    help: core_1.Flags.help({ char: 'h' }),
    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', description: 'The path-name to the file to which the settings are written, relative to the root of the Codiac project.' }),
    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: 'The name of the environment to which this value is to apply.' }),
    cabinet: core_1.Flags.string({ char: 'c', exclusive: ["enterprise-scope", "enviro"], description: 'The name of the cabinet to which this value is to apply.' }),
    setting: core_1.Flags.string({ required: false, description: "The config key (in dotpath/JSONPath), relative to the root of the config." }),
    "entire-file": core_1.Flags.boolean({ required: false, exclusive: ["setting", "type"], description: "Declares that the entire config doc content shall consist only of the value of this setting.  If the system encounters this argument while assembling configs, it will ignore any other settings applied to the given target file." }),
    value: core_1.Flags.string({ required: false, description: "The actual concrete data to be applied as the setting value." }),
    "value-stdin": core_1.Flags.boolean({ required: false, dependsOn: ["file"], exclusive: ["value"], description: "(for type=file only) Reads the actual concrete data to be applied as the setting value from the stdin io stream (see the help for examples)." }),
    // TODO: Move these into the base class
    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." })
};
ConfigAdd.args = {
// setting: Args.string({ description: 'The key for the config setting to add' })
};
ConfigAdd.examples = [
    { description: "Apply a setting interactively", command: "cod config set" },
    { description: "Apply a setting interactively and assemble a non-interactive command string.", command: "cod config set --echo" },
    { description: "Assemble a non-interactive command string WITHOUT actually applying any settings (dry run).", command: "cod config set --to-script" },
    { description: "Set the value of an individual setting to the contents of a file on your local machine", command: "cat template.htm | cod config set -a api -c dev -f '/alert-settings.json' --setting 'email-body' --value-stdin" },
    { description: "Set the entire file content to the content of a file on your local machine", command: "cat way-too-much.xml | cod config set -a api -c dev -f '/datapoints.xml' --entire-file --value-stdin" },
    { description: "Set the entire file content to the value of a secret", command: "cod config set -a api -c dev -f '/some-managed-service.pem' --entire-file --value '#REF|azKeyVault|our-secret-store|some-managed-svc-cert'" },
];
//# sourceMappingURL=add.js.map