"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnterpriseContextualizer = void 0;
const tslib_1 = require("tslib");
const asset_binding_1 = require("../codiac-entities/asset-binding");
const _ = require("lodash");
const contracts_1 = require("../apis/codiac-api/contracts");
const codiac_relay_contracts_1 = require("@codiac.io/codiac-relay-contracts");
const codiac_relay_contracts_2 = require("@codiac.io/codiac-relay-contracts");
const contracts_2 = require("@codiac.io/codiac-common/contracts");
const codiac_constants_1 = require("../codiac-constants");
const config_ui_utils_1 = require("./config-ui-utils");
/** Establishes a single Enterprise Asset as which to deploy an ImageExport. */
class EnterpriseContextualizer {
    constructor(_tenantCode, _ops, relayClient, _codiacApi, _projCfg, _projUserCfg, _projCfgRepo, userContext, userContextManager, ui, styler, exitCli, logger) {
        this._tenantCode = _tenantCode;
        this._ops = _ops;
        this.relayClient = relayClient;
        this._codiacApi = _codiacApi;
        this._projCfg = _projCfg;
        this._projUserCfg = _projUserCfg;
        this._projCfgRepo = _projCfgRepo;
        this.userContext = userContext;
        this.userContextManager = userContextManager;
        this.ui = ui;
        this.styler = styler;
        this.exitCli = exitCli;
        this.logger = logger;
        this.takeDefaults = false;
    }
    /** Interacts with user to select an existing enterprise (or create one if none exist),
     * and persists it in the project user config.
     * NOTE:  Clears Asset context if the user switches enterprises,
     *        or keeps original with orignal asset context if user selects it. */
    establishEnterprise(rootMap, requestedEnterpriseCode, interactionMode = undefined) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e;
                try {
                    // TODO: instead, use the rootmap from the relay to mitigate requests over the wire
                    // let eResult = await this._codiacApi.Enterprise.tryReadMany({ all: true });
                    // if (eResult.error) throw new CodiacError(`Error reading enterprises: ${eResult.message}; ${eResult.error.message}`, eResult.error);
                    // if (!eResult.output || eResult.output.length == 0) {
                    if (rootMap.enterprises == undefined || rootMap.enterprises.length == 0) {
                        throw new contracts_2.CodiacError(`This tenant [${this._tenantCode}] does not have any enterprises defined. You need to create one to continue.`);
                    }
                    // If were running in silent mode, an enterprise must be pvovided if there is more than one on the tenant.
                    if (interactionMode == config_ui_utils_1.InteractionModes.silent && rootMap.enterprises.length > 1 && !requestedEnterpriseCode) {
                        throw new contracts_2.CodiacError(`This tenant [${this._tenantCode}] has multiple enterprises defined.  You must provide an enterprise code to continue.`);
                    }
                    // -------------------------------------------------------------------
                    //        !!!!   HELP  !!!! 
                    //      We're drowning in IF statments here!!  
                    //
                    // It's the end of a looong day of a LOT of coding solutions, 
                    // and I cant summon an elegant path thru this...
                    //
                    //   TODO:  REFACTOR THIS INTO SOMETHING THAT LOOKS PROFESSIONAL.
                    // -------------------------------------------------------------------
                    // GET:  proj THEN user
                    let originalCode = (_c = (_b = (_a = this._projUserCfg) === null || _a === void 0 ? void 0 : _a.enterprise) === null || _b === void 0 ? void 0 : _b.enterpriseCode) !== null && _c !== void 0 ? _c : (_e = (_d = this.userContext) === null || _d === void 0 ? void 0 : _d.currentEnterprise) === null || _e === void 0 ? void 0 : _e.enterprise;
                    // Validate the requested enterprise 
                    let enterprise;
                    if (requestedEnterpriseCode) {
                        enterprise = rootMap.enterprises.find(e => e.name == requestedEnterpriseCode);
                        if (enterprise == undefined) {
                            if (interactionMode == config_ui_utils_1.InteractionModes.silent) {
                                throw new Error("Requested enterprise was not found.");
                            }
                        }
                    }
                    else {
                        // Validate the enterprise set in the current project user context
                        if (!enterprise) {
                            if (originalCode !== undefined && originalCode !== "") {
                                enterprise = rootMap.enterprises.find(e => e.name == originalCode);
                            }
                        }
                        // Select the one and only
                        if (!enterprise && rootMap.enterprises.length === 1) {
                            enterprise = rootMap.enterprises[0];
                        }
                    }
                    // Prompt the user to select from the list of enterprises (for this tenant)
                    let attempts = 4;
                    while (!enterprise && attempts > 0) {
                        let eCode = yield this.ui.promptSelect("enterpriseCode", "Select a target enterprise:", rootMap.enterprises.map(e => e.name));
                        if (eCode)
                            enterprise = rootMap.enterprises.find(e => e.name == eCode);
                        attempts--;
                    }
                    if (!enterprise)
                        throw new Error("Failed to select a target enterprise.");
                    // We've got one now.
                    if (originalCode !== undefined && originalCode !== "" && enterprise.name == originalCode) {
                        // same as original.  Do nothing
                    }
                    else {
                        // new enterprise context
                        this.userContext.currentEnterprise = { enterprise: enterprise.name };
                        this.userContext.enterpriseContexts[this._tenantCode] = this.userContext.currentEnterprise;
                        yield this.userContextManager.writeUserContext(this.userContext);
                        // SET:  user THEN proj
                        if (this._projUserCfg && this._projCfgRepo) {
                            this._projUserCfg.enterprise = new codiac_relay_contracts_1.ProjectEnterpriseBinding(this.userContext.currentEnterprise.enterprise);
                            yield this._projCfgRepo.writeProjectUserConfig(this._projUserCfg);
                        }
                    }
                    resolve(enterprise);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    establishEnterpriseAsset(enterprise, suppressConfirmations) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                try {
                    let assetName;
                    // TODO: Consider using asset.code instead.
                    // assetName = this._projUserCfg?.enterprise?.assetBindings ? this._projUserCfg?.enterprise?.assetBindings[0].incarnation?.assetName ?? this.userContext.currentEnterprise?.asset : this.userContext.currentEnterprise?.asset;
                    // WITH PROJECT CONTEXT 
                    if (this._projCfg && this._projCfgRepo && (this._projCfg.images !== undefined && Object.keys(this._projCfg.images).length > 0)) {
                        let assetContext = yield this.establishProjectAssetBinding(enterprise.name, enterprise.assets, this._projCfg, this._projCfgRepo, suppressConfirmations, (_a = this.userContext.currentEnterprise) === null || _a === void 0 ? void 0 : _a.asset);
                        assetName = assetContext.incarnation.assetName;
                        yield this.validateImageOs(assetContext, enterprise.assets);
                        // TODO:  This should come BEFORE establishBinding.  This will require the new assetContextSwitching.
                        // TODO: validate that requested assets exist.  Dont auto-create them.  
                        // await this.validateAssetBinding(assetContext);  // <---- BUT THIS SHD BE PART OF this.establishBinding()
                        //   which should include: 
                        //     throw new CodiacError(`Requested Asset [${req.incarnation.assetName}] does not exist in the enterprise [${req.incarnation.enterprise}].`)
                        let assets = yield this.createMissingAssets(enterprise.name, [assetContext]);
                    }
                    // WITH USER CONTEXT
                    if (assetName === undefined || assetName === "") {
                        if (this.userContext.currentEnterprise == undefined || this.userContext.currentEnterprise.enterprise !== enterprise.name) {
                            // Make sure we are pointed to the given enterprise
                            this.userContext.currentEnterprise = new codiac_relay_contracts_1.EnterprisePointer(enterprise.name);
                        }
                        if (this.userContext.currentEnterprise.asset !== undefined && this.userContext.currentEnterprise.asset !== "") {
                            // Make sure current asset still exists in the enterprise record.
                            assetName = (_b = enterprise.assets.find(a => a.name === this.userContext.currentEnterprise.asset)) === null || _b === void 0 ? void 0 : _b.name;
                        }
                        if (assetName === undefined || assetName === "") {
                            // Prompt the user directly
                            let choices = enterprise.assets.map(a => { return { name: a.name, value: a.name }; });
                            assetName = (yield this.ui.promptSelect("assetName", "Asset: ", choices));
                        }
                    }
                    else {
                        // Save whatever was selected in the prject context
                    }
                    resolve(assetName);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** This method WAS a copy from config:add
     *
     * @param configFileDefs Cached list of existing config file definitions for the given asset.  Use this to absolve this method from making an api call.
    */
    promptForTargetFile(enterprise, asset, given, configFileDefs) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                // 1. Build choices
                let crit = {
                    asset: asset,
                    enterprise: enterprise,
                    type: contracts_1.ConfigDefTypesEnum.file
                };
                // let configs = await this._codiacApi.ConfigDef.readMany(crit, { throwNotFound: false, projection: { pathsExcluded: [], pathsIncluded: ["settings", "targetFile", "format"] } })
                let configs;
                if (configFileDefs != undefined) {
                    configs = [...configFileDefs];
                }
                else {
                    let cfgResult = yield this._codiacApi.Call.getConfigDefs(crit, { projection: { pathsExcluded: [], pathsIncluded: ["settings", "targetFile", "format"] } });
                    if (cfgResult.error)
                        throw cfgResult.error;
                    configs = (_a = cfgResult.output) !== null && _a !== void 0 ? _a : [];
                }
                if (configs.length === 0) {
                    throw new Error(`The asset ${asset} has no config files defined.`);
                }
                let choices = configs.map(c => { return { name: c.targetFile, value: c.targetFile }; });
                let targetFile;
                // 2. Validate and select the given value
                if (given !== undefined && !([codiac_constants_1.CliConstants.PROMPT_ME_DESIGNATOR, codiac_constants_1.CliConstants.PROMPT_ME_DESIGNATOR_MACOS].includes(given))) {
                    let found = choices.find(x => x.value === given);
                    if (found) {
                        targetFile = given;
                    }
                }
                if (targetFile == undefined) {
                    // At this point, we know EITHER: there's no given, OR: the given was invalid (and we're gonna change it).
                    /** If we changed the input they provided, we need their confirmation (whether or not they said takeDefaults)  */
                    let changedUserInput = !_.isEmpty(given);
                    let keep = this.takeDefaults;
                    targetFile = choices[0].value;
                    if (choices.length === 1) {
                        // 3. Select the one-and-only  
                        //    SCENARIOS: 
                        //    a. We changed their input and we need their confirmation
                        //    b. They didnt provide a file, and they dont have takeDefaults set, so we need their confirmation
                        //    c. They didnt provide a file, but they DO have takeDefaults set, so we're skipping confirmation
                        if (changedUserInput || !this.takeDefaults) {
                            // this.ui.userMessage(`Only 1 config file found: ${this.styler.hilite(targetFile)}`);
                            keep = yield this.ui.promptConfirm("keepTargetFile", `Only 1 file found. Continue?: ${this.styler.hilite(targetFile)}`, true);
                            if (!keep) {
                                throw new Error("Operation cancelled: user declined only available config file.");
                            }
                        }
                    }
                    else {
                        // 4. Select from list 
                        // Prompt in all cases here: (even if we have takeDefaults set, we dont have a real default, just the first in the list, so theyve really got to select one)
                        keep = yield this.ui.promptConfirm("keepTargetFile", `File: ${this.styler.hilite(targetFile)}`, true);
                        if (!keep) {
                            targetFile = (yield this.ui.promptSelect("fileExisting", "File: ", choices));
                        }
                    }
                }
                return targetFile;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Throws when the image os does not match that of the image in the remote enterprise asset record.
     *
     * Throws any errors. */
    validateImageOs(localBinding, enterpriseAssets) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c;
                try {
                    let assetRecord = enterpriseAssets.find(a => a.name == localBinding.incarnation.assetName);
                    if ((assetRecord === null || assetRecord === void 0 ? void 0 : assetRecord.image) && assetRecord.image.os && (((_a = localBinding.image.os) === null || _a === void 0 ? void 0 : _a.toLowerCase()) != assetRecord.image.os.toLowerCase())) {
                        throw new Error(`The image callout in the official enterprise asset record [asset: ${assetRecord.name}, enterprise: ${assetRecord.enterprise}] is showing a different OS [${(_b = assetRecord.image.os) !== null && _b !== void 0 ? _b : "undefined"}] than the one in this project of [${(_c = localBinding.image.os) !== null && _c !== void 0 ? _c : "undefined"}].  Please reconcile this issue.`);
                    }
                    resolve();
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** TODO:  Ensures that the given assets exist in the api. */
    createMissingAssets(enterpriseCode, requestedAssets) {
        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 assets = [];
                    let crit = { code: enterpriseCode };
                    let assetResult = yield this._codiacApi.Enterprise.tryReadMany(crit);
                    if (assetResult.error) {
                        throw new contracts_2.CodiacError(`Error querying enterprise to check for existing assets: ${assetResult.message || ""}; ${assetResult.error.message || ""}`, assetResult.error);
                    }
                    else if (!assetResult.success || !assetResult.output) {
                        throw new contracts_2.CodiacError(`Unable to verify assets: enterprise [${crit.code}] was not found.`);
                    }
                    else {
                        let enterprise = assetResult.output[0];
                        let assets = enterprise.assets; // Deploy all of em unless the caller requested specific ones.
                        if (requestedAssets && requestedAssets.length > 0) {
                            // Get only the requested ones...
                            // assets = enterprise.assets.filter(a => requestedAssets.find(ac => a.name == ac.incarnation.assetName));
                            assets = [];
                            let newAssets = [];
                            for (let req of requestedAssets) {
                                let epAsset = enterprise.assets.find(a => a.name == req.incarnation.assetName);
                                if (epAsset) {
                                    assets.push(epAsset);
                                }
                                else {
                                    let newAsset = yield this.hackBuildAsset(req, enterprise);
                                    newAssets.push(newAsset);
                                    enterprise.assets.push(...newAssets);
                                }
                            }
                            if (newAssets.length > 0) {
                                let saveResult = yield this._codiacApi.Asset.tryCreateMany(newAssets);
                                if (saveResult.error) {
                                    throw new contracts_2.CodiacError(`Error inserting missing enterprise assets [${newAssets.map(a => a.name).join(',')}]: ${saveResult.error.message || saveResult.message || ""}`, saveResult.error);
                                }
                                else if (!saveResult.success || !saveResult.successes || saveResult.successes.length == 0) {
                                    if (saveResult.failures && saveResult.failures.length > 0 && saveResult.failures.length < newAssets.length) {
                                        this.logger.debug(`Saving new assets yielded a partial failure.`);
                                    }
                                    throw new contracts_2.CodiacError(`Unable to save missing enterprise assets [${newAssets.map(a => a.name).join(',')}]: Returned failure without error. ${saveResult.message}`);
                                }
                                else {
                                    this.logger.debug(`Created missing assets: [${saveResult.successes.map(a => a.name).join(',')}]`);
                                    assets.push(...saveResult.successes);
                                }
                            }
                        }
                    }
                    resolve(assets);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** This is backwards - it should be impossible to have an asset binding to an asset that doesnt exist.
     * If somehow the project was still pointed to a since-deleted asset,
     * then it should still throw an error and allow the user to select an existing asset to bind/switch to.
     */
    hackBuildAsset(localDef, enterprise) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                try {
                    let newAsset;
                    let target;
                    // no record for this asset in the enterprise yet.
                    if (localDef.image.targets.length == 0) {
                        // TODO:  Use the default image target registry.
                        // imgRegistry = new ImageRegistry("dockerhub", "https://index.docker.io/v1");
                        target = new codiac_relay_contracts_1.RegistryTarget("default", EnterpriseContextualizer.DEFAULT_REGISTRY_URL);
                        this.logger.detail(`No target image registry declared for image [${localDef.image.name}], using default at ${target.registryUrl}`);
                        localDef.image.targets.push(target);
                    }
                    else {
                        target = localDef.image.targets[0]; // <-- HACK!!! taking the first one.  Confirm?
                    }
                    // COMMENTED OUT BECUASE let's just get it in there as a compositional entity
                    // Go fetch the registry obj from the api        
                    // TODO: Consider making this a get-sert
                    // let regCrit = <ImageRegistryCriteria>{ url: localDef.image.targets[0].registryUrl };
                    // let imageRegResult = await this._codiacApi.ImageRegistry.tryReadMany(regCrit);
                    // if (imageRegResult.error) {
                    //   throw new CodiacError(`Error retrieving ImageRegistry [${localDef.image.targets[0].registryUrl}] for image [${localDef.image.name}]: ${imageRegResult.error.message||imageRegResult.message||""}`, imageRegResult.error);
                    // } else if (!imageRegResult.success || imageRegResult.output == undefined || imageRegResult.output.length == 0) {
                    //   throw new Error(`No registry currently exists with url: [${regCrit.url||""}] ${imageRegResult.message||""}`)
                    // } else {
                    //  imgRegistry = imageRegResult.output[0];
                    // let imgRegistry = new ImageRegistry(`${this._tenantCode}-${CloudProviderEnum}`, target.registryUrl, CloudProviderEnum.dockerHub, ImageRegistryTypes.DockerHub);
                    let imgRegistry = {
                        code: `${this._tenantCode}-${contracts_1.CloudProviderEnum}`,
                        url: target.registryUrl,
                        provider: contracts_1.CloudProviderEnum.dockerHub,
                        type: contracts_1.ImageRegistryTypes.dockerHub
                    };
                    let audits = this.createAuditable();
                    let img = Object.assign({ name: localDef.image.name, registry: imgRegistry }, audits);
                    newAsset = Object.assign({ code: localDef.incarnation.assetName, name: localDef.incarnation.assetName, enterprise: enterprise.code, image: img, port: (_b = (_a = localDef.incarnation.port) !== null && _a !== void 0 ? _a : localDef.image.port) !== null && _b !== void 0 ? _b : 0, hasIngress: false, routedWithoutName: false }, audits);
                    // TODO:  Write to the enterprise?  or throw?  or ask?
                    // } 
                    resolve(newAsset);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Selects a single ImageExport and a single incarnation of that image (aka: AssetBinding).
     * @param suppressConfirmations (Optional: defaults to false) Prevents confirmations of user-values that are remembered from prior runs.
     * @param preferredAssetName (Optional) Asset name to select if it applies to the given image and is currently remembered.  User prompts will default to this if valid.
    */
    establishProjectAssetBinding(enterpriseCode, assets, projCfg, projCfgRepo, suppressConfirmations, preferredAssetName) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d;
                try {
                    let allowAddingNewAssets = true;
                    // STEP 1: Establish the ImageExport to use.
                    let image;
                    if (projCfg.images && !_.isEmpty(projCfg.images)) {
                        let images = _.keysIn(projCfg.images);
                        if (images.length == 1) {
                            image = projCfg.images[images[0]];
                        }
                        else if (images.length > 0) {
                            // Prompt the user to select one.  
                            // TODO: Default to the one selected in a previous execution,
                            //       and remember the selection for next time.
                            let selection = yield this.ui.promptSelect('imageName', 'Image you are executing this command for...', images);
                            if (!selection)
                                throw new Error(`No image selected`);
                            image = projCfg.images[selection];
                        }
                        else {
                            // This clause is impossible to reach (bc we already did a _.isEmpty check).
                            throw new Error(`No image exports have been declared for this project; use "codiac images:add" to declare an image for export from this project.`);
                        }
                    }
                    else { // No image exports defined for the project
                        throw new Error(`No image exports have been declared for this project; use "codiac images:add" to declare an image for export from this project.`);
                    }
                    let validBindings = assets
                        .filter(a => a.image.name == image.name)
                        .map(a => new asset_binding_1.AssetBinding(enterpriseCode, a.name, EnterpriseContextualizer.getFirstPort(a.port)));
                    // STEP 2: Establish the AssetBinding
                    let binding;
                    // a: See if we've already got one selected (via the project user config)
                    let remembered;
                    let found = (_c = (_b = (_a = this._projUserCfg) === null || _a === void 0 ? void 0 : _a.enterprise) === null || _b === void 0 ? void 0 : _b.assetBindings) === null || _c === void 0 ? void 0 : _c.filter(ab => ab.image.name == image.name);
                    if (found && found.length > 0) {
                        // Try to use preferred...
                        if (preferredAssetName !== undefined && preferredAssetName !== "") {
                            remembered = (_d = found.find(a => a.incarnation.assetName === preferredAssetName)) === null || _d === void 0 ? void 0 : _d.incarnation;
                        }
                        if (!remembered) {
                            // Otherwise, just take the first valid binding we've remembered for that image
                            remembered = found[0].incarnation;
                        }
                    }
                    // a: Select and validate an existing binding
                    if (remembered) {
                        // We only care about the existing bindings we know are still valid in this enterprise
                        // let existings = image.incarnations.filter(i => validBindings.find(v => v.enterprise == i.enterprise && v.assetName == i.assetName));
                        if (validBindings.find(v => v.enterprise == remembered.enterprise && v.assetName == remembered.assetName)) {
                            // confirm the remembered one only if they requested a selection
                            if (!suppressConfirmations) {
                                if (yield this.ui.promptConfirm("assetBinding", `Targeting asset: ${this.styler.bold(this.styler.hilite(remembered.enterprise + '/' + remembered.assetName))} ?`)) {
                                    binding = remembered;
                                }
                            }
                            else {
                                binding = remembered;
                            }
                        }
                        // if (existings.length == 1) {
                        // } else if (existings.length > 1) {
                        //   // select from existing
                        //   let choices = existings.map(e => e.assetName);
                        //   let selection = await this.ui.promptSelect("assetBinding", `Target asset (in enterprise: ${enterprise.code})...`, choices);
                        //   if (selection) binding = existings.find(a => a.assetName == selection); 
                        // }
                    }
                    // use preferred if it's valid (even tho it's not currently in proj user cfg)
                    if (!binding) {
                        binding = validBindings.find(b => b.assetName === preferredAssetName);
                    }
                    // b: Create a new Asset binding
                    if (!binding) {
                        let choices = validBindings.map((e, idx) => { return { name: e.assetName, value: idx }; });
                        if (allowAddingNewAssets)
                            choices.push({ name: EnterpriseContextualizer.CHOICE_VALUE_FOR_NEW, value: -1 });
                        let selection = yield this.ui.promptSelect("assetBinding", `Target asset (in enterprise: ${enterpriseCode})...`, choices);
                        if (selection == undefined)
                            throw new Error("Failed to select a target asset.");
                        if (selection >= 0) {
                            binding = validBindings[selection];
                        }
                        else {
                            // Create a new Asset  
                            // TODO:  Use the same UX code as asset:create
                            let assetName = yield this.ui.prompt("asset", "Name for the Asset to be made from this image:", true, image.name);
                            let assetCode = assetName; // TODO:  Prompt with this default
                            let assetPort = image.port || this.selectAvailableAssetPort(assets); // TODO:  Prompt with this default
                            let assetImg = yield this.createAssetImageRef(image);
                            let newAsset = Object.assign({ enterprise: enterpriseCode, code: assetCode, name: assetName, image: assetImg, port: assetPort }, this.createAuditable());
                            let createResult = yield this._codiacApi.Asset.tryCreate(newAsset);
                            if (createResult.error)
                                throw createResult.error;
                            else if (!createResult.success || !createResult.output)
                                throw new Error(`Failed creating new asset ${newAsset.name} in enterprise: ${newAsset.enterprise}: ${createResult.message}`);
                            newAsset = createResult.output;
                            this.logger.detail(`New Asset [${createResult.output.name}] Successfully created from image: [${createResult.output.image.name}] in enterprise [${createResult.output.enterprise}].`);
                            binding = new asset_binding_1.AssetBinding(enterpriseCode, newAsset.name, EnterpriseContextualizer.getFirstPort(newAsset.port));
                        }
                    }
                    if (!binding)
                        throw new Error("No asset binding was established.");
                    // Write this selection to the proj user config
                    if (!this._projUserCfg) {
                        this._projUserCfg = new codiac_relay_contracts_1.ProjectUserConfig();
                    }
                    if (!this._projUserCfg.enterprise) {
                        this._projUserCfg.enterprise = new codiac_relay_contracts_1.ProjectEnterpriseBinding(enterpriseCode);
                    }
                    if (!this._projUserCfg.enterprise.assetBindings)
                        this._projUserCfg.enterprise.assetBindings = [];
                    _.remove(this._projUserCfg.enterprise.assetBindings, ac => ac.image.name == image.name);
                    this._projUserCfg.enterprise.assetBindings.push(new codiac_relay_contracts_1.AssetContext(image, binding));
                    projCfgRepo.writeProjectUserConfig(this._projUserCfg);
                    resolve({ image: image, incarnation: binding });
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Selects the port number from the union type.   */
    static getFirstPort(spec) {
        let selected;
        let candidate = (_.isArray(spec)) ? spec[0] : spec;
        if (typeof candidate == 'number') {
            selected = candidate;
        }
        else if (typeof candidate == 'string') {
            let parsed = Number.parseInt(candidate); // NaN if not a number, which resolves a false boolean.
            if (parsed) {
                selected = parsed;
            }
            else {
                // Not currently supporting named ports
                // TODO: Consider a comma-delimited format like: "80,443,5775/80,5799/8080"
            }
        }
        else {
            selected = candidate.external;
        }
        return selected;
    }
    /** Builds a comma-delimited string format like: `80,443,5775/80,5799/8080`,
     * where PortMapping is rendered as `<external>/<internal>`
     */
    static renderAsCsv(spec) {
        let specs = (_.isArray(spec)) ? spec : [spec];
        let asStrings = specs.map(x => {
            if (typeof x == 'number') {
                return x;
            }
            else if (typeof x == 'string') {
                let parsed = Number.parseInt(x); // NaN if not a number, which resolves a false boolean.
                if (parsed) {
                    return parsed;
                }
                else {
                    // Not currently supporting named ports
                    return undefined;
                }
            }
            else {
                return x.internal ? `${x.external}/${x.internal}` : x.external;
            }
        }).filter(x => x != undefined);
        return asStrings.join(",");
    }
    /** provdes a randpm port number that is not currently owned by any asset in the enterprise. */
    selectAvailableAssetPort(existingAssets) {
        let port = EnterpriseContextualizer.DEFAULT_ASSET_PORT_NUMBER;
        while (!port || existingAssets.find(a => a.port == port)) {
            port = _.random(1024, 65535, false);
        }
        return port;
    }
    /** Factory method for the image reference object on which an Asset is based. */
    createAssetImageRef(projectImage, targetEnvironment) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let registryEnv = targetEnvironment || "default";
            let registryTarget = projectImage.targets.find(t => t.environment == registryEnv);
            if (!registryTarget) {
                throw new Error(`The image export [${projectImage.name}] in the local project does not have a target registry declared for the [${registryEnv}] environment.`);
            }
            let pvdResult = yield this.relayClient.Call.getRegistryType({ registryUrl: registryTarget.registryUrl });
            if (pvdResult.error)
                throw pvdResult.error;
            if (pvdResult.output == undefined)
                throw new Error(`Did not recogize registry domain [${registryTarget.registryUrl}].`);
            let regInfo = pvdResult.output;
            // let regClient = this.regClientFactory.createForDomain(registryTarget.registryUrl);
            let regCode = "TODO-DERIVE-THE-IMAGE-REGISTRY-CODE"; // <----- HEY!!! We need to provide proper support of image registries by code name (eg: "dockerhub", "acr", "google", "amazon-ecr" etc)
            let reg = Object.assign({ code: regCode, url: registryTarget.registryUrl, provider: regInfo.provider, type: regInfo.type }, this.createAuditable());
            let scope = (projectImage.name.indexOf("/") >= 0) ? projectImage.name.split("/")[0] : undefined;
            let img = Object.assign({ name: projectImage.name, registry: reg, scope: scope }, this.createAuditable());
            return img;
        });
    }
    /** Simply wraps establishCabinet() to return a CabinetScope.
     * TODO:  EITHER get rid of this method altogether and use Cabinet instead of CabinetScope
     *        OR Add ClusterName to CabinetScope (it's redundant, but it's important for gathering k8s cluster context without requerying the api)
     * @param suppressConfirmations (Optional: defaults to false) Prevents confirmations of user-values that are remembered from prior runs.
     */
    establishCabinetContext(requestedCabinet, suppressConfirmations, requestedEnviro) {
        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 cabinet = yield this.establishCabinet(requestedCabinet, suppressConfirmations, requestedEnviro);
                    let cabCntx = new codiac_relay_contracts_1.CabinetScope(cabinet.enterpriseCode, cabinet.environment, cabinet.name);
                    this.logger.trace(`Cabinet context`, cabCntx);
                    resolve(cabCntx);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    /** Selects from existing cabinets.
     * Defaults to the cabinet declared in the project user config.
     * DOES NOT lazily create new cabinets OR build cabinet infrastructure!
     * This method only selects an existing cabinet
     * (because cabinet scope - eg: enterprises, environments, and cabinets -
     *  should only be created deliberately and explicitly by their respective cli commands).
     * @param suppressConfirmations (Optional: defaults to false) Prevents confirmations of user-values that are remembered from prior runs.
    */
    establishCabinet(requestedCabinet, suppressConfirmations, requestedEnviro) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => tslib_1.__awaiter(this, void 0, void 0, function* () {
                var _a, _b, _c, _d, _e, _f, _g, _h, _j;
                try {
                    let enterpriseCode = (_c = (_b = (_a = this._projUserCfg) === null || _a === void 0 ? void 0 : _a.enterprise) === null || _b === void 0 ? void 0 : _b.enterpriseCode) !== null && _c !== void 0 ? _c : (_d = this.userContext.currentEnterprise) === null || _d === void 0 ? void 0 : _d.enterprise;
                    if (enterpriseCode === undefined) {
                        throw new contracts_2.CodiacError(`Cannot establish cabinet context: no enterprise is in context for the current user.`);
                    }
                    let current = (_h = (_g = (_f = (_e = this._projUserCfg) === null || _e === void 0 ? void 0 : _e.enterprise) === null || _f === void 0 ? void 0 : _f.cabinet) === null || _g === void 0 ? void 0 : _g.cabinet) !== null && _h !== void 0 ? _h : (_j = this.userContext.currentEnterprise) === null || _j === void 0 ? void 0 : _j.cabinet;
                    let cabName = requestedCabinet !== null && requestedCabinet !== void 0 ? requestedCabinet : current;
                    // -----------
                    // let eCntx = this._projUserCfg?.enterprise ?? this.userContext.currentEnterprise;
                    // if (eCntx == undefined || (<ProjectEnterpriseBinding>eCntx).enterpriseCode == undefined || (<EnterprisePointer>eCntx).enterprise == undefined) {
                    //   throw new CodiacError(`Cannot establish cabinet context: no enterprise is in context for the current user.`);
                    // }
                    // // Remember any existing context...
                    // let enterpriseCode = eCntx.enterpriseCode;
                    // let current = eCntx.cabinet?.cabinet;
                    // let cabName: string | undefined = requestedCabinet;
                    // if (!cabName) {  // THIS because we dont have the nullish operator...
                    //   cabName = current;
                    // }
                    // -----------
                    // let env: Environment;
                    // let crit: EnvironmentCriteria = { all: true };
                    // let envResult = await this._codiacApi.Environment.tryReadMany(crit);
                    // if (envResult.error) {
                    //   throw new CodiacError(`Error attempting to retrieve environments: ${envResult.error.message||envResult.message||""}`, envResult.error);
                    // } else if (envResult.count == 0 || !envResult.output || envResult.output.length == 0) {
                    //   throw new CodiacError(`No environments found: ${envResult.message||""}`);
                    // } else if (envResult.output.length == 1) {
                    //   env = envResult.output[0];
                    // } else {
                    //   // TODO: Let the user select which one, and remember his choice as a default for next time.
                    //   env = envResult.output[0];  // <--- HACK!!!!
                    // }
                    // if (env.cabinets.length == 0) {
                    //   throw new CodiacError(`No cabinets found for environment [${env.code}].  Use 'cod cabinet:create' to create one.`);
                    // } else  if (env.cabinets.length == 1) {
                    //   cab = env.cabinets[0].name; // TODO:  Consider a confirmation here that the -y attrib will skip
                    // } else {
                    //   // TODO: Let the user select which one, and remember his choice as a default for next time.
                    //   cab = env.cabinets[0].name;  // <--- HACK!!!!
                    // }
                    let cabinet;
                    let crit = { enterpriseCode: enterpriseCode };
                    if (requestedEnviro !== undefined && requestedEnviro !== "") {
                        crit.environment = requestedEnviro;
                    }
                    let cabResult = yield this._codiacApi.Cabinet.tryReadMany(crit); // <--- TODO: Consider injecting a RootMap instance instead of making api hits.
                    if (cabResult.error) {
                        throw new contracts_2.CodiacError(`Error checking for cabinets in enterprise [${enterpriseCode}]: ${cabResult.message || ""}; ${cabResult.error.message || cabResult.error.stack}`, cabResult.error);
                    }
                    else if (!cabResult.output || cabResult.output.length == 0) {
                        throw new contracts_2.CodiacError(`No cabinets found for enterprise [${enterpriseCode}]. Use 'cod cabinet:create' to create a new cabinet. ${cabResult.message || ""}`);
                    }
                    else {
                        let cabs = cabResult.output;
                        // HERE'S THE UX GAUNTLET...  
                        let selection;
                        if (cabName) {
                            // This is where we validate that the requested/remembered cabinet is indeed/still in the current enterprise.
                            // let found = cabs.find(c => c.name == cabName);
                            // let found = cabs.findIndex(c => c.name == cabName);
                            selection = cabs.findIndex(c => c.name == cabName);
                            if (selection < 0) {
                                this.logger.notice(`Cabinet [${cabName}] was not found in enterprise [${enterpriseCode}].`);
                                this.ui.userMessage(`Cabinet [${cabName}] was not found in enterprise [${enterpriseCode}].`);
                            }
                        }
                        // CI/CD support here
                        if (suppressConfirmations && cabName) {
                            cabinet = cabs.find(f => f.name == cabName);
                        }
                        let choices;
                        while (!cabinet) {
                            if (!selection || selection < 0) {
                                if (cabs.length == 1) { // We're adding this to prevent two steps 
                                    selection = 0;
                                }
                                else { // Let them choose
                                    choices = choices || cabs.map((c, index) => { return { name: `${c.name}${(cabs[index].name == current) ? " (current)" : ""}`, value: index }; });
                                    selection = (yield this.ui.promptSelect('cabIndex', `Select a cabinet [enterprise: ${this.styler.hilite(enterpriseCode)}]`, choices));
                                }
                            }
                            let confResult = yield this.ui.promptConfirm('confirm', `Target cabinet: ${this.styler.hilite(enterpriseCode + "/" + cabs[selection].name)}`);
                            if (confResult === true) { // TODO:  Refactor these 3 confirms into 1
                                // if (await this._ui.promptConfirm("targetCab", `Target cabinet (y/n): ${inOrange(cabs[selection].name)}`)) {  // TODO:  Refactor these 3 confirms into 1
                                cabinet = cabs[selection];
                            }
                            else if (cabs.length == 1) { // We're adding this so we can avoid what would be two steps 
                                this.logger.notice(`Operation cancelled.  That was the only cabinet available in enterprise [${enterpriseCode}].`);
                                this.ui.userMessage(`Operation cancelled.  That was the only cabinet available in enterprise [${enterpriseCode}].`);
                                this.exitCli(0);
                            }
                            else {
                                selection = undefined;
                            }
                        }
                    }
                    // PROJECT CONTEXT
                    if (this._projCfg && this._projCfgRepo) {
                        if (!this._projUserCfg)
                            this._projUserCfg = new codiac_relay_contracts_1.ProjectUserConfig();
                        if (this._projUserCfg.enterprise === undefined || this._projUserCfg.enterprise.enterpriseCode !== enterpriseCode) {
                            this._projUserCfg.enterprise = new codiac_relay_contracts_1.ProjectEnterpriseBinding(enterpriseCode);
                        }
                        // Are we sure we wanna set the exact current branch?  what if we're inheriting this cabinet from our branch lineage?
                        let branch = yield this._ops.gitCurrentBranchNameFor(this._ops.getConfigRoot());
                        if (branch)
                            this._projUserCfg.enterprise.cabinet = new codiac_relay_contracts_2.BranchBinding(branch, enterpriseCode, cabinet.name);
                        this._projCfgRepo.writeProjectUserConfig(this._projUserCfg);
                    }
                    // USER CONTEXT
                    if (this.userContext.currentEnterprise === undefined || this.userContext.currentEnterprise.enterprise !== enterpriseCode) {
                        this.userContext.currentEnterprise = new codiac_relay_contracts_1.EnterprisePointer(enterpriseCode);
                    }
                    this.userContext.currentEnterprise.cabinet = cabinet.name;
                    this.userContext.enterpriseContexts[this._tenantCode] = this.userContext.currentEnterprise;
                    this.userContextManager.writeUserContext(this.userContext);
                    this.logger.trace(`Cabinet`, cabinet);
                    // let cabCntx = new CabinetScope(cabinet.enterpriseCode, cabinet.environment, cabinet.name)
                    // this.logger.trace(`Cabinet context`, cabCntx)
                    resolve(cabinet);
                }
                catch (err) {
                    reject(err);
                }
            }));
        });
    }
    createAuditable() {
        let stamp = new Date(Date.now());
        return {
            created: stamp,
            createdBy: this.userContext.userName,
            modified: stamp,
            modifiedBy: this.userContext.userName
        };
    }
}
exports.EnterpriseContextualizer = EnterpriseContextualizer;
/** The value in a select list prompt designating the user's choice
 *  to create a new record instead of selecting from the existing choices. */
EnterpriseContextualizer.CHOICE_VALUE_FOR_NEW = "** create new **";
EnterpriseContextualizer.DEFAULT_REGISTRY_URL = "https://index.docker.io/v1";
EnterpriseContextualizer.DEFAULT_ASSET_PORT_NUMBER = 1787;
//# sourceMappingURL=enterprise-contextualizer.js.map