"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createCommandsV2 = exports.Inferrer = exports.titleToAzureServiceName = void 0;
const codemodel_1 = require("@autorest/codemodel");
const codegen_1 = require("@azure-tools/codegen");
const linq_1 = require("@azure-tools/linq");
const extension_base_1 = require("@autorest/extension-base");
const linq_2 = require("@azure-tools/linq");
const verbs_1 = require("../internal/verbs");
const components_1 = require("../utils/components");
const model_state_1 = require("../utils/model-state");
//import { Schema as SchemaV3 } from '../utils/schema';
const command_operation_1 = require("../utils/command-operation");
const command_operation_2 = require("../utils/command-operation");
const codemodel_2 = require("@autorest/codemodel");
const resourceName_1 = require("../utils/resourceName");
const http_operation_1 = require("../utils/http-operation");
const specialWords = { in: ['sign'] };
// UNUSED: Moved to plugin-tweak-model.ts in remodeler
// For now, we are not dynamically changing the service-name. Instead, we would figure out a method to change it during the creation of service readme's.
function titleToAzureServiceName(title) {
    const titleCamelCase = (0, codegen_1.pascalCase)((0, codegen_1.deconstruct)(title)).trim();
    const serviceName = titleCamelCase
        // Remove: !StartsWith(Management)AndContains(Management), Client, Azure, Microsoft, APIs, API, REST
        .replace(/(?!^Management)(?=.*)Management|Client|Azure|Microsoft|APIs|API|REST/g, '')
        // Remove: EndsWith(ServiceResourceProvider), EndsWith(ResourceProvider), EndsWith(DataPlane), EndsWith(Data)
        .replace(/ServiceResourceProvider$|ResourceProvider$|DataPlane$|Data$/g, '');
    return serviceName || titleCamelCase;
}
exports.titleToAzureServiceName = titleToAzureServiceName;
const pluralizationService = new codegen_1.EnglishPluralizationService();
pluralizationService.addWord('Database', 'Databases');
pluralizationService.addWord('database', 'databases');
pluralizationService.addWord('Premise', 'Premises');
pluralizationService.addWord('premise', 'premises');
function fn(active, remaining, result) {
    if ((0, linq_1.length)(active) || (0, linq_1.length)(remaining)) {
        if ((0, linq_1.length)(remaining)) {
            fn([...active, remaining[0]], remaining.slice(1), result);
            fn(active, remaining.slice(1), result);
        }
        else {
            result.push(active);
        }
    }
    return result;
}
function combinations(elements) {
    return fn([], elements, []);
}
function filterSpecialWords(preposition, parts) {
    let start = 0;
    let idx = -1;
    if (specialWords[preposition] !== undefined) {
        do {
            idx = parts.indexOf(preposition, start);
            if (idx <= 0 || !specialWords[preposition].includes(parts[idx - 1])) {
                return idx;
            }
            else {
                start = idx + 1;
            }
            // eslint-disable-next-line no-constant-condition
        } while (true);
    }
    return parts.indexOf(preposition);
}
function splitOnPreposition(preposition, parts) {
    const i = filterSpecialWords(preposition, parts);
    if (i > 0) {
        return [
            parts.slice(0, i),
            parts.slice(i + 1)
        ];
    }
    return undefined;
}
function splitOnAnyPreposition(parts) {
    for (const p of ['with', 'at', 'by', 'for', 'in', 'of']) {
        const result = splitOnPreposition(p, parts);
        if (result && result[0].length > 0) {
            // we found it, let's give it back.
            return result;
        }
    }
    return undefined;
}
class Inferrer {
    constructor(state) {
        this.state = state;
        this.commonParameters = new Array();
        // skip-for-time-being
        // isNameConflict(model: codemodel.Model, variant: CommandVariant, vname: string) {
        //   for (const each of values(model.commands.operations)) {
        //     if (each.details.default.name === vname) {
        //       return true;
        //     }
        //   }
        //   return false;
        // }
        // for tracking unique operation identities 
        this.operationIdentities = new Set();
        this.reservedPathParam = new Set(['SubscriptionId', 'resourceGroupName']);
    }
    async init() {
        this.commonParameters = await this.state.getValue('azure', false) ? [
            // 'resourceGroupName',
            'subscriptionId'
        ] : [];
        this.verbMap = await this.state.getValue('verb-mapping') || {};
        this.isAzure = await this.state.getValue('azure', false);
        this.prefix = await this.state.getValue('prefix');
        this.serviceName = titleToAzureServiceName(await this.state.getValue('service-name'));
        if (this.isAzure) {
            this.supportJsonInput = await this.state.getValue('support-json-input', true);
        }
        else {
            this.supportJsonInput = await this.state.getValue('support-json-input', false);
        }
        this.state.setValue('service-name', this.serviceName);
        this.subjectPrefix = await this.state.getValue('subject-prefix');
        this.state.setValue('isAzure', this.isAzure);
        this.state.setValue('prefix', this.prefix);
        const model = this.state.model;
        this.state.message({
            Channel: extension_base_1.Channel.Debug, Text: `[CMDLET-PREFIX] => '${model.language.default.prefix}'`
        });
        model.language.default.serviceName = this.serviceName;
        this.state.message({
            Channel: extension_base_1.Channel.Debug, Text: `[SERVICE-NAME] => '${model.language.default.serviceName}'`
        });
        model.language.default.subjectPrefix = this.subjectPrefix;
        this.state.message({
            Channel: extension_base_1.Channel.Debug, Text: `[SUBJECT-PREFIX] => '${model.language.default.subjectPrefix}'`
        });
        return this;
    }
    async createCommands() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
        const model = this.state.model;
        this.state.model.commands = {
            operations: new linq_1.Dictionary(),
            parameters: new linq_1.Dictionary(),
        };
        const disableGetPut = await this.state.getValue('disable-getput', false);
        const disableTransformIdentityType = await this.state.getValue('disable-transform-identity-type', false);
        const disableTransformIdentityTypeForOperation = await this.state.getValue('disable-transform-identity-type-for-operation', []);
        const optsToExclude = new Array();
        if (disableTransformIdentityTypeForOperation) {
            for (const item of (0, linq_1.values)(disableTransformIdentityTypeForOperation)) {
                optsToExclude.push(item);
            }
        }
        this.state.message({ Channel: extension_base_1.Channel.Debug, Text: 'detecting high level commands...' });
        for (const operationGroup of (0, linq_1.values)(model.operationGroups)) {
            let hasPatch = false;
            let commandType;
            const getOperations = [];
            let putOperation;
            let patchOperation;
            for (const operation of (0, linq_1.values)(operationGroup.operations)) {
                if (((_d = (_c = (_b = (_a = operation.requests) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.protocol) === null || _c === void 0 ? void 0 : _c.http) === null || _d === void 0 ? void 0 : _d.method.toLowerCase()) === 'patch') {
                    hasPatch = true;
                    patchOperation = operation;
                    // bez: remove patch operation to avoid conflicts with replacements
                    if (this.isAzure && !disableTransformIdentityType && this.IsManagedIdentityOperation(operation)) {
                        continue;
                    }
                }
                else if (((_h = (_g = (_f = (_e = operation.requests) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.protocol) === null || _g === void 0 ? void 0 : _g.http) === null || _h === void 0 ? void 0 : _h.method.toLowerCase()) === 'get') {
                    getOperations.push(operation);
                }
                else if (((_m = (_l = (_k = (_j = operation.requests) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.protocol) === null || _l === void 0 ? void 0 : _l.http) === null || _m === void 0 ? void 0 : _m.method.toLowerCase()) === 'put') {
                    putOperation = operation;
                    if (this.isAzure && !disableTransformIdentityType && this.IsManagedIdentityOperation(operation)) {
                        commandType = command_operation_1.CommandType.ManagedIdentityNew;
                    }
                }
                for (const variant of await this.inferCommandNames(operation, operationGroup.$key, this.state)) {
                    await this.addVariants(operation.parameters, operation, variant, '', this.state, undefined, commandType);
                }
            }
            // bez: if we have get+put, try to replace
            if (this.isAzure && getOperations && putOperation && ((_o = putOperation.requests) === null || _o === void 0 ? void 0 : _o.length) == 1) {
                const getOperation = getOperations.find(getOperation => { var _a, _b, _c, _d, _e, _f, _g, _h; return ((_d = (_c = (_b = (_a = getOperation.requests) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.protocol) === null || _c === void 0 ? void 0 : _c.http) === null || _d === void 0 ? void 0 : _d.path) === ((_h = (_g = (_f = (_e = putOperation === null || putOperation === void 0 ? void 0 : putOperation.requests) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.protocol) === null || _g === void 0 ? void 0 : _g.http) === null || _h === void 0 ? void 0 : _h.path); });
                const supportsCombineGetPutOperation = getOperation && this.supportsGetPut(getOperation, putOperation);
                if (!disableTransformIdentityType && supportsCombineGetPutOperation &&
                    (hasPatch && patchOperation && this.IsManagedIdentityOperation(patchOperation)
                        || !hasPatch && putOperation && this.IsManagedIdentityOperation(putOperation))) {
                    await this.addVariants(putOperation.parameters, putOperation, this.createCommandVariant('create', [operationGroup.$key], [], this.state.model), '', this.state, [getOperation], command_operation_1.CommandType.ManagedIdentityUpdate);
                }
                else if (!disableTransformIdentityType && !supportsCombineGetPutOperation && hasPatch && patchOperation && this.IsManagedIdentityOperation(patchOperation)) {
                    if (!optsToExclude.includes((_p = patchOperation.operationId) !== null && _p !== void 0 ? _p : '')) {
                        const transformIdentityTypeErrorMessage = `Parameter IdentityType in operation '${patchOperation.operationId}' can not be transformed as the best practice design. See https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/design-guidelines/managed-identity-best-practices.md#frequently-asked-question to mitigate this issue.`;
                        this.state.message({ Channel: extension_base_1.Channel.Error, Text: transformIdentityTypeErrorMessage });
                        throw new Error(transformIdentityTypeErrorMessage);
                    }
                    // bez: add patch operation back and disable transforming identity type
                    for (const variant of await this.inferCommandNames(patchOperation, operationGroup.$key, this.state)) {
                        await this.addVariants(patchOperation.parameters, patchOperation, variant, '', this.state);
                    }
                }
                else if (!disableGetPut && !hasPatch && supportsCombineGetPutOperation) {
                    /* generate variants for Update(Get+Put) for subjects only if:
                     - there is a get operation
                     - there is a put operation
                     - get operation path is the same as put operation path
                     - there is only one put request schema
                     - get operation response schema type is the same as put operation request schema type
                     */
                    await this.addVariants(putOperation.parameters, putOperation, this.createCommandVariant('create', [operationGroup.$key], [], this.state.model), '', this.state, [getOperation], command_operation_1.CommandType.GetPut);
                }
            }
            else if (this.isAzure && !disableTransformIdentityType && patchOperation && this.IsManagedIdentityOperation(patchOperation)) {
                if (!optsToExclude.includes((_q = patchOperation.operationId) !== null && _q !== void 0 ? _q : '')) {
                    const transformIdentityTypeErrorMessage = `Parameter IdentityType in operation '${patchOperation.operationId}' can not be transformed as the best practice design. See https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/design-guidelines/managed-identity-best-practices.md#frequently-asked-question to mitigate this issue.`;
                    this.state.message({ Channel: extension_base_1.Channel.Error, Text: transformIdentityTypeErrorMessage });
                    throw new Error(transformIdentityTypeErrorMessage);
                }
                // bez: add variants back and disable transforming identity type as no put or get
                for (const variant of await this.inferCommandNames(patchOperation, operationGroup.$key, this.state)) {
                    await this.addVariants(patchOperation.parameters, patchOperation, variant, '', this.state);
                }
            }
        }
        // for (const operation of values(model.http.operations)) {
        //   for (const variant of await this.inferCommandNames(operation, this.state)) {
        //     // no common parameters (standard variations)
        //     await this.addVariants(operation.parameters, operation, variant, '', this.state);
        //   }
        // }
        return model;
    }
    /**
     * Judge if the response of get operation can be piped as the input of put operation
     * 1. there is only one put request schema
     * 2. get operation response schema type is the same as put operation request schema type
     */
    supportsGetPut(getOperation, putOperation) {
        var _a, _b, _c, _d, _e, _f;
        const hasQueryParameter = (_a = getOperation === null || getOperation === void 0 ? void 0 : getOperation.parameters) === null || _a === void 0 ? void 0 : _a.find(p => { var _a; return ((_a = p.protocol.http) === null || _a === void 0 ? void 0 : _a.in) === 'query' && p.language.default.name !== 'apiVersion'; });
        //parameter.protocal.http.in === 'body' probably only applies to open api 2.0
        const schema = (_e = (_d = (_c = (_b = putOperation === null || putOperation === void 0 ? void 0 : putOperation.requests) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.parameters) === null || _d === void 0 ? void 0 : _d.find(p => { var _a; return ((_a = p.protocol.http) === null || _a === void 0 ? void 0 : _a.in) === 'body'; })) === null || _e === void 0 ? void 0 : _e.schema;
        return (_f = (getOperation && !hasQueryParameter && schema && [...(0, linq_1.values)(getOperation === null || getOperation === void 0 ? void 0 : getOperation.responses)].filter(each => each.schema !== schema).length === 0)) !== null && _f !== void 0 ? _f : false;
    }
    containsIdentityType(op) {
        var _a, _b, _c, _d, _e, _f;
        const body = ((_b = (_a = op.requests) === null || _a === void 0 ? void 0 : _a[0].parameters) === null || _b === void 0 ? void 0 : _b.find((p) => !p.origin || p.origin.indexOf('modelerfour:synthesized') < 0)) || null;
        // property identity in the body parameter
        const identityProperty = (body && body.schema && (0, codemodel_1.isObjectSchema)(body.schema)) ? (_d = (_c = (0, linq_1.values)((0, codemodel_1.getAllProperties)(body.schema)).where(property => !property.language.default.readOnly && (0, codemodel_1.isObjectSchema)(property.schema) && property.language.default.name === 'identity')) === null || _c === void 0 ? void 0 : _c.toArray()) === null || _d === void 0 ? void 0 : _d[0] : null;
        const identityTypeProperty = (identityProperty && identityProperty.schema && (0, codemodel_1.isObjectSchema)(identityProperty.schema)) ? (_f = (_e = (0, linq_1.values)((0, codemodel_1.getAllProperties)(identityProperty.schema)).where(property => !property.language.default.readOnly && property.language.default.name === 'type')) === null || _e === void 0 ? void 0 : _e.toArray()) === null || _f === void 0 ? void 0 : _f[0] : null;
        return identityTypeProperty !== null && identityTypeProperty !== undefined;
    }
    IsManagedIdentityOperation(op) {
        return this.containsIdentityType(op);
    }
    inferCommand(operation, group, suffix = []) {
        operation = operation.filter(each => each !== 'all');
        // no instant match 
        switch ((0, linq_1.length)(operation)) {
            case 0:
                throw new Error('Missing operation id?');
            case 1:
                // simple operation, just an id with a single value
                // OPERATION => OPERATION-GROUP
                return [
                    this.createCommandVariant(operation[0], [group], suffix, this.state.model)
                ];
            case 2:
                // should try to infer [SUBJECT] and [ACTION] from operation
                if (verbs_1.verbs.has(operation[0])) {
                    // [ACTION][SUBJECT]
                    return [
                        this.createCommandVariant(operation[0], [group, operation[1]], suffix, this.state.model)
                    ];
                }
                if (verbs_1.verbs.has(operation[1])) {
                    // [SUBJECT][ACTION]
                    return [
                        this.createCommandVariant(operation[1], [group, operation[0]], suffix, this.state.model)
                    ];
                }
                // can't tell which is the [ACTION] -- let's return it the way we used to, 
                // but now let's mention that we are doing that.
                this.state.warning(`Operation ${operation[0]}/${operation[1]} is inferred without finding action.`, [], {});
                return [
                    this.createCommandVariant(operation[0], [group, operation[1]], suffix, this.state.model)
                ];
        }
        // three or more words.
        // first, see if it's an 'or'
        const either = splitOnPreposition('or', operation);
        if (either) {
            // looks like we got two sets of operations from this.
            return [
                ...this.inferCommand([...either[0], ...either[1].slice(1)], group, suffix),
                ...this.inferCommand(either[1], group, suffix),
            ];
        }
        // any other preposition?
        const parts = splitOnAnyPreposition(operation);
        if (parts) {
            // so this is something like DoActionWithStyle
            return [...this.inferCommand(parts[0], group, parts[1])];
        }
        // if not, then seek out a verb from there.
        for (let i = 0; i < (0, linq_1.length)(operation); i++) {
            if (verbs_1.verbs.has(operation[i])) {
                // if the action is first
                if (i === 0) {
                    // everything else is the subject
                    return [this.createCommandVariant(operation[i], group ? [group, ...operation.slice(i + 1)] : operation.slice(i + 1), suffix, this.state.model)];
                }
                if (i === (0, linq_1.length)(operation) - 1) {
                    // if it's last, the subject would be the first thing
                    return [this.createCommandVariant(operation[i], group ? [group, ...operation.slice(0, i)] : operation.slice(0, i), suffix, this.state.model)];
                }
                // otherwise          
                // things before are part of the subject
                // things after the verb should be part of the suffix
                return [this.createCommandVariant(operation[i], group ? [group, ...operation.slice(i, i)] : operation.slice(i, i), [...suffix, ...operation.slice(i + 1)], this.state.model)];
            }
        }
        // so couldn't tell what the action was.
        // fallback to the original behavior with a warning.
        this.state.warning(`Operation ${operation[0]}/${operation[1]} is inferred without finding action.`, [], {});
        return [this.createCommandVariant(operation[0], group ? [group, ...operation.slice(1)] : operation.slice(1), [...suffix, ...operation.slice(1)], this.state.model)];
    }
    async inferCommandNames(op, group, state) {
        const method = op.language.default.name;
        // skip-for-time-being
        // if (!method) {
        //   if (!group) {
        //     // no operation id at all?
        //     const path = op.path.replace(/{.*?}/g, '').replace(/\/+/g, '/').replace(/\/$/g, '');
        //     method = path.split('/').last;
        //   } else {
        //     // no group given, use string as method
        //     method = group;
        //   }
        //   group = pascalCase(op.tags) || '';
        // }
        const groupWords = [...(0, codegen_1.removeSequentialDuplicates)((0, codegen_1.deconstruct)(group))];
        groupWords[groupWords.length - 1] = pluralizationService.singularize(groupWords.last);
        group = (0, codegen_1.pascalCase)(groupWords);
        const operation = (0, codegen_1.deconstruct)(method);
        // instant match
        if (this.verbMap[method]) {
            return [this.createCommandVariant(method, [group], [], state.model)];
        }
        // dig deeper.
        return this.inferCommand(operation, group);
    }
    async addVariant(vname, body, bodyParameterName, parameters, operation, variant, state, preOperations, commandType) {
        // beth: filter command description for New/Update command
        // positive test case: The operation to create or update the extension.
        // negative test case: Starts an UpdateRun 
        const createOrUpdateRegex = /((create or update)|(creates or updates)|(create)|(update)|(creates)|(updates))([^a-zA-Z0-9])/gi;
        operation.language.default.description = operation.language.default.description.replace(createOrUpdateRegex, `${variant.action.toLowerCase()} `);
        const op = await this.addCommandOperation(vname, parameters, operation, variant, state, preOperations, commandType);
        // if this has a body with it, let's add that parameter
        if (body && body.schema) {
            op.details.default.hasBody = true;
            op.parameters.push(new components_1.IParameter(bodyParameterName, body.schema, {
                required: body.required,
                details: {
                    default: {
                        description: body.schema.language.default.description,
                        name: (0, codegen_1.pascalCase)(bodyParameterName),
                        isBodyParameter: true,
                    }
                }
            }));
            // let's add a variant where it's expanded out.
            // *IF* the body is an object or dictionary
            if (body.schema.type === codemodel_1.SchemaType.Object || body.schema.type === codemodel_1.SchemaType.Dictionary || body.schema.type === codemodel_1.SchemaType.Any) {
                const opExpanded = await this.addCommandOperation(`${vname}Expanded`, parameters, operation, variant, state, preOperations, commandType);
                opExpanded.details.default.dropBodyParameter = true;
                opExpanded.parameters.push(new components_1.IParameter(`${bodyParameterName}Body`, body.schema, {
                    required: body.required,
                    details: {
                        default: {
                            description: body.schema.language.default.description,
                            name: (0, codegen_1.pascalCase)(`${bodyParameterName}Body`),
                            isBodyParameter: true,
                        }
                    }
                }));
            }
        }
        return op;
    }
    async addCommandOperation(vname, parameters, operation, variant, state, preOperations, commandType) {
        // skip-for-time-being following code seems redundant -----
        // let apiversion = '';
        var _a, _b, _c;
        // for (const each of items(operation.responses)) {
        //   for (const rsp of items(each)) {
        //     if (rsp.schema && rsp.schema.details && rsp.schema.details.default && rsp.schema.details.default.apiversion) {
        //       apiversion = rsp.schema.details.default.apiversion;
        //       break;
        //     }
        //   }
        // }
        // ----------------------------------------------------------
        // if vname is > 64 characters, let's trim it
        // after trimming it, make sure there aren't any other operation with a name that's exactly the same
        if ((0, linq_1.length)(vname) > 64) {
            const names = (0, codegen_1.deconstruct)(vname);
            let newVName = '';
            for (let i = 0; i < (0, linq_1.length)(names); i++) {
                newVName = (0, codegen_1.pascalCase)(names.slice(0, i));
                if ((0, linq_1.length)(newVName) > 60) {
                    break;
                }
            }
            vname = newVName;
        }
        // if we have an identical vname, let's add 'etc'
        let fname = `${variant.verb}-${variant.subject}-${vname}`;
        let n = 1;
        while (this.operationIdentities.has(fname)) {
            vname = `${vname.replace(/\d*$/g, '')}${n++}`;
            fname = `${variant.verb}-${variant.subject}-${vname}`;
        }
        this.operationIdentities.add(fname);
        variant.variant = vname;
        vname = (0, codegen_1.pascalCase)(vname);
        let commandName = operation.language.default.name;
        let operations = [operation];
        const language = { default: { ...operation.language.default } };
        //handle when there are pre operataions
        if (preOperations && preOperations.length > 0) {
            commandName = 'Update';
            language.default.name = 'Update';
            operations = [...preOperations, ...operations];
        }
        // skip-for-time-being x-ms-metadata looks not supported any more.
        //const xmsMetadata = operation.pathExtensions ? operation.pathExtensions['x-ms-metadata'] ? clone(operation.pathExtensions['x-ms-metadata']) : {} : {};
        // Add operation type to support x-ms-mutability
        let operationType = command_operation_2.OperationType.Other;
        if (operation.requests) {
            if (((_a = operation.requests[0].protocol.http) === null || _a === void 0 ? void 0 : _a.method) === 'put' && (variant.action.toLowerCase() === 'create' || variant.action.toLowerCase() === 'update' && variant.verb.toLowerCase() === 'set')) {
                // put create and put set
                operationType = command_operation_2.OperationType.Create;
            }
            else if ((((_b = operation.requests[0].protocol.http) === null || _b === void 0 ? void 0 : _b.method) === 'patch' || ((_c = operation.requests[0].protocol.http) === null || _c === void 0 ? void 0 : _c.method) === 'put') && variant.action.toLowerCase() === 'update') {
                // patch update, get+put update and exclude set update 
                operationType = command_operation_2.OperationType.Update;
            }
        }
        return state.model.commands.operations[`${(0, linq_1.length)(state.model.commands.operations)}`] = new command_operation_1.CommandOperation(commandName, {
            asjob: language.default.asjob ? true : false,
            operationType: operationType,
            extensions: {},
            ...variant,
            details: {
                ...language,
                default: {
                    ...language.default,
                    subject: variant.subject,
                    subjectPrefix: variant.subjectPrefix,
                    verb: variant.verb,
                    name: vname,
                    alias: variant.alias,
                    externalDocs: operation.externalDocs
                }
            },
            // operationId is not needed any more
            operationId: '',
            parameters: parameters.map(httpParameter => {
                // make it's own copy of the parameter since after this, 
                // the parameter can be altered for each operation individually.
                const each = (0, linq_2.clone)(httpParameter, false, undefined, undefined, ['schema', 'origin']);
                each.language.default = {
                    ...each.language.default,
                    name: (0, codegen_1.pascalCase)(each.language.default.name),
                    httpParameter
                };
                each.details = {};
                each.details.default = {
                    ...each.language.default,
                    name: (0, codegen_1.pascalCase)(each.language.default.name),
                    httpParameter
                };
                each.name = each.language.default.serializedName;
                return each;
            }),
            // skip-for-time-being, this callGraph is used in the header comments
            callGraph: operations
        }, commandType);
    }
    async addVariants(parameters, operation, variant, vname, state, preOperations, commandType) {
        var _a, _b, _c, _d, _e, _f;
        // now synthesize parameter set variants multiplexed by the variants.
        const [constants, requiredParameters] = (0, linq_1.values)(parameters).bifurcate(parameter => parameter.language.default.constantValue || parameter.language.default.fromHost ? true : false);
        const constantParameters = constants.map(each => `'${each.language.default.constantValue}'`);
        // the body parameter
        // xichen: How to handle if has multiple requests?
        const body = ((_b = (_a = operation.requests) === null || _a === void 0 ? void 0 : _a[0].parameters) === null || _b === void 0 ? void 0 : _b.find((p) => !p.origin || p.origin.indexOf('modelerfour:synthesized') < 0)) || null;
        // skip-for-time-being, looks x-ms-requestBody-name is not supported any more
        //const bodyParameterName = (operation.requestBody && operation.requestBody.extensions) ? operation.requestBody.extensions['x-ms-requestBody-name'] || 'bodyParameter' : '';
        const bodyParameterName = body ? body.language.default.name : '';
        // all the properties in the body parameter
        const bodyProperties = (body && body.schema && (0, codemodel_1.isObjectSchema)(body.schema)) ? (0, linq_1.values)((0, codemodel_1.getAllProperties)(body.schema)).where(property => !property.language.default.readOnly).toArray() : [];
        // smash body property names together
        const bodyPropertyNames = bodyProperties.joinWith(each => each.language.default.name);
        // for each polymorphic body, we should do a separate variant that takes the polymorphic body type instead of the base type
        // skip-for-time-being, this is for polymorphism
        //const polymorphicBodies = (body && body.schema && body.schema.details.default.polymorphicChildren && length(body.schema.details.default.polymorphicChildren)) ? (<Array<Schema>>body.schema.details.default.polymorphicChildren).joinWith(child => child.details.default.name) : '';
        // wait! "update" should be "set" if it's a PUT
        if (variant.verb === 'Update' && operation.requests && ((_d = (_c = operation.requests[0].protocol) === null || _c === void 0 ? void 0 : _c.http) === null || _d === void 0 ? void 0 : _d.method) === codemodel_1.HttpMethod.Put) {
            variant.verb = 'Set';
        }
        //for operations which has pre operations (only support GET+PUT for now), change it's action to update
        if (preOperations && preOperations.length > 0) {
            variant.action = variant.verb = 'Update';
        }
        // create variant 
        // skip-for-time-being, since operationId looks not included in m4.
        //state.message({ Channel: Channel.Debug, Text: `${variant.verb}-${variant.subject} //  ${operation.operationId} => ${JSON.stringify(variant)} taking ${requiredParameters.joinWith(each => each.name)}; ${constantParameters} ; ${bodyPropertyNames} ${polymorphicBodies ? `; Polymorphic bodies: ${polymorphicBodies} ` : ''}` });
        await this.addVariant((0, codegen_1.pascalCase)([variant.action, vname]), body, bodyParameterName, [...constants, ...requiredParameters], operation, variant, state, preOperations, commandType);
        if (await state.getValue('disable-via-identity', false)) {
            return;
        }
        // eslint-disable-next-line prefer-const
        let [pathParams, otherParams] = (0, linq_1.values)(requiredParameters).bifurcate(each => { var _a, _b; return ((_b = (_a = each === null || each === void 0 ? void 0 : each.protocol) === null || _a === void 0 ? void 0 : _a.http) === null || _b === void 0 ? void 0 : _b.in) === codemodel_1.ParameterLocation.Path; });
        //exclude subscriptionId and resourceGroupName from path parameters
        pathParams = pathParams.filter(pathParam => !this.reservedPathParam.has(pathParam.language.default.name));
        //if parent pipline input is disabled, only generate identity for current resource itself
        if (!await state.getValue('enable-parent-pipeline-input', this.isAzure)) {
            if ((0, linq_1.length)(pathParams) > 0 && variant.action.toLowerCase() != 'list') {
                await this.addVariant((0, codegen_1.pascalCase)([variant.action, vname, 'via-identity']), body, bodyParameterName, [...constants, ...otherParams], operation, variant, state, preOperations, commandType);
            }
            return;
        }
        const disableGetEnableList = await this.state.getValue('enable-parent-pipeline-input-for-list', false);
        /*
          for resource /A1/A2/.../An-1/An, generate variants that take
            ViaIdentity: An as identity
            ViaIdentity{An-1}: An-1 as identity + An Name
            ...
            ViaIdentity{A1}: A1 as identity + [A2 + A3 + ... + An-1 + An] Names
        */
        for (let i = pathParams.length - 1; i >= 0; i--) {
            if ((!disableGetEnableList && variant.action.toLowerCase() === 'list') || (disableGetEnableList && i === pathParams.length - 1 && variant.action.toLowerCase() === 'get')) {
                continue;
            }
            let resourceName = (0, resourceName_1.getResourceNameFromPath)((_f = (_e = operation.requests) === null || _e === void 0 ? void 0 : _e[0].protocol.http) === null || _f === void 0 ? void 0 : _f.path, pathParams[i].language.default.name, true);
            //cannot get resource name from path, give up generate ViaIdentity variant
            if (!resourceName) {
                break;
            }
            //variant for current resource is simply named ViaIdentity otherwise ViaIdentity${resourceName}
            if (i === pathParams.length - 1 && variant.action.toLowerCase() !== 'list') {
                resourceName = '';
            }
            await this.addVariant((0, codegen_1.pascalCase)([variant.action, vname, `via-identity${resourceName}`]), body, bodyParameterName, [...constants, ...otherParams, ...pathParams.slice(i + 1)], operation, variant, state, preOperations, commandType);
        }
        if (this.supportJsonInput && (0, http_operation_1.hasValidBodyParameters)(operation) &&
            commandType != command_operation_1.CommandType.GetPut && commandType != command_operation_1.CommandType.ManagedIdentityUpdate) {
            const createStringParameter = (name, description, serializedName) => {
                const schema = new codemodel_2.Schema(name, description, codemodel_1.SchemaType.String);
                const language = {
                    default: {
                        name: name,
                        description: description,
                        serializedName: serializedName,
                    },
                };
                schema.language = language;
                const httpParameter = {
                    implementation: 'Method',
                    language: language,
                    schema: schema,
                    required: true,
                };
                const parameter = new components_1.IParameter(name, schema, {
                    description: description,
                    required: true,
                    details: {
                        default: {
                            description: description,
                            name: name,
                            isBodyParameter: false,
                            httpParameter: httpParameter,
                        },
                    },
                    schema: schema,
                    allowEmptyValue: false,
                    deprecated: false,
                });
                parameter.httpParameter = httpParameter;
                return parameter;
            };
            const jsonVariant = (0, codegen_1.pascalCase)([variant.action, vname]);
            const parameter = new components_1.IParameter(`${bodyParameterName}Body`, body.schema, {
                details: {
                    default: {
                        description: body.schema.language.default.description,
                        name: (0, codegen_1.pascalCase)(`${bodyParameterName}Body`),
                        isBodyParameter: true,
                    }
                }
            });
            const opJsonString = await this.addVariant(`${jsonVariant}ViaJsonString`, null, '', [...constants, ...requiredParameters], operation, variant, state, preOperations, commandType);
            opJsonString.details.default.dropBodyParameter = true;
            opJsonString.parameters = opJsonString.parameters.filter(each => each.details.default.isBodyParameter !== true);
            opJsonString.parameters.push(createStringParameter('JsonString', `Json string supplied to the ${jsonVariant} operation`, 'jsonString'));
            opJsonString.parameters.push(parameter);
            opJsonString.details.default.dropBodyParameter = true;
            const opJsonFilePath = await this.addVariant(`${jsonVariant}ViaJsonFilePath`, null, '', [...constants, ...requiredParameters], operation, variant, state, preOperations, commandType);
            opJsonFilePath.details.default.dropBodyParameter = true;
            opJsonFilePath.parameters = opJsonFilePath.parameters.filter(each => each.details.default.isBodyParameter !== true);
            opJsonFilePath.parameters.push(createStringParameter('JsonFilePath', `Path of Json file supplied to the ${jsonVariant} operation`, 'jsonFilePath'));
            opJsonFilePath.parameters.push(parameter);
            opJsonFilePath.details.default.dropBodyParameter = true;
        }
    }
    createCommandVariant(action, subject, variant, model) {
        const verb = this.getPowerShellVerb(action);
        if (verb === 'Invoke') {
            // if the 'operation' name was  "post" -- it's kindof redundant.
            // so, only include the operation name in the group name if it's anything else
            if (action.toLowerCase() !== 'post') {
                subject = [action, ...subject];
            }
        }
        return {
            alias: [],
            subject: (0, codegen_1.pascalCase)([...(0, codegen_1.removeSequentialDuplicates)(subject.map(each => pluralizationService.singularize(each)))]),
            variant: (0, codegen_1.pascalCase)(variant),
            verb,
            subjectPrefix: model.language.default.subjectPrefix,
            action
        };
    }
    getPowerShellVerb(action) {
        const verb = this.verbMap[(0, codegen_1.pascalCase)(action)];
        if (verb) {
            return verb;
        }
        else {
            return 'Invoke';
        }
    }
}
exports.Inferrer = Inferrer;
async function createCommandsV2(service) {
    // return processCodeModel(commandCreator, service);
    //const session = await startSession<PwshModel>(service, {}, codeModelSchema);
    //const result = tweakModelV2(session);
    const state = await new model_state_1.ModelState(service).init();
    await service.writeFile({ filename: 'code-model-v4-createcommands-v2.yaml', content: (0, codegen_1.serialize)(await (await new Inferrer(state).init()).createCommands()), sourceMap: undefined, artifactType: 'code-model-v4' });
    // return processCodeModel(async (state) => {
    //   return await (await new Inferrer(state).init()).createCommands();
    // }, service, 'createCommands-v2');
}
exports.createCommandsV2 = createCommandsV2;
//# sourceMappingURL=create-commands-v2.js.map