"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonSerializableClass = void 0;
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
const codemodel_1 = require("@autorest/codemodel");
const codemodel_v3_1 = require("@azure-tools/codemodel-v3");
const codemodel_2 = require("@autorest/codemodel");
const codegen_1 = require("@azure-tools/codegen");
const linq_1 = require("@azure-tools/linq");
const codegen_csharp_1 = require("@azure-tools/codegen-csharp");
const codegen_csharp_2 = require("@azure-tools/codegen-csharp");
const codegen_csharp_3 = require("@azure-tools/codegen-csharp");
const codegen_csharp_4 = require("@azure-tools/codegen-csharp");
const codegen_csharp_5 = require("@azure-tools/codegen-csharp");
const codegen_csharp_6 = require("@azure-tools/codegen-csharp");
const codegen_csharp_7 = require("@azure-tools/codegen-csharp");
const codegen_csharp_8 = require("@azure-tools/codegen-csharp");
const codegen_csharp_9 = require("@azure-tools/codegen-csharp");
const codegen_csharp_10 = require("@azure-tools/codegen-csharp");
const codegen_csharp_11 = require("@azure-tools/codegen-csharp");
const codegen_csharp_12 = require("@azure-tools/codegen-csharp");
const codegen_csharp_13 = require("@azure-tools/codegen-csharp");
const clientruntime_1 = require("../clientruntime");
const codegen_csharp_14 = require("@azure-tools/codegen-csharp");
const primitive_1 = require("../schema/primitive");
const interface_1 = require("./interface");
class JsonSerializableClass extends codegen_csharp_2.Class {
    constructor(modelClass, objectInitializer) {
        var _a;
        super(modelClass.namespace, modelClass.name);
        this.modelClass = modelClass;
        this.apply(objectInitializer);
        this.partial = true;
        this.description = modelClass.description;
        this.addPartialMethods();
        // set up the declaration for the toJson method.
        const container = new codegen_csharp_6.Parameter('container', clientruntime_1.ClientRuntime.JsonObject, { description: `The <see cref="${clientruntime_1.ClientRuntime.JsonObject}"/> container to serialize this object into. If the caller passes in <c>null</c>, a new instance will be created and returned to the caller.` });
        const mode = new codegen_csharp_6.Parameter('serializationMode', clientruntime_1.ClientRuntime.SerializationMode, { description: `Allows the caller to choose the depth of the serialization. See <see cref="${clientruntime_1.ClientRuntime.SerializationMode}"/>.` });
        const toJsonMethod = this.addMethod(new codegen_csharp_5.Method('ToJson', clientruntime_1.ClientRuntime.JsonNode, {
            parameters: [container, mode],
            description: `Serializes this instance of <see cref="${this.name}" /> into a <see cref="${clientruntime_1.ClientRuntime.JsonNode}" />.`,
            returnsDescription: `a serialized instance of <see cref="${this.name}" /> as a <see cref="${clientruntime_1.ClientRuntime.JsonNode}" />.`
        }));
        // setup the declaration for the json deserializer constructor
        const jsonParameter = new codegen_csharp_6.Parameter('json', clientruntime_1.ClientRuntime.JsonObject, { description: `A ${clientruntime_1.ClientRuntime.JsonObject} instance to deserialize from.` });
        const deserializerConstructor = this.addMethod(new codegen_csharp_3.Constructor(this, {
            parameters: [jsonParameter], access: codegen_csharp_1.Access.Internal,
            description: `Deserializes a ${clientruntime_1.ClientRuntime.JsonObject} into a new instance of <see cref="${this.name}" />.`
        }));
        const serializeStatements = new codegen_csharp_11.Statements();
        const deserializeStatements = new codegen_csharp_11.Statements();
        this.excludes = '';
        if (this.modelClass.dictionaryImpl) {
            const vType = this.modelClass.dictionaryImpl.valueType;
            // we have to ensure that all the known wire-names are excluded on deserialization.
            const exclusions = new codegen_csharp_6.Parameter('exclusions', codegen_csharp_1.System.Collections.Generic.HashSet(codegen_csharp_14.dotnet.String), { defaultInitializer: codegen_csharp_14.dotnet.Null });
            deserializerConstructor.parameters.push(exclusions);
            this.excludes = [...(0, linq_1.values)((0, codemodel_2.getAllProperties)(this.modelClass.schema)).select(each => each.serializedName).select(each => new codegen_csharp_1.StringExpression(each))].join();
            this.excludes = this.excludes ? `,${codegen_csharp_1.System.Collections.Generic.HashSet(codegen_csharp_14.dotnet.String).new()}{ ${this.excludes} }` : '';
            const ap = `((${clientruntime_1.ClientRuntime}.IAssociativeArray<${vType.declaration}>)this).AdditionalProperties`;
            if (this.modelClass.dictionaryImpl.ownsDictionary) {
                // we have to implement the deserializer for it.
                serializeStatements.push(new codegen_csharp_11.Statements(`${clientruntime_1.ClientRuntime.JsonSerializable}.ToJson( ${ap}, ${container});`));
                if (vType === codegen_csharp_1.System.Object) {
                    // wildcard style
                    deserializeStatements.push(new codegen_csharp_11.Statements(`${clientruntime_1.ClientRuntime.JsonSerializable}.FromJson( json, ${ap}, ${clientruntime_1.ClientRuntime.JsonSerializable}.DeserializeDictionary(()=>${codegen_csharp_1.System.Collections.Generic.Dictionary(codegen_csharp_1.System.String, codegen_csharp_1.System.Object).new()}),${exclusions.value} );`));
                }
                else if (vType instanceof interface_1.ModelInterface) {
                    // use the class of the dictionary value to deserialize values
                    deserializeStatements.push(new codegen_csharp_11.Statements(`${clientruntime_1.ClientRuntime.JsonSerializable}.FromJson( json, ${ap}, (j) => ${vType.classImplementation.fullName}.FromJson(j) ,${exclusions.value} );`));
                }
                else {
                    deserializeStatements.push(new codegen_csharp_11.Statements(`${clientruntime_1.ClientRuntime.JsonSerializable}.FromJson( json, ${ap}, null ,${exclusions.value} );`));
                }
            }
        }
        for (const each of (0, linq_1.values)(modelClass.backingFields)) {
            serializeStatements.add(`${each.field.value}?.ToJson(${container}, ${mode.use});`);
            const sch = each.typeDeclaration.schema;
            const dictSchema = sch.type === codemodel_1.SchemaType.Dictionary ? sch :
                sch.type === codemodel_1.SchemaType.Object ? (_a = sch.parents) === null || _a === void 0 ? void 0 : _a.immediate.find((s) => s.type === codemodel_1.SchemaType.Dictionary) :
                    undefined;
            if (dictSchema) {
                deserializeStatements.add(`${each.field.value} = new ${each.className}(json${this.excludes});`);
            }
            else {
                deserializeStatements.add(`${each.field.value} = new ${each.className}(json);`);
            }
        }
        (0, primitive_1.pushTempVar)();
        for (const prop of (0, linq_1.values)(modelClass.ownedProperties)) {
            if (prop.language.csharp.HeaderProperty === 'Header') {
                continue;
            }
            const serializeStatement = prop.type.serializeToContainerMember(codemodel_v3_1.KnownMediaType.Json, prop.valuePrivate, container, prop.serializedName, mode);
            if (!prop.language.csharp.read && !prop.language.csharp.create && !prop.language.csharp.update) {
                continue;
            }
            else if (!(prop.language.csharp.read && prop.language.csharp.create && prop.language.csharp.update)) {
                const accessControlArray = new Array();
                if (prop.language.csharp.read) {
                    accessControlArray.push(clientruntime_1.ClientRuntime.SerializationMode.IncludeRead.toString());
                }
                if (prop.language.csharp.update) {
                    accessControlArray.push(clientruntime_1.ClientRuntime.SerializationMode.IncludeUpdate.toString());
                }
                if (prop.language.csharp.create) {
                    accessControlArray.push(clientruntime_1.ClientRuntime.SerializationMode.IncludeCreate.toString());
                }
                if (accessControlArray.length > 0) {
                    for (let i = 0; i < accessControlArray.length; i++) {
                        accessControlArray[i] = `${mode.use}.HasFlag(${accessControlArray[i]})`;
                    }
                    const accessControl = accessControlArray.join('||');
                    serializeStatements.add((0, codegen_csharp_9.If)(accessControl, serializeStatement));
                }
            }
            else {
                serializeStatements.add(serializeStatement);
            }
            if (prop.language.csharp.read) {
                deserializeStatements.add(prop.assignPrivate(prop.type.deserializeFromContainerMember(codemodel_v3_1.KnownMediaType.Json, jsonParameter, prop.serializedName, new codegen_csharp_1.LiteralExpression(prop.backingName))));
            }
        }
        (0, primitive_1.popTempVar)();
        const $this = this;
        // generate the implementation for toJson
        toJsonMethod.add(function* () {
            yield `${container} = ${container} ?? new ${clientruntime_1.ClientRuntime.JsonObject.declaration}();`;
            yield codegen_1.EOL;
            yield 'bool returnNow = false;';
            yield `${$this.btj.name}(ref ${container}, ref returnNow);`;
            yield (0, codegen_csharp_9.If)((0, codegen_csharp_4.toExpression)('returnNow'), `return ${container};`);
            // get serialization statements
            yield serializeStatements;
            yield `${$this.atj.name}(ref ${container});`;
            yield (0, codegen_csharp_10.Return)(container);
        });
        // and let's fill in the deserializer constructor statements now.
        deserializerConstructor.add(function* () {
            yield 'bool returnNow = false;';
            yield `${$this.bfj.name}(json, ref returnNow);`;
            yield (0, codegen_csharp_9.If)((0, codegen_csharp_4.toExpression)('returnNow'), 'return;');
            yield deserializeStatements;
            yield `${$this.afj.name}(json);`;
        });
    }
    get definition() {
        const $this = this.modelClass;
        // gotta write this just before we write out the class, since we had to wait until everyone had reported to their parents.
        const d = this.modelClass.discriminators;
        const isp = this.modelClass.isPolymorphic;
        // create the FromJson method
        const node = new codegen_csharp_6.Parameter('node', clientruntime_1.ClientRuntime.JsonNode, { description: `a <see cref="${clientruntime_1.ClientRuntime.JsonNode}" /> to deserialize from.` });
        const fromJson = this.addMethod(new codegen_csharp_5.Method('FromJson', this.modelClass.modelInterface, {
            parameters: [node], static: codegen_csharp_1.Modifier.Static,
            description: `Deserializes a <see cref="${clientruntime_1.ClientRuntime.JsonNode}"/> into an instance of ${this.modelClass.modelInterface}.`,
            returnsDescription: `an instance of ${this.modelClass.modelInterface}.`
        }));
        if (isp) {
            fromJson.description = fromJson.description + `\n Note: the ${this.modelClass.modelInterface} interface is polymorphic, and the precise model class that will get deserialized is determined at runtime based on the payload.`;
        }
        const $excludes = this.excludes;
        fromJson.add(function* () {
            const json = (0, codegen_csharp_4.IsDeclaration)(node, clientruntime_1.ClientRuntime.JsonObject, 'json');
            if (isp) {
                yield (0, codegen_csharp_9.If)((0, codegen_csharp_9.Not)(json.check), (0, codegen_csharp_10.Return)(codegen_csharp_14.dotnet.Null));
                yield '// Polymorphic type -- select the appropriate constructor using the discriminator';
                /** go thru the list of polymorphic values for the discriminator, and call the target class's constructor for that */
                if ($this.schema.discriminator) {
                    yield (0, codegen_csharp_12.Switch)((0, codegen_csharp_4.toExpression)(`json.StringProperty("${$this.schema.discriminator.property.serializedName}")`), function* () {
                        for (const { key, value } of (0, linq_1.items)(d)) {
                            yield (0, codegen_csharp_8.TerminalCase)(`"${key}"`, function* () {
                                yield (0, codegen_csharp_10.Return)(value.new(json));
                            });
                        }
                    });
                }
                yield (0, codegen_csharp_10.Return)($this.new(json, (0, codegen_csharp_4.toExpression)($excludes.substring(1))));
            }
            else {
                // just tell it to create the instance (providing that it's a JSonObject)
                yield (0, codegen_csharp_10.Return)((0, codegen_csharp_13.Ternery)(json.check, $this.new(json, (0, codegen_csharp_4.toExpression)($excludes.substring(1))), codegen_csharp_14.dotnet.Null));
            }
        });
        return super.definition;
    }
    get fileName() {
        return `${super.fileName}.json`;
    }
    addPartialMethods() {
        // add partial methods for future customization
        this.btj = this.addMethod(new codegen_csharp_5.PartialMethod('BeforeToJson', codegen_csharp_14.dotnet.Void, {
            access: codegen_csharp_1.Access.Default,
            parameters: [
                new codegen_csharp_6.Parameter('container', clientruntime_1.ClientRuntime.JsonObject, { modifier: codegen_csharp_7.ParameterModifier.Ref, description: 'The JSON container that the serialization result will be placed in.' }),
                new codegen_csharp_6.Parameter('returnNow', codegen_csharp_14.dotnet.Bool, { modifier: codegen_csharp_7.ParameterModifier.Ref, description: 'Determines if the rest of the serialization should be processed, or if the method should return instantly.' }),
            ],
            description: `<c>BeforeToJson</c> will be called before the json serialization has commenced, allowing complete customization of the object before it is serialized.
      If you wish to disable the default serialization entirely, return <c>true</c> in the <paramref name="returnNow" /> output parameter.
      Implement this method in a partial class to enable this behavior.`
        }));
        this.atj = this.addMethod(new codegen_csharp_5.PartialMethod('AfterToJson', codegen_csharp_14.dotnet.Void, {
            access: codegen_csharp_1.Access.Default,
            parameters: [
                new codegen_csharp_6.Parameter('container', clientruntime_1.ClientRuntime.JsonObject, { modifier: codegen_csharp_7.ParameterModifier.Ref, description: 'The JSON container that the serialization result will be placed in.' }),
            ],
            description: `<c>AfterToJson</c> will be called after the json serialization has finished, allowing customization of the <see cref="${clientruntime_1.ClientRuntime.JsonObject}" /> before it is returned. Implement this method in a partial class to enable this behavior `
        }));
        this.bfj = this.addMethod(new codegen_csharp_5.PartialMethod('BeforeFromJson', codegen_csharp_14.dotnet.Void, {
            access: codegen_csharp_1.Access.Default,
            parameters: [
                new codegen_csharp_6.Parameter('json', clientruntime_1.ClientRuntime.JsonObject, { description: 'The JsonNode that should be deserialized into this object.' }),
                new codegen_csharp_6.Parameter('returnNow', codegen_csharp_14.dotnet.Bool, { modifier: codegen_csharp_7.ParameterModifier.Ref, description: 'Determines if the rest of the deserialization should be processed, or if the method should return instantly.' }),
            ],
            description: `<c>BeforeFromJson</c> will be called before the json deserialization has commenced, allowing complete customization of the object before it is deserialized.
      If you wish to disable the default deserialization entirely, return <c>true</c> in the <paramref name= "returnNow" /> output parameter.
      Implement this method in a partial class to enable this behavior.`
        }));
        this.afj = this.addMethod(new codegen_csharp_5.PartialMethod('AfterFromJson', codegen_csharp_14.dotnet.Void, {
            access: codegen_csharp_1.Access.Default,
            parameters: [
                new codegen_csharp_6.Parameter('json', clientruntime_1.ClientRuntime.JsonObject, { description: 'The JsonNode that should be deserialized into this object.' }),
            ],
            description: '<c>AfterFromJson</c> will be called after the json deserialization has finished, allowing customization of the object before it is returned. Implement this method in a partial class to enable this behavior '
        }));
    }
}
exports.JsonSerializableClass = JsonSerializableClass;
//# sourceMappingURL=model-class-json.js.map