"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.csnamerSdk = void 0;
const codemodel_1 = require("@autorest/codemodel");
const codegen_1 = require("@azure-tools/codegen");
const linq_1 = require("@azure-tools/linq");
const model_state_1 = require("../utils/model-state");
const utility_1 = require("../sdk/utility");
const code_namer_1 = require("../utils/code-namer");
// function setPropertyNames(schema: Schema) {
//   // name each property in this schema
//   // skip-for-time-being
//   if (!isObjectSchema(schema)) {
//     return;
//   }
//   for (const propertySchema of values(schema.properties)) {
//     const propertyDetails = propertySchema.language.default;
//     const mutability = getMutability(propertySchema);
//     propertyDetails.required = propertySchema.required ?? false;
//     propertyDetails.readOnly = propertySchema.readOnly ?? false;
//     propertyDetails.read = mutability.read;
//     propertyDetails.update = mutability.update && !propertyDetails.readOnly;
//     propertyDetails.create = mutability.create && !propertyDetails.readOnly;
//     const className = schema.language.csharp?.name;
//     let pname = getPascalIdentifier(propertyDetails.name);
//     if (pname === className) {
//       pname = `${pname}Property`;
//     }
//     if (pname === 'default') {
//       pname = '@default';
//     }
//     propertySchema.language.csharp = {
//       ...propertyDetails,
//       name: pname // and so are the propertyNmaes
//     };
//     if (propertyDetails.isNamedStream) {
//       propertySchema.language.csharp.namedStreamPropertyName = pascalCase(fixLeadingNumber([...deconstruct(propertyDetails.name), 'filename']));
//     }
//   }
// }
function csharpForArray(elementType, helper, nullable = true) {
    if (elementType.type === codemodel_1.SchemaType.Array) {
        // recursively generate the csharpForArray
        return `System.Collections.Generic.IList<${csharpForArray(elementType.elementType, helper)}>`;
    }
    else if (elementType.type === codemodel_1.SchemaType.Dictionary) {
        return `System.Collections.Generic.IList<${csharpForDict(elementType, helper)}>`;
    }
    const rawElementType = elementType;
    elementType = rawElementType;
    if ((rawElementType.type === codemodel_1.SchemaType.Choice || rawElementType.type === codemodel_1.SchemaType.SealedChoice) && !helper.IsEnum(rawElementType)) {
        elementType = rawElementType.choiceType;
    }
    let type = helper.GetCsharpType(elementType);
    if (elementType.type === 'any') {
        type = 'object';
    }
    const postfix = ((type && type !== 'string' && type !== 'object') || helper.IsEnum(rawElementType)) && nullable ? '?' : '';
    return `System.Collections.Generic.IList<${type ? type + postfix :
        (helper.IsEnum(rawElementType) && nullable ? rawElementType.language.default.name + '?' : rawElementType.language.default.name)}>`;
}
function csharpForDict(dictSchema, helper) {
    const rawElementType = dictSchema.elementType;
    let elementType = rawElementType;
    if ((rawElementType.type === codemodel_1.SchemaType.Choice || rawElementType.type === codemodel_1.SchemaType.SealedChoice) && !helper.IsEnum(rawElementType)) {
        elementType = rawElementType.choiceType;
    }
    let valueType = helper.GetCsharpType(elementType) ? helper.GetCsharpType(elementType) : (rawElementType.type === codemodel_1.SchemaType.Array ? csharpForArray(rawElementType.elementType, helper) : rawElementType.language.default.name);
    if (rawElementType.type === 'any') {
        valueType = 'object';
    }
    else if (rawElementType.type === codemodel_1.SchemaType.Dictionary) {
        valueType = csharpForDict(rawElementType, helper);
    }
    if (((helper.GetCsharpType(elementType) && valueType !== 'string') || helper.IsEnum(rawElementType)) && dictSchema.nullableItems != false) {
        valueType += '?';
    }
    return `System.Collections.Generic.IDictionary<string, ${valueType}>`;
}
function setSchemaNames(schemaGroups, azure, serviceNamespace, helper) {
    var _a;
    const baseNamespace = new Set();
    const subNamespace = new Map();
    for (const group of (0, linq_1.values)(schemaGroups)) {
        for (const schema of group) {
            if (schema.language.default.skip) {
                continue;
            }
            let thisNamespace = baseNamespace;
            let thisApiversion = '';
            // create the namespace if required
            if (azure) {
                const versions = [...(0, linq_1.values)(schema.apiVersions).select(v => v.version)];
                if (((_a = schema.language.default) === null || _a === void 0 ? void 0 : _a.uid) !== 'universal-parameter-type') {
                    if (versions && (0, linq_1.length)(versions) > 0) {
                        thisApiversion = (0, codegen_1.minimum)(versions);
                        thisNamespace = subNamespace.get(thisApiversion) || new Set();
                        subNamespace.set(thisApiversion, thisNamespace);
                    }
                }
            }
            // for each schema, we're going to set the name
            // to the suggested name, unless we have collisions
            // at which point, we're going to add a number (for now?)
            const details = schema.language.default;
            let schemaName = details.name;
            const apiName = (!thisApiversion) ? '' : (0, codegen_1.getPascalIdentifier)(`Api ${thisApiversion}`);
            let n = 1;
            while (thisNamespace.has(schemaName)) {
                schemaName = `${details.name}_${n++}`;
            }
            thisNamespace.add(schemaName);
            // object types.
            if (schema.type === codemodel_1.SchemaType.Object) {
                schema.language.default.name = (0, code_namer_1.getEscapedReservedName)(schemaName, 'Model');
                schema.language.csharp = {
                    ...details,
                    apiversion: thisApiversion,
                    apiname: apiName,
                    name: (0, code_namer_1.getEscapedReservedName)(schemaName, 'Model'),
                    namespace: (0, codegen_1.pascalCase)([serviceNamespace, '.', 'Models']),
                    fullname: (0, code_namer_1.getEscapedReservedName)(schemaName, 'Model'),
                };
            }
            else if (schema.type === codemodel_1.SchemaType.Any) {
                schema.language.csharp = {
                    ...details,
                    apiversion: thisApiversion,
                    apiname: apiName,
                    name: schemaName,
                    fullname: 'object',
                };
            }
            else if (schema.type === codemodel_1.SchemaType.Array) {
                schema.language.csharp = {
                    ...details,
                    apiversion: thisApiversion,
                    apiname: apiName,
                    name: schemaName,
                    fullname: csharpForArray(schema.elementType, helper, schema.nullableItems != false),
                };
            }
            else if (schema.type === codemodel_1.SchemaType.Choice || schema.type === codemodel_1.SchemaType.SealedChoice) {
                // oh, it's an enum type
                // ToDo: comment out for time being
                // const choiceSchema = <ChoiceSchema<StringSchema> | SealedChoiceSchema<StringSchema>>schema;
                // schema.language.csharp = <SchemaDetails>{
                //   ...details,
                //   interfaceName: 'I' + pascalCase(fixLeadingNumber([...deconstruct(schemaName)])),
                //   name: getPascalIdentifier(schemaName),
                //   namespace: pascalCase([serviceNamespace, '.', 'Support']),
                //   fullname: `${pascalCase([serviceNamespace, '.', 'Support'])}.${getPascalIdentifier(schemaName)}`,
                //   enum: {
                //     ...schema.language.default.enum,
                //     name: getPascalIdentifier(schema.language.default.name),
                //     values: choiceSchema.choices.map(each => {
                //       return {
                //         ...each,
                //         name: getPascalIdentifier(each.language.default.name),
                //         description: each.language.default.description
                //       };
                //     })
                //   }
                // };
                const choiceSchema = schema;
                schema.language.csharp = {
                    ...details,
                    interfaceName: 'I' + (0, codegen_1.pascalCase)((0, codegen_1.fixLeadingNumber)([...(0, codegen_1.deconstruct)(schemaName)])),
                    name: schemaName,
                    namespace: (0, codegen_1.pascalCase)([serviceNamespace, '.', 'Support']),
                    fullname: choiceSchema.extensions && !choiceSchema.extensions['x-ms-model-as-string'] && choiceSchema.choiceType.type === codemodel_1.SchemaType.String ? schemaName : helper.GetCsharpType(choiceSchema.choiceType),
                    enum: {
                        ...schema.language.default.enum,
                        name: schemaName,
                        values: choiceSchema.choices.map(each => {
                            return {
                                ...each,
                                name: each.language.default.name,
                                description: each.language.default.description
                            };
                        })
                    }
                };
            }
            else if (schema.type !== codemodel_1.SchemaType.Dictionary) {
                // here are primitive types
                const schemaDetails = {
                    ...details,
                    name: schemaName,
                    fullname: helper.GetCsharpType(schema)
                };
                // add jonconverters for some types
                if (schema.type === codemodel_1.SchemaType.Date) {
                    schemaDetails.jsonConverters = schemaDetails.jsonConverters || [];
                    schemaDetails.jsonConverters.push('Microsoft.Rest.Serialization.DateJsonConverter');
                }
                else if (schema.type === codemodel_1.SchemaType.UnixTime) {
                    schemaDetails.jsonConverters = schemaDetails.jsonConverters || [];
                    schemaDetails.jsonConverters.push('Microsoft.Rest.Serialization.UnixTimeJsonConverter');
                }
                schema.language.csharp = schemaDetails;
                // xichen: for invalid namespace case, we won't create model class. So we do not need consider dup case
                thisNamespace.delete(schemaName);
            }
            else {
                // handle dictionary
                const rawElementType = schema.elementType;
                let elementType = rawElementType;
                if ((rawElementType.type === codemodel_1.SchemaType.Choice || rawElementType.type === codemodel_1.SchemaType.SealedChoice) && !helper.IsEnum(rawElementType)) {
                    elementType = rawElementType.choiceType;
                }
                let valueType = helper.GetCsharpType(elementType) ? helper.GetCsharpType(elementType) : (rawElementType.type === codemodel_1.SchemaType.Array ? csharpForArray(rawElementType.elementType, helper) : rawElementType.language.default.name);
                if (rawElementType.type === 'any') {
                    valueType = 'object';
                }
                if (((helper.GetCsharpType(elementType) && valueType !== 'string') || helper.IsEnum(rawElementType)) && schema.nullableItems != false) {
                    valueType += '?';
                }
                schema.language.csharp = {
                    ...details,
                    apiversion: thisApiversion,
                    apiname: apiName,
                    name: schemaName,
                    fullname: csharpForDict(schema, helper),
                };
            }
        }
    }
}
// async function setOperationNames(state: State, resolver: SchemaDefinitionResolver) {
//   // keep a list of operation names that we've assigned.
//   const operationNames = new Set<string>();
//   for (const operationGroup of values(state.model.operationGroups)) {
//     for (const operation of values(operationGroup.operations)) {
//       const details = operation.language.default;
//       // come up with a name
//       const oName = getPascalIdentifier(operationGroup.$key + '_' + details.name);
//       let i = 1;
//       let operationName = oName;
//       while (operationNames.has(operationName)) {
//         // if we have used that name, try again.
//         operationName = `${oName}${i++}`;
//       }
//       operationNames.add(operationName);
//       operation.language.csharp = {
//         ...details, // inherit
//         name: operationName,
//       };
//       // parameters are camelCased.
//       for (const parameter of values(operation.parameters)) {
//         const parameterDetails = parameter.language.default;
//         let propName = camelCase(fixLeadingNumber(deconstruct(parameterDetails.serializedName)));
//         if (propName === 'default') {
//           propName = '@default';
//         }
//         parameter.language.csharp = {
//           ...parameterDetails,
//           name: propName
//         };
//       }
//       const responses = [...values(operation.responses), ...values(operation.exceptions)];
//       for (const rsp of responses) {
//         // per responseCode
//         const response = <SchemaResponse>rsp;
//         const responseTypeDefinition = response.schema ? resolver.resolveTypeDeclaration(<any>response.schema, true, state) : undefined;
//         const headerSchema = response.language.default.headerSchema;
//         const headerTypeDefinition = headerSchema ? resolver.resolveTypeDeclaration(<any>headerSchema, true, state.path('schemas', headerSchema.language.default.name)) : undefined;
//         let code = (System.Net.HttpStatusCode[response.protocol.http?.statusCodes[0]] ? System.Net.HttpStatusCode[response.protocol.http?.statusCodes[0]].value : (response.protocol.http?.statusCodes[0] || '')).replace('global::System.Net.HttpStatusCode', '');
//         let rawValue = code.replace(/\./, '');
//         if (response.protocol.http?.statusCodes[0] === 'default' || rawValue === 'default' || '') {
//           rawValue = 'any response code not handled elsewhere';
//           code = 'default';
//           response.language.default.isErrorResponse = true;
//         }
//         response.language.csharp = {
//           ...response.language.default,
//           responseType: responseTypeDefinition ? responseTypeDefinition.declaration : '',
//           headerType: headerTypeDefinition ? headerTypeDefinition.declaration : '',
//           name: (length(response.protocol.http?.mimeTypes) <= 1) ?
//             camelCase(fixLeadingNumber(deconstruct(`on ${code}`))) : // the common type (or the only one.)
//             camelCase(fixLeadingNumber(deconstruct(`on ${code} ${response.protocol.http?.mimeTypes[0]}`))),
//           description: (length(response.protocol.http?.mimeTypes) <= 1) ?
//             `a delegate that is called when the remote service returns ${response.protocol.http?.statusCodes[0]} (${rawValue}).` :
//             `a delegate that is called when the remote service returns ${response.protocol.http?.statusCodes[0]} (${rawValue}) with a Content-Type matching ${response.protocol.http?.mimeTypes.join(',')}.`
//         };
//       }
//     }
//   }
// }
function duplicateLRO(model) {
    for (const operationGroup of model.operationGroups) {
        for (const operation of operationGroup.operations) {
            if (operation.extensions && operation.extensions['x-ms-long-running-operation']) {
                const duplicate = new codemodel_1.Operation('Begin' + operation.language.default.name, '', operation);
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const extensions = Object.assign({}, duplicate.extensions);
                delete extensions['x-ms-long-running-operation'];
                if (extensions && 'x-ms-examples' in extensions) {
                    delete extensions['x-ms-examples'];
                }
                duplicate.extensions = extensions;
                duplicate.language.default.original = duplicate.language.default.name;
                duplicate.language.default.name = 'Begin' + duplicate.language.default.name;
                operationGroup.operations.push(duplicate);
            }
        }
    }
}
const xmsPageable = 'x-ms-pageable';
// nextLineName is required parameter in 'x-ms-pageable' but its value could be null
const defaultNextLinkName = '';
const defaultItemName = 'value';
function getPageClass(operation, model) {
    if (!operation.extensions || !(xmsPageable in operation.extensions)) {
        return null;
    }
    const nextLinkName = operation.extensions[xmsPageable].nextLinkName || defaultNextLinkName;
    const itemName = operation.extensions[xmsPageable].itemName || defaultItemName;
    const pair = `${nextLinkName} ${itemName}`;
    if (!(pair in model.language.default.pageClasses)) {
        const className = Object.keys(model.language.default.pageClasses).length > 0 ? `Page${Object.keys(model.language.default.pageClasses).length}` : 'Page';
        model.language.default.pageClasses[pair] = className;
    }
    return model.language.default.pageClasses[pair];
}
function addNextPageOperation(model) {
    model.language.default.pageClasses = model.language.default.pageClasses || {};
    for (const operationGroup of model.operationGroups) {
        for (const operation of operationGroup.operations) {
            if (operation.extensions && xmsPageable in operation.extensions) {
                operation.language.default.pageable = {
                    pageType: getPageClass(operation, model),
                    ipageType: operation.extensions[xmsPageable].nextLinkName ? 'Microsoft.Rest.Azure.IPage' : 'System.Collections.Generic.IEnumerable',
                    nextLinkName: operation.extensions[xmsPageable].nextLinkName || defaultNextLinkName,
                    itemName: operation.extensions[xmsPageable].itemName || defaultItemName,
                    operationName: operation.extensions[xmsPageable].operationName,
                    nextPageOperation: false,
                };
                const extensions = Object.assign({}, operation.extensions);
                delete extensions[xmsPageable];
                if (operation.extensions[xmsPageable].nextLinkName) {
                    const nextPageOperation = new codemodel_1.Operation(operation.extensions[xmsPageable].operationName || `${operation.language.default.name}Next`, '', operation);
                    nextPageOperation.language.default.pageable = {
                        pageType: getPageClass(operation, model),
                        ipageType: operation.extensions[xmsPageable].nextLinkName ? 'Microsoft.Rest.Azure.IPage' : 'System.Collections.Generic.IEnumerable',
                        nextLinkName: operation.extensions[xmsPageable].nextLinkName || defaultNextLinkName,
                        itemName: operation.extensions[xmsPageable].itemName || defaultItemName,
                        operationName: operation.extensions[xmsPageable].operationName || `${operation.language.default.name}Next`,
                        nextPageOperation: true,
                    };
                    nextPageOperation.extensions = extensions;
                    nextPageOperation.language.default.original = `${operation.language.default.name}`;
                    // Set operation name, the name initialization in new Operation() doesn't work
                    nextPageOperation.language.default.name = nextPageOperation.language.default.pageable.operationName || `${nextPageOperation.language.default.pageable.operationName}Next`;
                    operationGroup.operations.push(nextPageOperation);
                }
                operation.extensions = extensions;
            }
        }
    }
}
function correctParameterNames(model) {
    for (const operationGroup of model.operationGroups) {
        for (const operation of operationGroup.operations) {
            for (const parameter of (0, linq_1.values)(operation.parameters)) {
                // Use suffix 'property' for parameters in parameter group since we use 'property' for schema names
                const suffix = parameter.extensions && parameter.extensions['x-ms-parameter-grouping'] ? 'Property' : 'Parameter';
                parameter.language.default.name = (0, code_namer_1.getEscapedReservedName)(parameter.language.default.name, suffix);
            }
            if (operation.requests) {
                // body parameters
                for (const parameter of (0, linq_1.values)(operation.requests[0].parameters)) {
                    parameter.language.default.name = (0, code_namer_1.getEscapedReservedName)(parameter.language.default.name, 'Parameter');
                }
            }
        }
    }
}
async function nameStuffRight(state) {
    const useDateTimeOffset = await state.getValue('useDateTimeOffset', false);
    const helper = new utility_1.Helper(useDateTimeOffset);
    const model = state.model;
    // set the namespace for the service
    const serviceNamespace = await state.getValue('namespace', 'Microsoft.Azure.Sample');
    const azure = await state.getValue('azure', false) || await state.getValue('azure-arm', false);
    const clientName = (0, codegen_1.getPascalIdentifier)(model.language.default.name);
    // dolauli see model.details.csharp for c# related staff
    // set c# client details (name)
    model.language.csharp = {
        ...model.language.default,
        name: clientName,
        namespace: serviceNamespace,
        fullname: `${serviceNamespace}.${clientName}`
    };
    setSchemaNames(model.schemas, azure, serviceNamespace, helper);
    duplicateLRO(model);
    addNextPageOperation(model);
    correctParameterNames(model);
    return model;
}
async function csnamerSdk(service) {
    const state = await new model_state_1.ModelState(service).init();
    await service.writeFile({ filename: 'sdk-code-model-v4-csnamer.yaml', content: (0, codegen_1.serialize)(await nameStuffRight(state)), sourceMap: undefined, artifactType: 'code-model-v4' });
}
exports.csnamerSdk = csnamerSdk;
//# sourceMappingURL=sdk-cs-namer.js.map