namespace Azure.Iot.Operations.ProtocolCompilerLib
{
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Text.Json;
    using System.Text.RegularExpressions;

    public static class EnvoyTransformFactory
    {
        private static readonly Dictionary<string, SerializerValues> formatSerializers = new()
        {
            { PayloadFormat.Avro, new SerializerValues("AVRO", "AvroSerializer{0}", EmptyTypeName.AvroInstance) },
            { PayloadFormat.Cbor, new SerializerValues("CBOR", "CborSerializer", EmptyTypeName.CborInstance) },
            { PayloadFormat.Json, new SerializerValues("JSON", "Utf8JsonSerializer", EmptyTypeName.JsonInstance) },
            { PayloadFormat.Proto2, new SerializerValues("protobuf", "ProtobufSerializer{0}", EmptyTypeName.ProtoInstance) },
            { PayloadFormat.Proto3, new SerializerValues("protobuf", "ProtobufSerializer{0}", EmptyTypeName.ProtoInstance) },
            { PayloadFormat.Raw, new SerializerValues("raw", "PassthroughSerializer", EmptyTypeName.RawInstance) },
            { PayloadFormat.Custom, new SerializerValues("custom", "ExternalSerializer", EmptyTypeName.CustomInstance) },
        };

        public static IEnumerable<ITemplateTransform> GetTransforms(string language, string projectName, JsonDocument annexDocument, string? workingPath, string? sdkPath, bool generateClient, bool generateServer, bool defaultImpl, string genRoot, CodeName? sharedPrefix, bool generateProject)
        {
            string modelId = annexDocument.RootElement.GetProperty(AnnexFileProperties.ModelId).GetString()!;
            CodeName genNamespace = new CodeName(annexDocument.RootElement.GetProperty(AnnexFileProperties.Namespace).GetString()!);
            CodeName? sharedNamespace = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.Shared, out JsonElement sharedElt) ? new(sharedElt.GetString()!) : null;
            CodeName serviceName = new CodeName(annexDocument.RootElement.GetProperty(AnnexFileProperties.ServiceName).GetString()!);
            string genFormat = annexDocument.RootElement.GetProperty(AnnexFileProperties.PayloadFormat).GetString()!;
            bool separateTelemetries = annexDocument.RootElement.GetProperty(AnnexFileProperties.TelemSeparate).GetBoolean();
            bool separateProperties = annexDocument.RootElement.GetProperty(AnnexFileProperties.PropSeparate).GetBoolean();

            string? telemetryTopic = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.TelemetryTopic, out JsonElement telemTopicElt) ? telemTopicElt.GetString() : null;
            string? propertyTopic = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.PropertyTopic, out JsonElement propTopicElt) ? propTopicElt.GetString() : null;
            string? commandTopic = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.CommandRequestTopic, out JsonElement cmdTopicElt) ? cmdTopicElt.GetString() : null;
            string? telemServiceGroupId = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.TelemServiceGroupId, out JsonElement tGroupIdElt) ? tGroupIdElt.GetString() : null;
            string? cmdServiceGroupId = annexDocument.RootElement.TryGetProperty(AnnexFileProperties.CmdServiceGroupId, out JsonElement cGroupIdElt) ? cGroupIdElt.GetString() : null;

            string? version = modelId.IndexOf(";") > 0 ? modelId.Substring(modelId.IndexOf(";") + 1) : null;
            string? normalizedVersionSuffix = version?.Replace(".", "_");

            List<CommandEnvoyInfo> cmdEnvoyInfos = new();
            List<PropertyEnvoyInfo> propEnvoyInfos = new();
            List<TelemetryEnvoyInfo> telemEnvoyInfos = new();

            if (annexDocument.RootElement.TryGetProperty(AnnexFileProperties.TelemetryList, out JsonElement telemsElt) && telemsElt.GetArrayLength() > 0)
            {
                if (telemetryTopic == null)
                {
                    throw new Exception($"Model {modelId} has at least one Telemetry content but no {DtdlMqttExtensionValues.GetStandardTerm(DtdlMqttExtensionValues.TelemTopicPropertyFormat)} property");
                }

                foreach (JsonElement telemEl in telemsElt.EnumerateArray())
                {
                    foreach (ITemplateTransform templateTransform in GetTelemetryTransforms(language, projectName, genNamespace, modelId, serviceName, genFormat, telemEl, telemEnvoyInfos, workingPath, generateClient, generateServer, useSharedSubscription: telemServiceGroupId != null))
                    {
                        yield return templateTransform;
                    }
                }
            }

            if (annexDocument.RootElement.TryGetProperty(AnnexFileProperties.PropertyList, out JsonElement propsElt) && propsElt.GetArrayLength() > 0)
            {
                if (propertyTopic == null)
                {
                    throw new Exception($"Model {modelId} has at least one Property content but no {DtdlMqttExtensionValues.GetStandardTerm(DtdlMqttExtensionValues.PropTopicPropertyFormat)} property");
                }
                else if (!propertyTopic.Contains(MqttTopicTokens.PropertyAction) && propsElt.EnumerateArray().Any(p => p.TryGetProperty(AnnexFileProperties.PropWriteReqSchema, out _)))
                {
                    throw new Exception($"Model {modelId} has at least one writable Property content but {DtdlMqttExtensionValues.GetStandardTerm(DtdlMqttExtensionValues.PropTopicPropertyFormat)} does not contain {MqttTopicTokens.PropertyAction} token");
                }

                bool doesPropertyTargetMaintainer = DoesTopicReferToMaintainer(propertyTopic);
                foreach (JsonElement propEl in propsElt.EnumerateArray())
                {
                    foreach (ITemplateTransform templateTransform in GetPropertyTransforms(language, projectName, genNamespace, modelId, serviceName, genFormat, propEl, propEnvoyInfos, workingPath, generateClient, generateServer, doesPropertyTargetMaintainer, separateProperties))
                    {
                        yield return templateTransform;
                    }
                }
            }

            bool needJsonSerialization = false;
            if (annexDocument.RootElement.TryGetProperty(AnnexFileProperties.CommandList, out JsonElement cmdsElt) && cmdsElt.GetArrayLength() > 0)
            {
                if (commandTopic == null)
                {
                    throw new Exception($"Model {modelId} has at least one Command content but no {DtdlMqttExtensionValues.GetStandardTerm(DtdlMqttExtensionValues.CmdReqTopicPropertyFormat)} property");
                }

                foreach (JsonElement cmdEl in cmdsElt.EnumerateArray())
                {
                    if (cmdEl.TryGetProperty(AnnexFileProperties.ErrorInfoSchema, out _))
                    {
                        needJsonSerialization = true;
                    }

                    foreach (ITemplateTransform templateTransform in GetCommandTransforms(language, projectName, genNamespace, modelId, serviceName, genFormat, commandTopic, cmdEl, cmdEnvoyInfos, normalizedVersionSuffix, workingPath, generateClient, generateServer, useSharedSubscription: cmdServiceGroupId != null))
                    {
                        yield return templateTransform;
                    }
                }
            }

            if (annexDocument.RootElement.TryGetProperty(AnnexFileProperties.ErrorList, out JsonElement errsElt))
            {
                foreach (JsonElement errEl in errsElt.EnumerateArray())
                {
                    foreach (ITemplateTransform templateTranform in GetErrorTransforms(language, projectName, genNamespace, genFormat, workingPath, errEl))
                    {
                        yield return templateTranform;
                    }
                }
            }

            if (annexDocument.RootElement.TryGetProperty(AnnexFileProperties.AggregateErrorList, out JsonElement aggErrsElt))
            {
                foreach (JsonElement aggErrEl in aggErrsElt.EnumerateArray())
                {
                    foreach (ITemplateTransform templateTranform in GetAggregateErrorTransforms(language, projectName, genNamespace, genFormat, workingPath, aggErrEl))
                    {
                        yield return templateTranform;
                    }
                }
            }

            foreach (ITemplateTransform templateTransform in GetServiceTransforms(language, projectName, genNamespace, sharedNamespace, modelId, serviceName, genFormat, commandTopic, propertyTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, cmdEnvoyInfos, propEnvoyInfos, telemEnvoyInfos, sharedPrefix, genRoot, generateClient, generateServer, defaultImpl, separateTelemetries, separateProperties))
            {
                yield return templateTransform;
            }

            foreach (ITemplateTransform templateTransform in GetResourceTransforms(language, projectName, genFormat))
            {
                yield return templateTransform;
            }

            foreach (ITemplateTransform templateTransform in GetProjectTransforms(language, projectName, genNamespace, genFormat, sdkPath, sharedPrefix, generateProject, needJsonSerialization))
            {
                yield return templateTransform;
            }
        }

        private static IEnumerable<ITemplateTransform> GetTelemetryTransforms(string language, string projectName, CodeName genNamespace, string modelId, CodeName serviceName, string genFormat, JsonElement telemElt, List<TelemetryEnvoyInfo> telemEnvoyInfos, string? workingPath, bool generateClient, bool generateServer, bool useSharedSubscription)
        {
            string serializerSubNamespace = formatSerializers[genFormat].SubNamespace;
            string serializerClassName = formatSerializers[genFormat].ClassName;
            EmptyTypeName serializerEmptyType = formatSerializers[genFormat].EmptyType;

            CodeName telemetryName = new CodeName(telemElt.TryGetProperty(AnnexFileProperties.TelemName, out JsonElement nameElt) ? nameElt.GetString() ?? string.Empty : string.Empty);
            string schemaRep = telemElt.GetProperty(AnnexFileProperties.TelemSchema).GetString()!;
            ITypeName schemaType = schemaRep switch
            {
                RawTypeName.Designator => RawTypeName.Instance,
                CustomTypeName.Designator => CustomTypeName.Instance,
                _ => new CodeName(schemaRep),
            };

            switch (language)
            {
                case "csharp":
                    if (generateServer)
                    {
                        yield return new DotNetTelemetrySender(telemetryName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, schemaType);
                    }

                    if (generateClient)
                    {
                        yield return new DotNetTelemetryReceiver(telemetryName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, schemaType);
                    }

                    if (genFormat == PayloadFormat.Avro && schemaType is CodeName dotnetSchema)
                    {
                        yield return new DotNetSerialization(projectName, genNamespace, dotnetSchema, workingPath);
                    }

                    break;
                case "go":
                    if (generateServer)
                    {
                        yield return new GoTelemetrySender(telemetryName, genNamespace, serializerSubNamespace, schemaType);
                    }

                    if (generateClient)
                    {
                        yield return new GoTelemetryReceiver(telemetryName, genNamespace, serializerSubNamespace, schemaType);
                    }

                    break;
                case "java":
                    if (generateServer)
                    {
                        yield return new JavaTelemetrySender(telemetryName.AsGiven, genNamespace, serializerSubNamespace, serializerClassName, schemaType.GetTypeName(TargetLanguage.Java));
                    }

                    if (generateClient)
                    {
                        yield return new JavaTelemetryReceiver(telemetryName.AsGiven, genNamespace, serializerSubNamespace, serializerClassName, schemaType.GetTypeName(TargetLanguage.Java));
                    }

                    break;
                case "python":
                    if (generateServer)
                    {
                        yield return new PythonTelemetrySender(telemetryName.AsGiven, genNamespace, serializerSubNamespace, serializerClassName, schemaType.GetTypeName(TargetLanguage.Python));
                    }

                    if (generateClient)
                    {
                        yield return new PythonTelemetryReceiver(telemetryName.AsGiven, genNamespace, serializerSubNamespace, serializerClassName, schemaType.GetTypeName(TargetLanguage.Python));
                    }

                    break;
                case "rust":
                    if (generateServer)
                    {
                        yield return new RustTelemetrySender(telemetryName, genNamespace, schemaType);
                    }

                    if (generateClient)
                    {
                        yield return new RustTelemetryReceiver(telemetryName, genNamespace, schemaType, useSharedSubscription);
                    }

                    if (schemaType is CodeName rustSchema)
                    {
                        yield return new RustSerialization(genNamespace, genFormat, rustSchema, workingPath);
                    }

                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }

            telemEnvoyInfos.Add(new TelemetryEnvoyInfo(telemetryName, schemaType));
        }

        private static IEnumerable<ITemplateTransform> GetPropertyTransforms(string language, string projectName, CodeName genNamespace, string modelId, CodeName serviceName, string genFormat, JsonElement propElt, List<PropertyEnvoyInfo> propEnvoyInfos, string? workingPath, bool generateClient, bool generateServer, bool doesPropertyTargetMaintainer, bool separateProperties)
        {
            string serializerSubNamespace = formatSerializers[genFormat].SubNamespace;
            string serializerClassName = formatSerializers[genFormat].ClassName;
            EmptyTypeName serializerEmptyType = formatSerializers[genFormat].EmptyType;

            CodeName? propertyName = propElt.TryGetProperty(AnnexFileProperties.PropName, out JsonElement nameElt) ? new CodeName(nameElt.GetString()!) : null;
            CodeName propSchema = new CodeName(propElt.GetProperty(AnnexFileProperties.PropSchema).GetString()!);
            CodeName readRespSchema = new CodeName(propElt.GetProperty(AnnexFileProperties.PropReadRespSchema).GetString()!);
            CodeName? readRespNamespace = propElt.TryGetProperty(AnnexFileProperties.PropReadRespNamespace, out JsonElement readRespNamespaceElt) ? new(readRespNamespaceElt.GetString()!) : null;
            CodeName? writeReqSchema = propElt.TryGetProperty(AnnexFileProperties.PropWriteReqSchema, out JsonElement writeRequestSchemaElt) ? new(writeRequestSchemaElt.GetString()!) : null;
            CodeName? writeReqNamespace = propElt.TryGetProperty(AnnexFileProperties.PropWriteReqNamespace, out JsonElement writeReqNamespaceElt) ? new(writeReqNamespaceElt.GetString()!) : null;
            CodeName? writeRespSchema = propElt.TryGetProperty(AnnexFileProperties.PropWriteRespSchema, out JsonElement writeResponseSchemaElt) ? new(writeResponseSchemaElt.GetString()!) : null;
            CodeName? writeRespNamespace = propElt.TryGetProperty(AnnexFileProperties.PropWriteRespNamespace, out JsonElement writeRespNamespaceElt) ? new(writeRespNamespaceElt.GetString()!) : null;
            CodeName? propValueName = propElt.TryGetProperty(AnnexFileProperties.PropValueName, out JsonElement propValueNameElt) ? new(propValueNameElt.GetString()!) : null;
            CodeName? readErrorName = propElt.TryGetProperty(AnnexFileProperties.PropReadErrorName, out JsonElement readErrorNameElt) ? new(readErrorNameElt.GetString()!) : null;
            CodeName? readErrorSchema = propElt.TryGetProperty(AnnexFileProperties.PropReadErrorSchema, out JsonElement readErrorSchemaElt) ? new(readErrorSchemaElt.GetString()!) : null;
            CodeName? readErrorNamespace = propElt.TryGetProperty(AnnexFileProperties.PropReadErrorNamespace, out JsonElement readErrorNamespaceElt) ? new(readErrorNamespaceElt.GetString()!) : null;
            CodeName? writeErrorName = propElt.TryGetProperty(AnnexFileProperties.PropWriteErrorName, out JsonElement writeErrorNameElt) ? new(writeErrorNameElt.GetString()!) : null;
            CodeName? writeErrorSchema = propElt.TryGetProperty(AnnexFileProperties.PropWriteErrorSchema, out JsonElement writeErrorSchemaElt) ? new(writeErrorSchemaElt.GetString()!) : null;
            CodeName? writeErrorNamespace = propElt.TryGetProperty(AnnexFileProperties.PropWriteErrorNamespace, out JsonElement writeErrorNamespaceElt) ? new(writeErrorNamespaceElt.GetString()!) : null;

            switch (language)
            {
                case "csharp":
                    if (generateServer)
                    {
                        yield return new DotNetPropertyMaintainer(propertyName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, readRespSchema, readRespNamespace, writeReqSchema, writeReqNamespace, writeRespSchema, writeRespNamespace);
                    }

                    if (generateClient)
                    {
                        yield return new DotNetPropertyConsumer(propertyName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, readRespSchema, readRespNamespace, writeReqSchema, writeReqNamespace, writeRespSchema, writeRespNamespace);
                    }

                    break;
                case "go":
                    break;
                case "java":
                    break;
                case "python":
                    break;
                case "rust":
                    if (generateServer)
                    {
                        yield return new RustPropertyMaintainer(propertyName, propSchema, genNamespace, serializerEmptyType, readRespSchema, readRespNamespace, writeReqSchema, writeReqNamespace, writeRespSchema, writeRespNamespace, propValueName, readErrorName, readErrorSchema, readErrorNamespace, writeErrorName, writeErrorSchema, writeErrorNamespace, doesPropertyTargetMaintainer, separateProperties);
                    }

                    if (generateClient)
                    {
                        yield return new RustPropertyConsumer(propertyName, propSchema, genNamespace, serializerEmptyType, readRespSchema, readRespNamespace, writeReqSchema, writeReqNamespace, writeRespSchema, writeRespNamespace, propValueName, readErrorName, readErrorSchema, readErrorNamespace, writeErrorName, writeErrorSchema, writeErrorNamespace, doesPropertyTargetMaintainer, separateProperties);
                    }

                    yield return new RustSerialization(genNamespace, genFormat, propSchema, workingPath);

                    if (!readRespSchema.Equals(propSchema))
                    {
                        yield return new RustSerialization(readRespNamespace ?? genNamespace, genFormat, readRespSchema, workingPath);
                    }

                    if (writeReqSchema != null && !writeReqSchema.Equals(readRespSchema) && !writeReqSchema.Equals(propSchema))
                    {
                        yield return new RustSerialization(writeReqNamespace ?? genNamespace, genFormat, writeReqSchema, workingPath);
                    }

                    if (writeRespSchema != null)
                    {
                        yield return new RustSerialization(writeRespNamespace ?? genNamespace, genFormat, writeRespSchema, workingPath);
                    }

                    if (readErrorSchema != null)
                    {
                        yield return new RustSerialization(readErrorNamespace ?? genNamespace, genFormat, readErrorSchema, workingPath);
                    }

                    if (writeErrorSchema != null && !writeErrorSchema.Equals(readErrorSchema))
                    {
                        yield return new RustSerialization(writeErrorNamespace ?? genNamespace, genFormat, writeErrorSchema, workingPath);
                    }

                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }

            propEnvoyInfos.Add(new PropertyEnvoyInfo(propertyName, propSchema, readRespSchema, writeReqSchema, writeRespSchema, propValueName, readErrorName, readErrorSchema, writeErrorName, writeErrorSchema));
        }

        private static IEnumerable<ITemplateTransform> GetCommandTransforms(string language, string projectName, CodeName genNamespace, string modelId, CodeName serviceName, string genFormat, string? commandTopic, JsonElement cmdElt, List<CommandEnvoyInfo> cmdEnvoyInfos, string? normalizedVersionSuffix, string? workingPath, bool generateClient, bool generateServer, bool useSharedSubscription)
        {
            bool doesCommandTargetExecutor = DoesTopicReferToExecutor(commandTopic);
            string serializerSubNamespace = formatSerializers[genFormat].SubNamespace;
            string serializerClassName = formatSerializers[genFormat].ClassName;
            EmptyTypeName serializerEmptyType = formatSerializers[genFormat].EmptyType;

            CodeName commandName = new CodeName(cmdElt.GetProperty(AnnexFileProperties.CommandName).GetString()!);

            string? reqSchemaRep = cmdElt.TryGetProperty(AnnexFileProperties.CmdRequestSchema, out JsonElement reqSchemaElt) ? reqSchemaElt.GetString() : null;
            ITypeName? reqSchemaType = reqSchemaRep == null ? null : reqSchemaRep switch
            {
                RawTypeName.Designator => RawTypeName.Instance,
                CustomTypeName.Designator => CustomTypeName.Instance,
                _ => new CodeName(reqSchemaRep),
            };
            CodeName? reqSchemaNamespace = cmdElt.TryGetProperty(AnnexFileProperties.CmdRequestNamespace, out JsonElement reqNamespaceElt) ? new(reqNamespaceElt.GetString()!) : null;

            string? respSchemaRep = cmdElt.TryGetProperty(AnnexFileProperties.CmdResponseSchema, out JsonElement respSchemaElt) ? respSchemaElt.GetString() : null;
            ITypeName? respSchemaType = respSchemaRep == null ? null : reqSchemaRep switch
            {
                RawTypeName.Designator => RawTypeName.Instance,
                CustomTypeName.Designator => CustomTypeName.Instance,
                _ => new CodeName(respSchemaRep),
            };
            CodeName? respSchemaNamespace = cmdElt.TryGetProperty(AnnexFileProperties.CmdResponseNamespace, out JsonElement respNamespaceElt) ? new(respNamespaceElt.GetString()!) : null;

            CodeName? normalResultName = cmdElt.TryGetProperty(AnnexFileProperties.NormalResultName, out JsonElement normalNameElt) ? new(normalNameElt.GetString()!) : null;
            CodeName? normalResultSchema = cmdElt.TryGetProperty(AnnexFileProperties.NormalResultSchema, out JsonElement normalSchemaElt) ? new(normalSchemaElt.GetString()!) : null;
            CodeName? normalResultNamespace = cmdElt.TryGetProperty(AnnexFileProperties.NormalResultNamespace, out JsonElement normalNamespaceElt) ? new(normalNamespaceElt.GetString()!) : null;
            CodeName? errorResultName = cmdElt.TryGetProperty(AnnexFileProperties.ErrorResultName, out JsonElement errorNameElt) ? new(errorNameElt.GetString()!) : null;
            CodeName? errorResultSchema = cmdElt.TryGetProperty(AnnexFileProperties.ErrorResultSchema, out JsonElement errorSchemaElt) ? new(errorSchemaElt.GetString()!) : null;
            CodeName? errorResultNamespace = cmdElt.TryGetProperty(AnnexFileProperties.ErrorResultNamespace, out JsonElement errorNamespaceElt) ? new(errorNamespaceElt.GetString()!) : null;
            CodeName? errorCodeName = cmdElt.TryGetProperty(AnnexFileProperties.ErrorCodeName, out JsonElement codeNameElt) ? new(codeNameElt.GetString()!) : null;
            CodeName? errorCodeSchema = cmdElt.TryGetProperty(AnnexFileProperties.ErrorCodeSchema, out JsonElement codeSchemaElt) ? new(codeSchemaElt.GetString()!) : null;
            CodeName? errorCodeNamespace = cmdElt.TryGetProperty(AnnexFileProperties.ErrorCodeNamespace, out JsonElement codeNamespaceElt) ? new(codeNamespaceElt.GetString()!) : null;
            CodeName? errorInfoName = cmdElt.TryGetProperty(AnnexFileProperties.ErrorInfoName, out JsonElement infoNameElt) ? new(infoNameElt.GetString()!) : null;
            CodeName? errorInfoSchema = cmdElt.TryGetProperty(AnnexFileProperties.ErrorInfoSchema, out JsonElement infoSchemaElt) ? new(infoSchemaElt.GetString()!) : null;
            CodeName? errorInfoNamespace = cmdElt.TryGetProperty(AnnexFileProperties.ErrorInfoNamespace, out JsonElement infoNamespaceElt) ? new(infoNamespaceElt.GetString()!) : null;
            Dictionary<CodeName, string> errorCodeEnumeration = cmdElt.TryGetProperty(AnnexFileProperties.ErrorCodeEnumeration, out JsonElement codeEnumElt) ? codeEnumElt.EnumerateObject().ToDictionary(p => new CodeName(p.Name), p => p.Value.GetString()!) : new Dictionary<CodeName, string>();

            bool isRequestNullable = cmdElt.TryGetProperty(AnnexFileProperties.RequestIsNullable, out JsonElement reqNullableElt) ? reqNullableElt.GetBoolean() : false;
            bool isResponseNullable = cmdElt.TryGetProperty(AnnexFileProperties.ResponseIsNullable, out JsonElement respNullableElt) ? respNullableElt.GetBoolean() : false;

            bool isIdempotent = cmdElt.GetProperty(AnnexFileProperties.CmdIsIdempotent).GetBoolean();
            string? cacheability = cmdElt.GetProperty(AnnexFileProperties.Cacheability).GetString();

            switch (language)
            {
                case "csharp":
                    if (generateClient)
                    {
                        yield return new DotNetCommandInvoker(commandName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, reqSchemaType, respSchemaType, reqSchemaNamespace, respSchemaNamespace);
                    }

                    if (generateServer)
                    {
                        yield return new DotNetCommandExecutor(commandName, projectName, genNamespace, modelId, serviceName, serializerSubNamespace, serializerClassName, serializerEmptyType, reqSchemaType, respSchemaType, reqSchemaNamespace, respSchemaNamespace, isIdempotent, cacheability);
                    }

                    if (respSchemaType != null && errorCodeName != null && errorCodeSchema != null)
                    {
                        yield return new DotNetResponseExtension(projectName, genNamespace, respSchemaType, respSchemaNamespace, errorCodeName, errorCodeSchema, errorCodeNamespace, errorInfoName, errorInfoSchema, errorInfoNamespace, errorCodeEnumeration, generateClient, generateServer);
                    }

                    if (genFormat == PayloadFormat.Avro)
                    {
                        if (reqSchemaType is CodeName dotnetReqSchema)
                        {
                            yield return new DotNetSerialization(projectName, genNamespace, dotnetReqSchema, workingPath);
                        }

                        if (respSchemaType is CodeName dotnetRespSchema)
                        {
                            yield return new DotNetSerialization(projectName, genNamespace, dotnetRespSchema, workingPath);
                        }
                    }

                    break;
                case "go":
                    if (generateClient)
                    {
                        yield return new GoCommandInvoker(commandName, genNamespace, serializerSubNamespace, reqSchemaType, respSchemaType, normalResultName, normalResultSchema, errorResultName, errorResultSchema, isResponseNullable, doesCommandTargetExecutor);
                    }

                    if (generateServer)
                    {
                        yield return new GoCommandExecutor(commandName, genNamespace, serializerSubNamespace, reqSchemaType, respSchemaType, normalResultName, normalResultSchema, errorResultName, errorResultSchema, isResponseNullable, isIdempotent, cacheability);
                    }

                    break;
                case "java":
                    if (generateClient)
                    {
                        yield return new JavaCommandInvoker(commandName, genNamespace, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Java), respSchemaType?.GetTypeName(TargetLanguage.Java));
                    }

                    if (generateServer)
                    {
                        yield return new JavaCommandExecutor(commandName, genNamespace, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Java), respSchemaType?.GetTypeName(TargetLanguage.Java));
                    }

                    break;
                case "python":
                    if (generateClient)
                    {
                        yield return new PythonCommandInvoker(commandName, genNamespace, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Python), respSchemaType?.GetTypeName(TargetLanguage.Python));
                    }

                    if (generateServer)
                    {
                        yield return new PythonCommandExecutor(commandName, genNamespace, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Python), respSchemaType?.GetTypeName(TargetLanguage.Python));
                    }

                    break;
                case "rust":
                    if (generateClient)
                    {
                        yield return new RustCommandInvoker(commandName, genNamespace, serializerEmptyType, reqSchemaType, respSchemaType, reqSchemaNamespace, respSchemaNamespace, normalResultName, normalResultSchema, normalResultNamespace, errorResultName, errorResultSchema, errorResultNamespace, isResponseNullable, doesCommandTargetExecutor);
                        if (errorCodeName != null && errorCodeSchema != null)
                        {
                            yield return new RustCommandInvokerHeaders(commandName, genNamespace, errorCodeName, errorCodeSchema, errorInfoName, errorInfoSchema, errorCodeEnumeration);
                        }
                    }

                    if (generateServer)
                    {
                        yield return new RustCommandExecutor(commandName, genNamespace, serializerEmptyType, reqSchemaType, respSchemaType, reqSchemaNamespace, respSchemaNamespace, normalResultName, normalResultSchema, normalResultNamespace, errorResultName, errorResultSchema, errorResultNamespace, isResponseNullable, isIdempotent, cacheability, useSharedSubscription);
                        if (errorCodeName != null && errorCodeSchema != null)
                        {
                            yield return new RustCommandExecutorHeaders(commandName, genNamespace, errorCodeName, errorCodeSchema, errorInfoName, errorInfoSchema, errorCodeEnumeration);
                        }
                    }

                    if (reqSchemaType is CodeName rustReqSchema)
                    {
                        yield return new RustSerialization(reqSchemaNamespace ?? genNamespace, genFormat, rustReqSchema, workingPath);
                    }

                    if (respSchemaType is CodeName rustRespSchema)
                    {
                        yield return new RustSerialization(respSchemaNamespace ?? genNamespace, genFormat, rustRespSchema, workingPath);
                    }

                    if (normalResultSchema != null)
                    {
                        yield return new RustSerialization(normalResultNamespace ?? genNamespace, genFormat, normalResultSchema, workingPath);
                    }

                    break;
                case "c":
                    if (generateClient)
                    {
                        yield return new CCommandInvoker(modelId, commandName, commandTopic!, genNamespace, serviceName, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Independent), respSchemaType?.GetTypeName(TargetLanguage.Independent), normalizedVersionSuffix);
                    }

                    if (generateServer)
                    {
                        yield return new CCommandExecutor(modelId, commandName, commandTopic!, genNamespace, serviceName, serializerSubNamespace, serializerClassName, reqSchemaType?.GetTypeName(TargetLanguage.Independent), respSchemaType?.GetTypeName(TargetLanguage.Independent), normalizedVersionSuffix);
                    }

                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }

            cmdEnvoyInfos.Add(new CommandEnvoyInfo(commandName, reqSchemaType, respSchemaType, normalResultName, normalResultSchema, errorResultName, errorResultSchema, errorCodeName, errorCodeSchema, errorInfoName, errorInfoSchema, isRequestNullable, isResponseNullable));
        }

        private static IEnumerable<ITemplateTransform> GetErrorTransforms(string language, string projectName, CodeName genNamespace, string genFormat, string? workingPath, JsonElement errElt)
        {
            CodeName schemaName = new CodeName(errElt.GetProperty(AnnexFileProperties.ErrorSchema).GetString()!);
            CodeName schemaNamespace = errElt.TryGetProperty(AnnexFileProperties.ErrorNamespace, out JsonElement namespaceElt) ? new CodeName(namespaceElt.GetString()!) : genNamespace;
            CodeName? errorCodeName = errElt.TryGetProperty(AnnexFileProperties.ErrorCodeName, out JsonElement codeNameElt) ? new(codeNameElt.GetString()!) : null;
            CodeName? errorCodeSchema = errElt.TryGetProperty(AnnexFileProperties.ErrorCodeSchema, out JsonElement codeSchemaElt) ? new(codeSchemaElt.GetString()!) : null;
            CodeName? errorInfoName = errElt.TryGetProperty(AnnexFileProperties.ErrorInfoName, out JsonElement infoNameElt) ? new(infoNameElt.GetString()!) : null;
            CodeName? errorInfoSchema = errElt.TryGetProperty(AnnexFileProperties.ErrorInfoSchema, out JsonElement infoSchemaElt) ? new(infoSchemaElt.GetString()!) : null;
            string description = errElt.TryGetProperty(AnnexFileProperties.ErrorDescription, out JsonElement descriptionElt) ? descriptionElt.GetString() ?? schemaName.AsGiven : schemaName.AsGiven;
            CodeName? messageField = errElt.TryGetProperty(AnnexFileProperties.ErrorMessageField, out JsonElement msgFieldElt) ? new CodeName(msgFieldElt.GetString()!) : null;
            bool isNullable = errElt.TryGetProperty(AnnexFileProperties.ErrorMessageIsNullable, out JsonElement nullableElt) ? nullableElt.GetBoolean() : false;

            switch (language)
            {
                case "csharp":
                    yield return new DotNetError(projectName, schemaName, schemaNamespace, errorCodeName, errorCodeSchema, errorInfoName, errorInfoSchema, description, messageField, isNullable);
                    break;
                case "go":
                    yield return new GoError(schemaName, schemaNamespace, description, messageField, isNullable);
                    break;
                case "java":
                    break;
                case "python":
                    break;
                case "rust":
                    yield return new RustError(schemaName, schemaNamespace, description, messageField, isNullable);
                    yield return new RustSerialization(schemaNamespace, genFormat, schemaName, workingPath);
                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }
        }

        private static IEnumerable<ITemplateTransform> GetAggregateErrorTransforms(string language, string projectName, CodeName genNamespace, string genFormat, string? workingPath, JsonElement aggErrElt)
        {
            CodeName schemaName = new CodeName(aggErrElt.GetProperty(AnnexFileProperties.ErrorSchema).GetString()!);
            CodeName schemaNamespace = aggErrElt.TryGetProperty(AnnexFileProperties.ErrorNamespace, out JsonElement namespaceElt) ? new CodeName(namespaceElt.GetString()!) : genNamespace;
            List<(CodeName, CodeName)> innerNameSchemas = aggErrElt.GetProperty(AnnexFileProperties.InnerErrorList).EnumerateArray().Select(ie => (new CodeName(ie.GetProperty(AnnexFileProperties.InnerErrorName).GetString()!), new CodeName(ie.GetProperty(AnnexFileProperties.InnerErrorSchema).GetString()!))).ToList();

            switch (language)
            {
                case "csharp":
                    yield return new DotNetAggregateError(projectName, schemaName, schemaNamespace, innerNameSchemas);
                    break;
                case "go":
                    break;
                case "java":
                    break;
                case "python":
                    break;
                case "rust":
                    yield return new RustAggregateError(schemaName, schemaNamespace, innerNameSchemas);
                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }
        }

        private static IEnumerable<ITemplateTransform> GetServiceTransforms(string language, string projectName, CodeName genNamespace, CodeName? sharedNamespace, string modelId, CodeName serviceName, string genFormat, string? commandTopic, string? propertyTopic, string? telemetryTopic, string? cmdServiceGroupId, string? telemServiceGroupId, List<CommandEnvoyInfo> cmdEnvoyInfos, List<PropertyEnvoyInfo> propEnvoyInfos, List<TelemetryEnvoyInfo> telemEnvoyInfos, CodeName? sharedPrefix, string genRoot, bool generateClient, bool generateServer, bool defaultImpl, bool separateTelemetries, bool separateProperties)
        {
            bool doesCommandTargetExecutor = DoesTopicReferToExecutor(commandTopic);
            bool doesCommandTargetService = DoesTopicReferToService(commandTopic);
            bool doesPropertyTargetMaintainer = DoesTopicReferToMaintainer(propertyTopic);
            bool doesTelemetryTargetService = DoesTopicReferToService(telemetryTopic);
            string serializerSubNamespace = formatSerializers[genFormat].SubNamespace;
            EmptyTypeName serializerEmptyType = formatSerializers[genFormat].EmptyType;

            switch (language)
            {
                case "csharp":
                    yield return new DotNetService(projectName, genNamespace, sharedNamespace, serviceName, serializerSubNamespace, serializerEmptyType, commandTopic, propertyTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, cmdEnvoyInfos, propEnvoyInfos, telemEnvoyInfos, doesCommandTargetExecutor, doesCommandTargetService, doesPropertyTargetMaintainer, doesTelemetryTargetService, generateClient, generateServer, defaultImpl, separateProperties);
                    break;
                case "go":
                    yield return new GoService(genNamespace, modelId, serviceName, commandTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, cmdEnvoyInfos, telemEnvoyInfos, doesCommandTargetService, doesTelemetryTargetService, generateClient, generateServer, separateTelemetries);
                    break;
                case "java":
                    yield return new JavaService(genNamespace, modelId, serviceName, commandTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, cmdEnvoyInfos, telemEnvoyInfos, doesCommandTargetExecutor, doesCommandTargetService, doesTelemetryTargetService);
                    break;
                case "python":
                    yield return new PythonService(genNamespace, modelId, serviceName, commandTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, cmdEnvoyInfos, telemEnvoyInfos, doesCommandTargetExecutor, doesCommandTargetService, doesTelemetryTargetService);
                    break;
                case "rust":
                    yield return new RustService(genNamespace, modelId, commandTopic, propertyTopic, telemetryTopic, cmdServiceGroupId, telemServiceGroupId, generateClient, generateServer, genRoot);
                    if (sharedPrefix != null)
                    {
                        yield return new RustShared(sharedPrefix, genRoot);
                    }
                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }
        }

        private static IEnumerable<ITemplateTransform> GetProjectTransforms(string language, string projectName, CodeName genNamespace, string genFormat, string? sdkPath, CodeName? sharedPrefix, bool generateProject, bool needJsonSerialization)
        {
            switch (language)
            {
                case "csharp":
                    if (generateProject)
                    {
                        yield return new DotNetProject(projectName, genFormat, sdkPath);
                    }
                    break;
                case "go":
                    break;
                case "java":
                    break;
                case "python":
                    break;
                case "rust":
                    yield return new RustLib(genNamespace, sharedPrefix, generateProject);
                    yield return new RustCargoToml(projectName, genFormat, sdkPath, generateProject, needJsonSerialization);
                    break;
                case "c":
                    break;
                default:
                    throw GetLanguageNotRecognizedException(language);
            }
        }

        private static IEnumerable<ITemplateTransform> GetResourceTransforms(string language, string projectName, string genFormat)
        {
            string serializerSubNamespace = formatSerializers[genFormat].SubNamespace;

            string ext = language switch
            {
                "csharp" => "cs",
                "go" => "go",
                "java" => "java",
                "python" => "py",
                "rust" => "rs",
                "c" => "c",
                _ => throw GetLanguageNotRecognizedException(language)
            };

            foreach (string subNamespace in new List<string> { "common", serializerSubNamespace })
            {
                foreach (string resourceName in Assembly.GetExecutingAssembly().GetManifestResourceNames())
                {
                    Regex rx = new($"^{Assembly.GetExecutingAssembly().GetName().Name}\\.{ResourceNames.LanguageResourcesFolder}\\.{language}\\.({subNamespace})(?:\\.(\\w+(?:\\.\\w+)*))?\\.(\\w+)\\.{ext}$", RegexOptions.IgnoreCase);
                    Match? match = rx.Match(resourceName);
                    if (match.Success)
                    {
                        string subFolder = match.Groups[1].Captures[0].Value;
                        string resourcePath = match.Groups[2].Captures.Count > 0 ? match.Groups[2].Captures[0].Value : string.Empty;
                        string resourceFile = match.Groups[3].Captures[0].Value;

                        StreamReader resourceReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)!);

                        yield return new ResourceTransform(language, projectName, subFolder, resourcePath, resourceFile, ext, resourceReader.ReadToEnd());
                    }
                }
            }
        }

        private static bool DoesTopicReferToExecutor(string? topic)
        {
            return topic != null && topic.Contains(MqttTopicTokens.CommandExecutorId);
        }

        private static bool DoesTopicReferToMaintainer(string? topic)
        {
            return topic != null && topic.Contains(MqttTopicTokens.PropertyMaintainerId);
        }

        private static bool DoesTopicReferToService(string? topic)
        {
            return topic != null && topic.Contains(MqttTopicTokens.ModelId);
        }

        private static Exception GetLanguageNotRecognizedException(string language)
        {
            return new Exception($"language '{language}' not recognized");
        }

        private readonly struct SerializerValues
        {
            public SerializerValues(string subNamespace, string className, EmptyTypeName emptyType)
            {
                SubNamespace = subNamespace;
                ClassName = className;
                EmptyType = emptyType;
            }

            public readonly string SubNamespace;
            public readonly string ClassName;
            public readonly EmptyTypeName EmptyType;
        }
    }
}
