// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Apache.Fory.Generator;

[Generator(LanguageNames.CSharp)]
public sealed class ForyObjectGenerator : IIncrementalGenerator
{
    private static readonly SymbolDisplayFormat FullNameFormat =
        SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
            SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);

    private static readonly DiagnosticDescriptor GenericTypeNotSupported = new(
        id: "FORY001",
        title: "Generic types are not supported by ForyObject generator",
        messageFormat: "Type '{0}' is generic and is not supported by [ForyObject]",
        category: "Fory",
        defaultSeverity: DiagnosticSeverity.Error,
        isEnabledByDefault: true);

    private static readonly DiagnosticDescriptor MissingCtor = new(
        id: "FORY002",
        title: "Missing parameterless constructor",
        messageFormat: "Class '{0}' must declare an accessible parameterless constructor for [ForyObject]",
        category: "Fory",
        defaultSeverity: DiagnosticSeverity.Error,
        isEnabledByDefault: true);

    private static readonly DiagnosticDescriptor UnsupportedEncoding = new(
        id: "FORY003",
        title: "Unsupported Field encoding",
        messageFormat: "Member '{0}' uses unsupported [Field] encoding for type '{1}'",
        category: "Fory",
        defaultSeverity: DiagnosticSeverity.Error,
        isEnabledByDefault: true);

    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        IncrementalValuesProvider<TypeModel?> typeModels = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "Apache.Fory.ForyObjectAttribute",
                static (node, _) => node is TypeDeclarationSyntax || node is EnumDeclarationSyntax,
                static (syntaxContext, ct) => BuildTypeModel(syntaxContext, ct))
            .Where(static m => m is not null);

        context.RegisterSourceOutput(
            typeModels.Collect(),
            static (spc, models) => Emit(spc, models));
    }

    private static void Emit(SourceProductionContext context, ImmutableArray<TypeModel?> maybeModels)
    {
        if (maybeModels.IsDefaultOrEmpty)
        {
            return;
        }

        Dictionary<string, TypeModel> models = new(StringComparer.Ordinal);
        foreach (TypeModel? maybeModel in maybeModels)
        {
            if (maybeModel is null)
            {
                continue;
            }

            models[maybeModel.TypeName] = maybeModel;
        }

        if (models.Count == 0)
        {
            return;
        }

        StringBuilder sb = new();
        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("#nullable enable");
        sb.AppendLine("namespace Apache.Fory.Generated;");
        sb.AppendLine();

        foreach (KeyValuePair<string, TypeModel> entry in models.OrderBy(kv => kv.Key, StringComparer.Ordinal))
        {
            TypeModel model = entry.Value;
            if (model.Kind == DeclKind.Struct || model.Kind == DeclKind.Class)
            {
                EmitObjectSerializer(sb, model);
                sb.AppendLine();
            }
        }

        sb.AppendLine("internal static class __ForyGeneratedModuleInitializer");
        sb.AppendLine("{");
        sb.AppendLine("    [global::System.Runtime.CompilerServices.ModuleInitializer]");
        sb.AppendLine("    internal static void Register()");
        sb.AppendLine("    {");
        foreach (KeyValuePair<string, TypeModel> entry in models.OrderBy(kv => kv.Key, StringComparer.Ordinal))
        {
            TypeModel model = entry.Value;
            if (model.Kind == DeclKind.Enum)
            {
                sb.AppendLine(
                    $"        global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, global::Apache.Fory.EnumSerializer<{model.TypeName}>>();");
            }
            else
            {
                sb.AppendLine(
                    $"        global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, {model.SerializerName}>();");
            }
        }

        sb.AppendLine("    }");
        sb.AppendLine("}");

        context.AddSource("Fory.GeneratedSerializers.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
    }

    private static void EmitObjectSerializer(StringBuilder sb, TypeModel model)
    {
        sb.AppendLine(
            $"file sealed class {model.SerializerName} : global::Apache.Fory.Serializer<{model.TypeName}>");
        sb.AppendLine("{");
        sb.AppendLine("    private static readonly object __ForyTypeMetaCacheLock = new();");
        sb.AppendLine("    private static ulong __ForyTypeMetaResolverVersion;");
        sb.AppendLine("    private static ulong __ForyTypeMetaHeaderHashNoTrackRef;");
        sb.AppendLine("    private static ulong __ForyTypeMetaHeaderHashTrackRef;");
        sb.AppendLine("    private static global::Apache.Fory.TypeMeta? __ForyLastTypeMetaNoTrackRef;");
        sb.AppendLine("    private static bool __ForyLastTypeMetaMatchedNoTrackRef;");
        sb.AppendLine("    private static global::Apache.Fory.TypeMeta? __ForyLastTypeMetaTrackRef;");
        sb.AppendLine("    private static bool __ForyLastTypeMetaMatchedTrackRef;");
        sb.AppendLine(
            $"    private const bool __ForyAllFieldsBuiltIn = {BoolLiteral(model.SortedMembers.All(m => m.DynamicAnyKind == DynamicAnyKind.None && m.Classification.IsBuiltIn))};");
        sb.AppendLine(
            "    private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>? __ForyTypeMetaFieldsNoTrackRef;");
        sb.AppendLine(
            "    private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>? __ForyTypeMetaFieldsTrackRef;");

        if (model.SortedMembers.Length > 0)
        {
            sb.AppendLine();
        }

        sb.AppendLine("    private static global::Apache.Fory.RefMode __ForyRefMode(bool nullable, bool trackRef)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (trackRef)");
        sb.AppendLine("        {");
        sb.AppendLine("            return global::Apache.Fory.RefMode.Tracking;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return nullable ? global::Apache.Fory.RefMode.NullOnly : global::Apache.Fory.RefMode.None;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine("    private static bool __ForyCanReadCompatiblePrimitive(global::Apache.Fory.TypeId typeId)");
        sb.AppendLine("    {");
        sb.AppendLine("        return typeId switch");
        sb.AppendLine("        {");
        sb.AppendLine("            global::Apache.Fory.TypeId.Bool or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int8 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int16 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int32 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarInt32 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarInt64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.TaggedInt64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt8 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt16 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt32 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarUInt32 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarUInt64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.TaggedUInt64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Float32 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.Float64 or");
        sb.AppendLine("            global::Apache.Fory.TypeId.String => true,");
        sb.AppendLine("            _ => false,");
        sb.AppendLine("        };");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine("    private static object __ForyReadCompatiblePrimitivePayload(global::Apache.Fory.TypeId typeId, global::Apache.Fory.ReadContext context)");
        sb.AppendLine("    {");
        sb.AppendLine("        return typeId switch");
        sb.AppendLine("        {");
        sb.AppendLine("            global::Apache.Fory.TypeId.Bool => context.Reader.ReadUInt8() != 0,");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int8 => context.Reader.ReadInt8(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int16 => context.Reader.ReadInt16(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int32 => context.Reader.ReadInt32(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarInt32 => context.Reader.ReadVarInt32(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.Int64 => context.Reader.ReadInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarInt64 => context.Reader.ReadVarInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.TaggedInt64 => context.Reader.ReadTaggedInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt8 => context.Reader.ReadUInt8(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt16 => context.Reader.ReadUInt16(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt32 => context.Reader.ReadUInt32(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarUInt32 => context.Reader.ReadVarUInt32(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.UInt64 => context.Reader.ReadUInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.VarUInt64 => context.Reader.ReadVarUInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.TaggedUInt64 => context.Reader.ReadTaggedUInt64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.Float32 => context.Reader.ReadFloat32(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.Float64 => context.Reader.ReadFloat64(),");
        sb.AppendLine("            global::Apache.Fory.TypeId.String => global::Apache.Fory.StringSerializer.ReadString(context),");
        sb.AppendLine("            _ => throw new global::Apache.Fory.InvalidDataException($\"unsupported compatible primitive type id {typeId}\"),");
        sb.AppendLine("        };");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine("    private static T __ForyReadCompatibleField<T>(");
        sb.AppendLine("        global::Apache.Fory.ReadContext context,");
        sb.AppendLine("        global::Apache.Fory.TypeMetaFieldType fieldType,");
        sb.AppendLine("        global::Apache.Fory.RefMode refMode,");
        sb.AppendLine("        bool readTypeInfo)");
        sb.AppendLine("    {");
        sb.AppendLine("        global::Apache.Fory.TypeId typeId = (global::Apache.Fory.TypeId)fieldType.TypeId;");
        sb.AppendLine("        if (!readTypeInfo && __ForyCanReadCompatiblePrimitive(typeId))");
        sb.AppendLine("        {");
        sb.AppendLine("            object? value;");
        sb.AppendLine("            switch (refMode)");
        sb.AppendLine("            {");
        sb.AppendLine("                case global::Apache.Fory.RefMode.None:");
        sb.AppendLine("                    value = __ForyReadCompatiblePrimitivePayload(typeId, context);");
        sb.AppendLine("                    break;");
        sb.AppendLine("                case global::Apache.Fory.RefMode.NullOnly:");
        sb.AppendLine("                {");
        sb.AppendLine("                    sbyte refFlag = context.Reader.ReadInt8();");
        sb.AppendLine("                    if (refFlag == (sbyte)global::Apache.Fory.RefFlag.Null)");
        sb.AppendLine("                    {");
        sb.AppendLine("                        value = null;");
        sb.AppendLine("                    }");
        sb.AppendLine("                    else if (refFlag == (sbyte)global::Apache.Fory.RefFlag.NotNullValue)");
        sb.AppendLine("                    {");
        sb.AppendLine("                        value = __ForyReadCompatiblePrimitivePayload(typeId, context);");
        sb.AppendLine("                    }");
        sb.AppendLine("                    else");
        sb.AppendLine("                    {");
        sb.AppendLine("                        throw new global::Apache.Fory.InvalidDataException($\"invalid compatible nullOnly ref flag {refFlag}\");");
        sb.AppendLine("                    }");
        sb.AppendLine();
        sb.AppendLine("                    break;");
        sb.AppendLine("                }");
        sb.AppendLine("                default:");
        sb.AppendLine("                    return context.TypeResolver.GetSerializer<T>().Read(context, refMode, readTypeInfo);");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            if (value is null)");
        sb.AppendLine("            {");
        sb.AppendLine("                return default!;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            if (value is T typedValue)");
        sb.AppendLine("            {");
        sb.AppendLine("                return typedValue;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            try");
        sb.AppendLine("            {");
        sb.AppendLine("                return (T)value;");
        sb.AppendLine("            }");
        sb.AppendLine("            catch (global::System.InvalidCastException ex)");
        sb.AppendLine("            {");
        sb.AppendLine("                throw new global::Apache.Fory.InvalidDataException($\"cannot cast compatible field value of type {value.GetType()} to {typeof(T)}: {ex.Message}\");");
        sb.AppendLine("            }");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return context.TypeResolver.GetSerializer<T>().Read(context, refMode, readTypeInfo);");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    private static global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> __ForyBuildTypeMetaFields(bool trackRef)");
        sb.AppendLine("    {");
        if (model.SortedMembers.Length == 0)
        {
            sb.AppendLine("        return global::System.Array.Empty<global::Apache.Fory.TypeMetaFieldInfo>();");
        }
        else
        {
            sb.AppendLine("        return new global::Apache.Fory.TypeMetaFieldInfo[]");
            sb.AppendLine("        {");
            foreach (MemberModel member in model.SortedMembers)
            {
                sb.AppendLine(
                    $"            new global::Apache.Fory.TypeMetaFieldInfo({BuildTypeMetaFieldIdExpression(member.FieldId)}, \"{EscapeString(member.FieldIdentifier)}\", {BuildTypeMetaExpression(member.TypeMeta, "trackRef")}),");
            }

            sb.AppendLine("        };");
        }

        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    private bool __ForyMatchesTypeMeta(global::Apache.Fory.TypeMeta typeMeta, bool trackRef)");
        sb.AppendLine("    {");
        sb.AppendLine(
            "        global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> expectedFields = TypeMetaFields(trackRef);");
        sb.AppendLine("        if (typeMeta.Fields.Count != expectedFields.Count)");
        sb.AppendLine("        {");
        sb.AppendLine("            return false;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        for (int i = 0; i < expectedFields.Count; i++)");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            global::Apache.Fory.TypeMetaFieldInfo remoteField = typeMeta.Fields[i];");
        sb.AppendLine(
            "            global::Apache.Fory.TypeMetaFieldInfo localField = expectedFields[i];");
        sb.AppendLine("            if (remoteField.FieldId.HasValue && localField.FieldId.HasValue)");
        sb.AppendLine("            {");
        sb.AppendLine(
            "                if (remoteField.FieldId.Value != localField.FieldId.Value || !remoteField.FieldType.Equals(localField.FieldType))");
        sb.AppendLine("                {");
        sb.AppendLine("                    return false;");
        sb.AppendLine("                }");
        sb.AppendLine();
        sb.AppendLine("                continue;");
        sb.AppendLine("            }");
        sb.AppendLine(
            "            if (remoteField.FieldName != localField.FieldName || !remoteField.FieldType.Equals(localField.FieldType))");
        sb.AppendLine("            {");
        sb.AppendLine("                return false;");
        sb.AppendLine("            }");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return true;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    private static void __ForyEnsureTypeMetaCache(global::Apache.Fory.TypeResolver typeResolver)");
        sb.AppendLine("    {");
        sb.AppendLine("        ulong resolverVersion = typeResolver.VersionHash();");
        sb.AppendLine("        if (__ForyTypeMetaResolverVersion == resolverVersion)");
        sb.AppendLine("        {");
        sb.AppendLine("            return;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        lock (__ForyTypeMetaCacheLock)");
        sb.AppendLine("        {");
        sb.AppendLine("            if (__ForyTypeMetaResolverVersion == resolverVersion)");
        sb.AppendLine("            {");
        sb.AppendLine("                return;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine(
            $"            global::Apache.Fory.TypeInfo typeInfo = typeResolver.GetTypeInfo<{model.TypeName}>();");
        sb.AppendLine(
            "            __ForyTypeMetaHeaderHashNoTrackRef = typeInfo.GetTypeMetaHeaderHash(false);");
        sb.AppendLine(
            "            __ForyTypeMetaHeaderHashTrackRef = typeInfo.GetTypeMetaHeaderHash(true);");
        sb.AppendLine("            __ForyLastTypeMetaNoTrackRef = null;");
        sb.AppendLine("            __ForyLastTypeMetaMatchedNoTrackRef = false;");
        sb.AppendLine("            __ForyLastTypeMetaTrackRef = null;");
        sb.AppendLine("            __ForyLastTypeMetaMatchedTrackRef = false;");
        sb.AppendLine("            __ForyTypeMetaResolverVersion = resolverVersion;");
        sb.AppendLine("        }");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            "    private bool __ForyMatchesCachedTypeMeta(global::Apache.Fory.TypeMeta typeMeta, bool trackRef, global::Apache.Fory.TypeResolver typeResolver)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (trackRef)");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            if (global::System.Object.ReferenceEquals(__ForyLastTypeMetaTrackRef, typeMeta))");
        sb.AppendLine("            {");
        sb.AppendLine("                return __ForyLastTypeMetaMatchedTrackRef;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            __ForyEnsureTypeMetaCache(typeResolver);");
        sb.AppendLine();
        sb.AppendLine("            bool matched = false;");
        sb.AppendLine("            if (typeMeta.HeaderHash == __ForyTypeMetaHeaderHashTrackRef)");
        sb.AppendLine("            {");
        sb.AppendLine("                matched = __ForyMatchesTypeMeta(typeMeta, true);");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            __ForyLastTypeMetaTrackRef = typeMeta;");
        sb.AppendLine("            __ForyLastTypeMetaMatchedTrackRef = matched;");
        sb.AppendLine("            return matched;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine(
            "        if (global::System.Object.ReferenceEquals(__ForyLastTypeMetaNoTrackRef, typeMeta))");
        sb.AppendLine("        {");
        sb.AppendLine("            return __ForyLastTypeMetaMatchedNoTrackRef;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        __ForyEnsureTypeMetaCache(typeResolver);");
        sb.AppendLine();
        sb.AppendLine("        bool noTrackMatched = false;");
        sb.AppendLine("        if (typeMeta.HeaderHash == __ForyTypeMetaHeaderHashNoTrackRef)");
        sb.AppendLine("        {");
        sb.AppendLine("            noTrackMatched = __ForyMatchesTypeMeta(typeMeta, false);");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        __ForyLastTypeMetaNoTrackRef = typeMeta;");
        sb.AppendLine("        __ForyLastTypeMetaMatchedNoTrackRef = noTrackMatched;");
        sb.AppendLine("        return noTrackMatched;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine("    private static uint? __ForySchemaHashNoTrackRef;");
        sb.AppendLine();
        sb.AppendLine("    private static uint __ForySchemaHash(bool trackRef, global::Apache.Fory.TypeResolver typeResolver)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (!trackRef && __ForySchemaHashNoTrackRef.HasValue)");
        sb.AppendLine("        {");
        sb.AppendLine("            return __ForySchemaHashNoTrackRef.Value;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.Append("        uint value = global::Apache.Fory.SchemaHash.StructHash32(");
        sb.Append(BuildSchemaFingerprintExpression(model.Members));
        sb.AppendLine(");");
        sb.AppendLine("        if (!trackRef)");
        sb.AppendLine("        {");
        sb.AppendLine("            __ForySchemaHashNoTrackRef = value;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        return value;");
        sb.AppendLine("    }");
        sb.AppendLine();
        if (model.Kind == DeclKind.Class)
        {
            sb.AppendLine($"    public override {model.TypeName} DefaultValue => null!;");
        }
        else
        {
            sb.AppendLine($"    public override {model.TypeName} DefaultValue => new {model.TypeName}();");
        }

        sb.AppendLine();
        sb.AppendLine("    private global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo> TypeMetaFields(bool trackRef)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (trackRef)");
        sb.AppendLine("        {");
        sb.AppendLine(
            "            return __ForyTypeMetaFieldsTrackRef ??= __ForyBuildTypeMetaFields(true);");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine(
            "        return __ForyTypeMetaFieldsNoTrackRef ??= __ForyBuildTypeMetaFields(false);");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine(
            $"    public override void WriteData(global::Apache.Fory.WriteContext context, in {model.TypeName} value, bool hasGenerics)");
        sb.AppendLine("    {");
        sb.AppendLine("        _ = hasGenerics;");
        sb.AppendLine("        if (context.Compatible)");
        sb.AppendLine("        {");
        if (model.SortedMembers.Length == 0)
        {
            sb.AppendLine("            return;");
        }
        else
        {
            foreach (MemberModel member in model.SortedMembers)
            {
                EmitWriteMember(sb, member, true);
            }

            sb.AppendLine("            return;");
        }

        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        uint schemaHash = __ForySchemaHash(context.TrackRef, context.TypeResolver);");
        sb.AppendLine("        context.Writer.WriteInt32(unchecked((int)schemaHash));");
        foreach (MemberModel member in model.SortedMembers)
        {
            EmitWriteMember(sb, member, false);
        }

        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine($"    private {model.TypeName} ReadDataWithoutTypeMeta(global::Apache.Fory.ReadContext context)");
        sb.AppendLine("    {");
        sb.AppendLine($"        {model.TypeName} valueNoTypeMeta = new {model.TypeName}();");
        if (model.Kind == DeclKind.Class)
        {
            sb.AppendLine("        context.StoreRef(valueNoTypeMeta);");
        }

        foreach (MemberModel member in model.SortedMembers)
        {
            EmitReadMemberAssignment(
                sb,
                member,
                BuildWriteRefModeExpression(member),
                BuildFieldTypeInfoLiteral(member),
                "valueNoTypeMeta",
                "CompatNoTypeMeta",
                4,
                true);
        }

        sb.AppendLine("        return valueNoTypeMeta;");
        sb.AppendLine("    }");
        sb.AppendLine();
        sb.AppendLine($"    public override {model.TypeName} ReadData(global::Apache.Fory.ReadContext context)");
        sb.AppendLine("    {");
        sb.AppendLine("        if (context.Compatible)");
        sb.AppendLine("        {");
        sb.AppendLine(
            $"            global::Apache.Fory.TypeMeta? maybeTypeMeta = context.GetTypeMeta<{model.TypeName}>();");
        sb.AppendLine("            if (maybeTypeMeta is null)");
        sb.AppendLine("            {");
        sb.AppendLine("                return ReadDataWithoutTypeMeta(context);");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            global::Apache.Fory.TypeMeta typeMeta = maybeTypeMeta;");
        sb.AppendLine($"            {model.TypeName} value = new {model.TypeName}();");
        if (model.Kind == DeclKind.Class)
        {
            sb.AppendLine("            context.StoreRef(value);");
        }

        sb.AppendLine("            bool __ForyExactTypeMeta = __ForyMatchesCachedTypeMeta(typeMeta, context.TrackRef, context.TypeResolver);");
        sb.AppendLine("            if (__ForyAllFieldsBuiltIn && __ForyExactTypeMeta)");
        sb.AppendLine("            {");
        foreach (MemberModel member in model.SortedMembers)
        {
            EmitReadMemberAssignment(
                sb,
                member,
                BuildWriteRefModeExpression(member),
                "false",
                "value",
                "CompatExact",
                6,
                true);
        }

        sb.AppendLine("                return value;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            if (__ForyExactTypeMeta)");
        sb.AppendLine("            {");
        foreach (MemberModel member in model.SortedMembers)
        {
            EmitReadMemberAssignment(
                sb,
                member,
                BuildWriteRefModeExpression(member),
                BuildFieldTypeInfoLiteral(member),
                "value",
                "CompatExactTyped",
                6,
                true);
        }

        sb.AppendLine("                return value;");
        sb.AppendLine("            }");
        sb.AppendLine();
        sb.AppendLine("            for (int i = 0; i < typeMeta.Fields.Count; i++)");
        sb.AppendLine("            {");
        sb.AppendLine("                global::Apache.Fory.TypeMetaFieldInfo remoteField = typeMeta.Fields[i];");
        sb.AppendLine("                global::Apache.Fory.RefMode remoteRefMode = __ForyRefMode(remoteField.FieldType.Nullable, remoteField.FieldType.TrackRef);");
        sb.AppendLine("                switch (remoteField.AssignedFieldId)");
        sb.AppendLine("                {");
        for (int idx = 0; idx < model.SortedMembers.Length; idx++)
        {
            MemberModel member = model.SortedMembers[idx];
            sb.AppendLine($"                    case {idx}:");
            sb.AppendLine("                        {");
            EmitReadMemberAssignment(
                sb,
                member,
                "remoteRefMode",
                BuildFieldTypeInfoLiteral(member),
                "value",
                "Compat",
                7,
                false);
            sb.AppendLine("                            break;");
            sb.AppendLine("                        }");
        }

        sb.AppendLine("                    default:");
        sb.AppendLine("                        global::Apache.Fory.FieldSkipper.SkipFieldValue(context, remoteField.FieldType);");
        sb.AppendLine("                        break;");
        sb.AppendLine("                }");
        sb.AppendLine("            }");
        sb.AppendLine("            return value;");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine("        uint schemaHash = unchecked((uint)context.Reader.ReadInt32());");
        sb.AppendLine("        if (context.CheckStructVersion)");
        sb.AppendLine("        {");
        sb.AppendLine("            uint expectedHash = __ForySchemaHash(context.TrackRef, context.TypeResolver);");
        sb.AppendLine("            if (schemaHash != expectedHash)");
        sb.AppendLine("            {");
        sb.AppendLine("                throw new global::Apache.Fory.InvalidDataException($\"class version hash mismatch: expected {expectedHash}, got {schemaHash}\");");
        sb.AppendLine("            }");
        sb.AppendLine("        }");
        sb.AppendLine();
        sb.AppendLine($"        {model.TypeName} valueSchema = new {model.TypeName}();");
        if (model.Kind == DeclKind.Class)
        {
            sb.AppendLine("        context.StoreRef(valueSchema);");
        }

        foreach (MemberModel member in model.SortedMembers)
        {
            EmitReadMemberAssignment(sb, member, BuildWriteRefModeExpression(member), "false", "valueSchema", "Schema", 2, true);
        }

        sb.AppendLine("        return valueSchema;");
        sb.AppendLine("    }");
        sb.AppendLine("}");
    }

    private static void EmitWriteMember(StringBuilder sb, MemberModel member, bool compatibleMode)
    {
        string refModeExpr = BuildWriteRefModeExpression(member);
        string memberAccess = $"value.{member.Name}";
        string hasGenerics = member.IsCollection ? "true" : "false";
        string writeTypeInfo = compatibleMode
            ? BuildFieldTypeInfoLiteral(member)
            : "false";

        switch (member.DynamicAnyKind)
        {
            case DynamicAnyKind.AnyValue:
                sb.AppendLine(
                    $"            global::Apache.Fory.DynamicAnyCodec.WriteAny(context, {memberAccess}, {refModeExpr}, true, false);");
                return;
            case DynamicAnyKind.None:
                break;
            default:
                throw new InvalidOperationException($"unsupported dynamic any kind {member.DynamicAnyKind}");
        }

        if (member.UseDictionaryTypeInfoCache)
        {
            EmitWriteDictionaryWithTypeInfoCache(
                sb,
                member,
                memberAccess,
                refModeExpr,
                writeTypeInfo,
                hasGenerics,
                compatibleMode);
            return;
        }

        if (!member.IsNullable && TryBuildDirectFieldWrite(member, memberAccess, out string? directWriteCode))
        {
            sb.AppendLine($"            {directWriteCode}");
            return;
        }

        if (TryBuildNullableFixedTaggedFieldWrite(member, memberAccess, out string? nullableWriteCode))
        {
            sb.AppendLine($"            {nullableWriteCode}");
            return;
        }

        if (writeTypeInfo == "false")
        {
            if (CanUseDirectWriteDataInvocation(member))
            {
                sb.AppendLine(
                    $"            context.TypeResolver.GetSerializer<{member.TypeName}>().WriteData(context, {memberAccess}, {hasGenerics});");
                return;
            }

            if (CanUseTrackRefBranchWriteDataInvocation(member))
            {
                sb.AppendLine("            if (context.TrackRef)");
                sb.AppendLine("            {");
                sb.AppendLine(
                    $"                context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, {memberAccess}, global::Apache.Fory.RefMode.Tracking, false, {hasGenerics});");
                sb.AppendLine("            }");
                sb.AppendLine("            else");
                sb.AppendLine("            {");
                sb.AppendLine(
                    $"                context.TypeResolver.GetSerializer<{member.TypeName}>().WriteData(context, {memberAccess}, {hasGenerics});");
                sb.AppendLine("            }");
                return;
            }
        }

        sb.AppendLine(
            $"            context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, {memberAccess}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
    }

    private static void EmitWriteDictionaryWithTypeInfoCache(
        StringBuilder sb,
        MemberModel member,
        string memberAccess,
        string refModeExpr,
        string writeTypeInfo,
        string hasGenerics,
        bool compatibleMode)
    {
        string memberId = Sanitize(member.Name);
        string modeSuffix = compatibleMode ? "Compat" : "Schema";
        string fieldValueVar = $"__{memberId}DictValue{modeSuffix}";
        string runtimeTypeVar = $"__{memberId}DictRuntimeType{modeSuffix}";
        string typeInfoVar = $"__{memberId}DictTypeInfo{modeSuffix}";
        sb.AppendLine($"            {member.TypeName} {fieldValueVar} = {memberAccess};");
        sb.AppendLine($"            if ({fieldValueVar} is null)");
        sb.AppendLine("            {");
        sb.AppendLine(
            $"                context.TypeResolver.GetSerializer<{member.TypeName}>().Write(context, ({member.TypeName})null!, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
        sb.AppendLine("            }");
        sb.AppendLine("            else");
        sb.AppendLine("            {");
        sb.AppendLine($"                global::System.Type {runtimeTypeVar} = {fieldValueVar}.GetType();");
        sb.AppendLine($"                global::Apache.Fory.TypeInfo {typeInfoVar} = context.TypeResolver.GetTypeInfo({runtimeTypeVar});");
        sb.AppendLine(
            $"                context.TypeResolver.WriteObject({typeInfoVar}, context, {fieldValueVar}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
        sb.AppendLine("            }");
    }

    private static void EmitReadMemberAssignment(
        StringBuilder sb,
        MemberModel member,
        string refModeExpr,
        string readTypeInfoExpr,
        string valueVar,
        string variableSuffix,
        int indentLevel,
        bool allowDirectRead)
    {
        string indent = new(' ', indentLevel * 2);
        string assignmentTarget = $"{valueVar}.{member.Name}";
        string typeOfTypeName = StripNullableForTypeOf(member.TypeName);
        switch (member.DynamicAnyKind)
        {
            case DynamicAnyKind.AnyValue:
                sb.AppendLine(
                    $"{indent}{assignmentTarget} = ({member.TypeName})global::Apache.Fory.DynamicAnyCodec.CastAnyDynamicValue(global::Apache.Fory.DynamicAnyCodec.ReadAny(context, {refModeExpr}, true), typeof({typeOfTypeName}))!;");
                return;
            case DynamicAnyKind.None:
                break;
            default:
                throw new InvalidOperationException($"unsupported dynamic any kind {member.DynamicAnyKind}");
        }

        if (allowDirectRead && !member.IsNullable && TryBuildDirectFieldRead(member, out string? directReadExpr))
        {
            sb.AppendLine($"{indent}{assignmentTarget} = {directReadExpr};");
            return;
        }

        if (allowDirectRead && TryBuildNullableFixedTaggedFieldRead(member, assignmentTarget, variableSuffix, indent, out string? nullableReadCode))
        {
            sb.AppendLine(nullableReadCode);
            return;
        }

        if (variableSuffix == "Compat")
        {
            sb.AppendLine(
                $"{indent}{assignmentTarget} = __ForyReadCompatibleField<{member.TypeName}>(context, remoteField.FieldType, {refModeExpr}, {readTypeInfoExpr});");
            return;
        }

        sb.AppendLine(
            $"{indent}{assignmentTarget} = context.TypeResolver.GetSerializer<{member.TypeName}>().Read(context, {refModeExpr}, {readTypeInfoExpr});");
    }

    private static string StripNullableForTypeOf(string typeName)
    {
        return typeName.Replace("?", string.Empty);
    }

    private static bool TryBuildDirectFieldWrite(MemberModel member, string memberAccess, out string? writeCode)
    {
        writeCode = null;
        if (!CanUseDirectBuiltInFieldAccess(member))
        {
            return false;
        }

        return TryBuildDirectPayloadWrite(member.Classification.TypeId, memberAccess, out writeCode);
    }

    private static bool TryBuildDirectFieldRead(MemberModel member, out string? readExpr)
    {
        readExpr = null;
        if (!CanUseDirectBuiltInFieldAccess(member))
        {
            return false;
        }

        return TryBuildDirectPayloadRead(member.Classification.TypeId, out readExpr);
    }

    private static bool TryBuildNullableFixedTaggedFieldWrite(MemberModel member, string memberAccess, out string? writeCode)
    {
        writeCode = null;
        if (!member.IsNullableValueType || !IsFixedTaggedTypeId(member.Classification.TypeId))
        {
            return false;
        }

        if (!TryBuildDirectPayloadWrite(member.Classification.TypeId, $"{memberAccess}.Value", out string? payloadWriteCode))
        {
            return false;
        }

        writeCode = $"if (!{memberAccess}.HasValue) {{ context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.Null); }} else {{ context.Writer.WriteInt8((sbyte)global::Apache.Fory.RefFlag.NotNullValue); {payloadWriteCode} }}";
        return true;
    }

    private static bool TryBuildNullableFixedTaggedFieldRead(
        MemberModel member,
        string assignmentTarget,
        string variableSuffix,
        string indent,
        out string code)
    {
        code = string.Empty;
        if (!member.IsNullableValueType || !IsFixedTaggedTypeId(member.Classification.TypeId))
        {
            return false;
        }

        if (!TryBuildDirectPayloadRead(member.Classification.TypeId, out string? payloadReadExpr))
        {
            return false;
        }

        string refFlagVar = $"__{member.Name}RefFlag{variableSuffix}";
        string nestedIndent = indent + "  ";
        StringBuilder sb = new();
        sb.AppendLine($"{indent}sbyte {refFlagVar} = context.Reader.ReadInt8();");
        sb.AppendLine($"{indent}if ({refFlagVar} == (sbyte)global::Apache.Fory.RefFlag.Null)");
        sb.AppendLine($"{indent}{{");
        sb.AppendLine($"{nestedIndent}{assignmentTarget} = ({member.TypeName})null!;");
        sb.AppendLine($"{indent}}}");
        sb.AppendLine($"{indent}else");
        sb.AppendLine($"{indent}{{");
        sb.AppendLine($"{nestedIndent}{assignmentTarget} = {payloadReadExpr};");
        sb.Append($"{indent}}}");
        code = sb.ToString();
        return true;
    }

    private static bool IsFixedTaggedTypeId(uint typeId)
    {
        return typeId is 4 or 6 or 8 or 11 or 13 or 15;
    }

    private static bool TryBuildDirectPayloadWrite(uint typeId, string valueExpr, out string? writeCode)
    {
        writeCode = null;
        switch (typeId)
        {
            case 1:
                writeCode = $"context.Writer.WriteUInt8({valueExpr} ? (byte)1 : (byte)0);";
                return true;
            case 2:
                writeCode = $"context.Writer.WriteInt8({valueExpr});";
                return true;
            case 3:
                writeCode = $"context.Writer.WriteInt16({valueExpr});";
                return true;
            case 4:
                writeCode = $"context.Writer.WriteInt32({valueExpr});";
                return true;
            case 5:
                writeCode = $"context.Writer.WriteVarInt32({valueExpr});";
                return true;
            case 6:
                writeCode = $"context.Writer.WriteInt64({valueExpr});";
                return true;
            case 7:
                writeCode = $"context.Writer.WriteVarInt64({valueExpr});";
                return true;
            case 8:
                writeCode = $"context.Writer.WriteTaggedInt64({valueExpr});";
                return true;
            case 9:
                writeCode = $"context.Writer.WriteUInt8({valueExpr});";
                return true;
            case 10:
                writeCode = $"context.Writer.WriteUInt16({valueExpr});";
                return true;
            case 11:
                writeCode = $"context.Writer.WriteUInt32({valueExpr});";
                return true;
            case 12:
                writeCode = $"context.Writer.WriteVarUInt32({valueExpr});";
                return true;
            case 13:
                writeCode = $"context.Writer.WriteUInt64({valueExpr});";
                return true;
            case 14:
                writeCode = $"context.Writer.WriteVarUInt64({valueExpr});";
                return true;
            case 15:
                writeCode = $"context.Writer.WriteTaggedUInt64({valueExpr});";
                return true;
            case 19:
                writeCode = $"context.Writer.WriteFloat32({valueExpr});";
                return true;
            case 20:
                writeCode = $"context.Writer.WriteFloat64({valueExpr});";
                return true;
            case 21:
                writeCode = $"global::Apache.Fory.StringSerializer.WriteString(context, {valueExpr});";
                return true;
            default:
                return false;
        }
    }

    private static bool TryBuildDirectPayloadRead(uint typeId, out string? readExpr)
    {
        readExpr = null;
        switch (typeId)
        {
            case 1:
                readExpr = "context.Reader.ReadUInt8() != 0";
                return true;
            case 2:
                readExpr = "context.Reader.ReadInt8()";
                return true;
            case 3:
                readExpr = "context.Reader.ReadInt16()";
                return true;
            case 4:
                readExpr = "context.Reader.ReadInt32()";
                return true;
            case 5:
                readExpr = "context.Reader.ReadVarInt32()";
                return true;
            case 6:
                readExpr = "context.Reader.ReadInt64()";
                return true;
            case 7:
                readExpr = "context.Reader.ReadVarInt64()";
                return true;
            case 8:
                readExpr = "context.Reader.ReadTaggedInt64()";
                return true;
            case 9:
                readExpr = "context.Reader.ReadUInt8()";
                return true;
            case 10:
                readExpr = "context.Reader.ReadUInt16()";
                return true;
            case 11:
                readExpr = "context.Reader.ReadUInt32()";
                return true;
            case 12:
                readExpr = "context.Reader.ReadVarUInt32()";
                return true;
            case 13:
                readExpr = "context.Reader.ReadUInt64()";
                return true;
            case 14:
                readExpr = "context.Reader.ReadVarUInt64()";
                return true;
            case 15:
                readExpr = "context.Reader.ReadTaggedUInt64()";
                return true;
            case 19:
                readExpr = "context.Reader.ReadFloat32()";
                return true;
            case 20:
                readExpr = "context.Reader.ReadFloat64()";
                return true;
            case 21:
                readExpr = "global::Apache.Fory.StringSerializer.ReadString(context)";
                return true;
            default:
                return false;
        }
    }

    private static bool CanUseDirectBuiltInFieldAccess(MemberModel member)
    {
        if (member.IsNullable ||
            member.DynamicAnyKind != DynamicAnyKind.None ||
            member.IsCollection ||
            member.Classification.IsMap)
        {
            return false;
        }

        return member.Classification.IsPrimitive || member.Classification.TypeId == 21;
    }

    private static bool CanUseDirectWriteDataInvocation(MemberModel member)
    {
        if (member.IsNullable || member.DynamicAnyKind != DynamicAnyKind.None)
        {
            return false;
        }

        return member.Classification.IsBuiltIn || !member.IsRefType;
    }

    private static bool CanUseTrackRefBranchWriteDataInvocation(MemberModel member)
    {
        if (member.IsNullable || member.DynamicAnyKind != DynamicAnyKind.None)
        {
            return false;
        }

        return !member.Classification.IsBuiltIn && member.IsRefType;
    }

    private static string BuildSchemaFingerprintExpression(ImmutableArray<MemberModel> members)
    {
        if (members.IsDefaultOrEmpty)
        {
            return "\"\"";
        }

        IEnumerable<MemberModel> ordered = members
            .OrderBy(m => m.FieldIdentifier, StringComparer.Ordinal)
            .ThenBy(m => m.OriginalIndex);

        StringBuilder sb = new();
        bool first = true;
        foreach (MemberModel member in ordered)
        {
            uint fingerprintTypeId = (member.Classification.IsPrimitive || member.Classification.IsBuiltIn)
                ? member.Classification.TypeId
                : 0;
            string trackRefExpr = member.DynamicAnyKind switch
            {
                DynamicAnyKind.AnyValue => "(trackRef ? 1 : 0)",
                _ => member.Classification.IsBuiltIn || !member.IsRefType
                    ? "0"
                    : "(trackRef ? 1 : 0)",
            };
            string nullable = member.IsNullable ? "1" : "0";
            string piece = $"\"{EscapeString(member.FieldIdentifier)},{fingerprintTypeId},\" + {trackRefExpr} + \",{nullable};\"";
            if (!first)
            {
                sb.Append(" + ");
            }

            first = false;
            sb.Append(piece);
        }

        return sb.ToString();
    }

    private static string BuildTypeMetaExpression(TypeMetaFieldTypeModel model, string trackRefExpr)
    {
        string localTrackRefExpr = model.TrackRefByContext ? trackRefExpr : "false";
        if (model.Generics.Length > 0)
        {
            string generics = string.Join(
                ", ",
                model.Generics.Select(g => BuildTypeMetaExpression(g, "false")));
            return
                $"new global::Apache.Fory.TypeMetaFieldType({model.TypeIdExpr}, {BoolLiteral(model.Nullable)}, {localTrackRefExpr}, new global::Apache.Fory.TypeMetaFieldType[] {{ {generics} }})";
        }

        return $"new global::Apache.Fory.TypeMetaFieldType({model.TypeIdExpr}, {BoolLiteral(model.Nullable)}, {localTrackRefExpr})";
    }

    private static string BuildTypeMetaFieldIdExpression(short? fieldId)
    {
        return fieldId.HasValue ? $"(short){fieldId.Value}" : "null";
    }

    private static string BuildWriteRefModeExpression(MemberModel member)
    {
        return member.DynamicAnyKind switch
        {
            DynamicAnyKind.AnyValue => $"__ForyRefMode({BoolLiteral(member.IsNullable)}, context.TrackRef)",
            _ => member.Classification.IsBuiltIn || !member.IsRefType
                ? $"__ForyRefMode({BoolLiteral(member.IsNullable)}, false)"
                : $"__ForyRefMode({BoolLiteral(member.IsNullable)}, context.TrackRef)",
        };
    }

    private static string BuildFieldTypeInfoLiteral(MemberModel member)
    {
        return BoolLiteral(member.NeedsFieldTypeInfo);
    }

    private static TypeModel? BuildTypeModel(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken)
    {
        _ = cancellationToken;
        if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
        {
            return null;
        }

        if (typeSymbol.TypeParameters.Length > 0)
        {
            return null;
        }

        string typeName = typeSymbol.ToDisplayString(FullNameFormat);
        string serializerName = "__ForySerializer_" + Sanitize(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
        if (typeSymbol.TypeKind == TypeKind.Enum)
        {
            return new TypeModel(
                typeName,
                serializerName,
                DeclKind.Enum,
                ImmutableArray<MemberModel>.Empty,
                ImmutableArray<MemberModel>.Empty);
        }

        DeclKind kind = typeSymbol.TypeKind switch
        {
            TypeKind.Struct => DeclKind.Struct,
            TypeKind.Class => DeclKind.Class,
            _ => DeclKind.Unknown,
        };

        if (kind == DeclKind.Unknown)
        {
            return null;
        }

        if (kind == DeclKind.Class && !HasAccessibleParameterlessCtor(typeSymbol))
        {
            return null;
        }

        List<MemberModel> members = [];
        foreach (ISymbol member in typeSymbol.GetMembers())
        {
            if (member.IsStatic)
            {
                continue;
            }

            if (member is IFieldSymbol field)
            {
                if (field.IsConst || field.IsReadOnly || !IsReadableWritableAccessibility(field.DeclaredAccessibility))
                {
                    continue;
                }

                MemberModel? parsedField = BuildMemberModel(field.Name, field.Type, field, MemberDeclKind.Field);
                if (parsedField is not null)
                {
                    members.Add(parsedField);
                }

                continue;
            }

            if (member is IPropertySymbol property)
            {
                if (property.IsIndexer || property.GetMethod is null || property.SetMethod is null)
                {
                    continue;
                }

                if (property.SetMethod.IsInitOnly)
                {
                    continue;
                }

                if (!IsReadableWritableAccessibility(property.GetMethod.DeclaredAccessibility) ||
                    !IsReadableWritableAccessibility(property.SetMethod.DeclaredAccessibility))
                {
                    continue;
                }

                MemberModel? parsedProperty = BuildMemberModel(
                    property.Name,
                    property.Type,
                    property,
                    MemberDeclKind.Property);
                if (parsedProperty is not null)
                {
                    members.Add(parsedProperty);
                }
            }
        }

        ImmutableArray<MemberModel> ordered = members
            .OrderBy(m => m.OriginalIndex)
            .ToImmutableArray();
        ImmutableArray<MemberModel> sorted = SortMembers(ordered);

        return new TypeModel(typeName, serializerName, kind, ordered, sorted);
    }

    private static MemberModel? BuildMemberModel(
        string name,
        ITypeSymbol memberType,
        ISymbol memberSymbol,
        MemberDeclKind memberDeclKind)
    {
        (bool isOptional, ITypeSymbol unwrappedType) = UnwrapNullable(memberType);
        FieldEncoding fieldEncoding = FieldEncoding.None;
        short? fieldId = null;
        foreach (AttributeData attribute in memberSymbol.GetAttributes())
        {
            string? attrName = attribute.AttributeClass?.ToDisplayString();
            if (!string.Equals(attrName, "Apache.Fory.FieldAttribute", StringComparison.Ordinal))
            {
                continue;
            }

            foreach (KeyValuePair<string, TypedConstant> namedArg in attribute.NamedArguments)
            {
                if (string.Equals(namedArg.Key, "Id", StringComparison.Ordinal))
                {
                    if (TryGetNonNegativeShort(namedArg.Value, out short parsedFieldId))
                    {
                        fieldId = parsedFieldId;
                    }

                    continue;
                }

                if (!string.Equals(namedArg.Key, "Encoding", StringComparison.Ordinal))
                {
                    continue;
                }

                if (namedArg.Value.Value is int encoding)
                {
                    fieldEncoding = (FieldEncoding)encoding;
                }
            }
        }

        DynamicAnyKind dynamicAnyKind = ResolveDynamicAnyKind(unwrappedType);
        TypeResolution resolution = ResolveTypeResolution(unwrappedType, fieldEncoding);
        if (!resolution.Supported)
        {
            return null;
        }

        TypeClassification classification = resolution.Classification;
        int group = classification.IsPrimitive
            ? (isOptional ? 2 : 1)
            : classification.IsMap
                ? 5
                : classification.IsCollection
                    ? 4
                    : classification.IsBuiltIn
                        ? 3
                        : 6;

        int index = int.MaxValue;
        Location? sourceLocation = memberSymbol.Locations.FirstOrDefault(loc => loc.IsInSource);
        if (sourceLocation is not null)
        {
            index = sourceLocation.SourceSpan.Start;
        }

        string typeName = memberType.ToDisplayString(FullNameFormat);
        TypeMetaFieldTypeModel typeMeta = BuildTypeMetaFieldTypeModel(
            memberType,
            isOptional,
            dynamicAnyKind,
            resolution.Classification.TypeId);

        return new MemberModel(
            name,
            ToSnakeCase(name),
            index,
            memberDeclKind,
            typeName,
            isOptional,
            memberType is INamedTypeSymbol nts &&
            nts.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T,
            fieldId,
            classification,
            group,
            classification.IsCollection || classification.IsMap,
            classification.IsMap && !IsTypeSealed(unwrappedType),
            !unwrappedType.IsValueType && classification.TypeId != 21,
            FieldNeedsTypeInfo(classification, dynamicAnyKind, unwrappedType),
            dynamicAnyKind == DynamicAnyKind.None ? DynamicAnyKind.None : dynamicAnyKind,
            typeMeta);
    }

    private static TypeMetaFieldTypeModel BuildTypeMetaFieldTypeModel(
        ITypeSymbol memberType,
        bool nullable,
        DynamicAnyKind dynamicAnyKind,
        uint explicitTypeId)
    {
        (bool _, ITypeSymbol unwrapped) = UnwrapNullable(memberType);

        if (TryGetListElementType(unwrapped, out ITypeSymbol? listElementType))
        {
            bool elementNullable = GenericNullable(listElementType!);
            if (!elementNullable &&
                TryResolvePackedArrayTypeIdForElement(listElementType!) is uint packedArrayTypeId &&
                explicitTypeId == packedArrayTypeId)
            {
                // Align compatible TypeMeta with C++ vector arithmetic handling:
                // when the wire type is already classified as a packed array (e.g. int[]),
                // use the specialized array TypeId directly instead of LIST<elem>.
                // This keeps schema/type-meta bytes consistent across languages.
                return new TypeMetaFieldTypeModel(
                    packedArrayTypeId.ToString(),
                    nullable,
                    false,
                    ImmutableArray<TypeMetaFieldTypeModel>.Empty);
            }

            TypeMetaFieldTypeModel element = BuildTypeMetaFieldTypeModel(
                listElementType!,
                elementNullable,
                ResolveDynamicAnyKind(UnwrapNullable(listElementType!).Item2),
                0);
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.List",
                nullable,
                false,
                ImmutableArray.Create(element));
        }

        if (TryGetSetElementType(unwrapped, out ITypeSymbol? setElementType))
        {
            bool elementNullable = GenericNullable(setElementType!);
            TypeMetaFieldTypeModel element = BuildTypeMetaFieldTypeModel(
                setElementType!,
                elementNullable,
                ResolveDynamicAnyKind(UnwrapNullable(setElementType!).Item2),
                0);
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.Set",
                nullable,
                false,
                ImmutableArray.Create(element));
        }

        if (TryGetMapTypeArguments(unwrapped, out ITypeSymbol? keyType, out ITypeSymbol? valueType))
        {
            bool keyNullable = GenericNullable(keyType!);
            bool valueNullable = GenericNullable(valueType!);
            TypeMetaFieldTypeModel key = BuildTypeMetaFieldTypeModel(
                keyType!,
                keyNullable,
                ResolveDynamicAnyKind(UnwrapNullable(keyType!).Item2),
                0);
            TypeMetaFieldTypeModel value = BuildTypeMetaFieldTypeModel(
                valueType!,
                valueNullable,
                ResolveDynamicAnyKind(UnwrapNullable(valueType!).Item2),
                0);
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.Map",
                nullable,
                false,
                ImmutableArray.Create(key, value));
        }

        TypeClassification classification = ClassifyType(unwrapped);
        if (explicitTypeId != 0 && classification.IsPrimitive && classification.TypeId != explicitTypeId)
        {
            return new TypeMetaFieldTypeModel(
                explicitTypeId.ToString(),
                nullable,
                false,
                ImmutableArray<TypeMetaFieldTypeModel>.Empty);
        }

        if (IsUnionType(unwrapped))
        {
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.Union",
                nullable,
                true,
                ImmutableArray<TypeMetaFieldTypeModel>.Empty);
        }

        if (dynamicAnyKind == DynamicAnyKind.AnyValue)
        {
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.Unknown",
                nullable,
                true,
                ImmutableArray<TypeMetaFieldTypeModel>.Empty);
        }

        if (unwrapped.TypeKind == TypeKind.Enum)
        {
            return new TypeMetaFieldTypeModel(
                "(uint)global::Apache.Fory.TypeId.Enum",
                nullable,
                false,
                ImmutableArray<TypeMetaFieldTypeModel>.Empty);
        }

        return new TypeMetaFieldTypeModel(
            $"(uint){classification.TypeId}",
            nullable,
            !classification.IsBuiltIn && unwrapped.TypeKind != TypeKind.Enum,
            ImmutableArray<TypeMetaFieldTypeModel>.Empty);
    }

    private static bool TryGetNonNegativeShort(TypedConstant value, out short fieldId)
    {
        fieldId = default;
        object? raw = value.Value;
        if (raw is null)
        {
            return false;
        }

        long numeric;
        switch (raw)
        {
            case byte v:
                numeric = v;
                break;
            case sbyte v:
                numeric = v;
                break;
            case short v:
                numeric = v;
                break;
            case ushort v:
                numeric = v;
                break;
            case int v:
                numeric = v;
                break;
            case uint v:
                numeric = v;
                break;
            case long v:
                numeric = v;
                break;
            case ulong v:
                if (v > (ulong)short.MaxValue)
                {
                    return false;
                }

                numeric = (long)v;
                break;
            default:
                return false;
        }

        if (numeric < 0 || numeric > short.MaxValue)
        {
            return false;
        }

        fieldId = (short)numeric;
        return true;
    }

    private static ImmutableArray<MemberModel> SortMembers(ImmutableArray<MemberModel> members)
    {
        return members
            .OrderBy(m => m.Group)
            .ThenBy(m =>
            {
                if (m.Group is 1 or 2)
                {
                    return m.Classification.IsCompressedNumeric ? 1 : 0;
                }

                return 0;
            })
            .ThenByDescending(m => m.Group is 1 or 2 ? m.Classification.PrimitiveSize : 0)
            .ThenBy(m =>
            {
                if (m.Group is 1 or 2)
                {
                    return (int)(uint.MaxValue - m.Classification.TypeId);
                }

                if (m.Group is 3 or 4 or 5)
                {
                    return (int)m.Classification.TypeId;
                }

                return 0;
            })
            .ThenBy(m => m.FieldIdentifier, StringComparer.Ordinal)
            .ThenBy(m => m.Name, StringComparer.Ordinal)
            .ThenBy(m => m.OriginalIndex)
            .ToImmutableArray();
    }

    private static bool GenericNullable(ITypeSymbol type)
    {
        (bool optional, ITypeSymbol unwrapped) = UnwrapNullable(type);
        if (optional)
        {
            return true;
        }

        TypeClassification c = ClassifyType(unwrapped);
        return !c.IsPrimitive;
    }

    private static bool FieldNeedsTypeInfo(
        TypeClassification classification,
        DynamicAnyKind dynamicAnyKind,
        ITypeSymbol unwrappedType)
    {
        if (dynamicAnyKind == DynamicAnyKind.AnyValue)
        {
            return true;
        }

        if (classification.IsBuiltIn || IsUnionType(unwrappedType) || unwrappedType.TypeKind == TypeKind.Enum)
        {
            return false;
        }

        return true;
    }

    private static bool IsReadableWritableAccessibility(Accessibility accessibility)
    {
        return accessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal;
    }

    private static bool HasAccessibleParameterlessCtor(INamedTypeSymbol type)
    {
        foreach (IMethodSymbol ctor in type.InstanceConstructors)
        {
            if (ctor.Parameters.Length != 0)
            {
                continue;
            }

            if (ctor.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal)
            {
                return true;
            }
        }

        return false;
    }

    private static TypeResolution ResolveTypeResolution(ITypeSymbol type, FieldEncoding encoding)
    {
        TypeClassification baseType = ClassifyType(type);
        if (encoding == FieldEncoding.None)
        {
            return new TypeResolution(true, baseType);
        }

        bool isInt32 = type.SpecialType == SpecialType.System_Int32;
        bool isUInt32 = type.SpecialType == SpecialType.System_UInt32;
        bool isInt64 = type.SpecialType == SpecialType.System_Int64;
        bool isUInt64 = type.SpecialType == SpecialType.System_UInt64;

        if (isInt32)
        {
            return encoding switch
            {
                FieldEncoding.Varint => new TypeResolution(true, baseType),
                FieldEncoding.Fixed => new TypeResolution(
                    true,
                    new TypeClassification(4, true, true, false, false, false, 4)),
                _ => new TypeResolution(false, baseType),
            };
        }

        if (isUInt32)
        {
            return encoding switch
            {
                FieldEncoding.Varint => new TypeResolution(true, baseType),
                FieldEncoding.Fixed => new TypeResolution(
                    true,
                    new TypeClassification(11, true, true, false, false, false, 4)),
                _ => new TypeResolution(false, baseType),
            };
        }

        if (isInt64)
        {
            return encoding switch
            {
                FieldEncoding.Varint => new TypeResolution(true, baseType),
                FieldEncoding.Fixed => new TypeResolution(
                    true,
                    new TypeClassification(6, true, true, false, false, false, 8)),
                FieldEncoding.Tagged => new TypeResolution(
                    true,
                    new TypeClassification(8, true, true, false, false, true, 8)),
                _ => new TypeResolution(false, baseType),
            };
        }

        if (isUInt64)
        {
            return encoding switch
            {
                FieldEncoding.Varint => new TypeResolution(true, baseType),
                FieldEncoding.Fixed => new TypeResolution(
                    true,
                    new TypeClassification(13, true, true, false, false, false, 8)),
                FieldEncoding.Tagged => new TypeResolution(
                    true,
                    new TypeClassification(15, true, true, false, false, true, 8)),
                _ => new TypeResolution(false, baseType),
            };
        }

        return new TypeResolution(false, baseType);
    }

    private static TypeClassification ClassifyType(ITypeSymbol type)
    {
        if (ResolveDynamicAnyKind(type) == DynamicAnyKind.AnyValue)
        {
            return new TypeClassification(0, false, true, false, false, false, 0);
        }

        if (type.SpecialType == SpecialType.System_Boolean)
        {
            return new TypeClassification(1, true, true, false, false, false, 1);
        }

        if (type.SpecialType == SpecialType.System_SByte)
        {
            return new TypeClassification(2, true, true, false, false, false, 1);
        }

        if (type.SpecialType == SpecialType.System_Int16)
        {
            return new TypeClassification(3, true, true, false, false, false, 2);
        }

        if (type.SpecialType == SpecialType.System_Int32)
        {
            return new TypeClassification(5, true, true, false, false, true, 4);
        }

        if (type.SpecialType == SpecialType.System_Int64)
        {
            return new TypeClassification(7, true, true, false, false, true, 8);
        }

        if (type.SpecialType == SpecialType.System_Byte)
        {
            return new TypeClassification(9, true, true, false, false, false, 1);
        }

        if (type.SpecialType == SpecialType.System_UInt16)
        {
            return new TypeClassification(10, true, true, false, false, false, 2);
        }

        if (type.SpecialType == SpecialType.System_UInt32)
        {
            return new TypeClassification(12, true, true, false, false, true, 4);
        }

        if (type.SpecialType == SpecialType.System_UInt64)
        {
            return new TypeClassification(14, true, true, false, false, true, 8);
        }

        if (type.SpecialType == SpecialType.System_Single)
        {
            return new TypeClassification(19, true, true, false, false, false, 4);
        }

        if (type.SpecialType == SpecialType.System_Double)
        {
            return new TypeClassification(20, true, true, false, false, false, 8);
        }

        if (type.SpecialType == SpecialType.System_String)
        {
            return new TypeClassification(21, false, true, false, false, false, 0);
        }

        if (IsDateType(type))
        {
            return new TypeClassification(39, false, true, false, false, false, 0);
        }

        if (IsTimestampType(type))
        {
            return new TypeClassification(38, false, true, false, false, false, 0);
        }

        if (IsDurationType(type))
        {
            return new TypeClassification(37, false, true, false, false, false, 0);
        }

        if (type is IArrayTypeSymbol arrayType)
        {
            uint typeId = TryResolvePackedArrayTypeIdForElement(arrayType.ElementType) ?? 22;
            return new TypeClassification(typeId, false, true, true, false, false, 0);
        }

        if (TryGetListElementType(type, out _))
        {
            return new TypeClassification(22, false, true, true, false, false, 0);
        }

        if (TryGetSetElementType(type, out _))
        {
            return new TypeClassification(23, false, true, true, false, false, 0);
        }

        if (TryGetMapTypeArguments(type, out _, out _))
        {
            return new TypeClassification(24, false, true, false, true, false, 0);
        }

        if (IsUnionType(type))
        {
            return new TypeClassification(33, false, false, false, false, false, 0);
        }

        return new TypeClassification(27, false, false, false, false, false, 0);
    }

    private static DynamicAnyKind ResolveDynamicAnyKind(ITypeSymbol type)
    {
        if (type.SpecialType == SpecialType.System_Object)
        {
            return DynamicAnyKind.AnyValue;
        }

        return DynamicAnyKind.None;
    }

    private static bool IsDateType(ITypeSymbol symbol)
    {
        return string.Equals(symbol.ToDisplayString(), "System.DateOnly", StringComparison.Ordinal);
    }

    private static bool IsTimestampType(ITypeSymbol symbol)
    {
        string name = symbol.ToDisplayString();
        return string.Equals(name, "System.DateTime", StringComparison.Ordinal) ||
               string.Equals(name, "System.DateTimeOffset", StringComparison.Ordinal);
    }

    private static bool IsDurationType(ITypeSymbol symbol)
    {
        return string.Equals(symbol.ToDisplayString(), "System.TimeSpan", StringComparison.Ordinal);
    }

    private static bool IsUnionType(ITypeSymbol symbol)
    {
        INamedTypeSymbol? current = symbol as INamedTypeSymbol;
        while (current is not null)
        {
            if (string.Equals(current.ToDisplayString(), "Apache.Fory.Union", StringComparison.Ordinal))
            {
                return true;
            }

            current = current.BaseType;
        }

        return false;
    }

    private static bool IsTypeSealed(ITypeSymbol symbol)
    {
        if (symbol.TypeKind == TypeKind.TypeParameter)
        {
            return false;
        }

        return symbol.IsSealed;
    }

    private static bool TryGetListElementType(ITypeSymbol type, out ITypeSymbol? elementType)
    {
        elementType = null;
        if (type is IArrayTypeSymbol arrayType)
        {
            elementType = arrayType.ElementType;
            return true;
        }

        if (type is not INamedTypeSymbol named)
        {
            return false;
        }

        string genericName = named.ConstructedFrom.ToDisplayString();
        if (genericName is
            "System.Collections.Generic.List<T>" or
            "System.Collections.Generic.LinkedList<T>" or
            "System.Collections.Generic.Queue<T>" or
            "System.Collections.Generic.Stack<T>" or
            "System.Collections.Generic.IList<T>" or
            "System.Collections.Generic.IReadOnlyList<T>")
        {
            elementType = named.TypeArguments[0];
            return true;
        }

        return false;
    }

    private static bool TryGetSetElementType(ITypeSymbol type, out ITypeSymbol? elementType)
    {
        elementType = null;
        if (type is not INamedTypeSymbol named)
        {
            return false;
        }

        string genericName = named.ConstructedFrom.ToDisplayString();
        if (genericName is
            "System.Collections.Generic.HashSet<T>" or
            "System.Collections.Generic.SortedSet<T>" or
            "System.Collections.Immutable.ImmutableHashSet<T>" or
            "System.Collections.Generic.ISet<T>" or
            "System.Collections.Generic.IReadOnlySet<T>" or
            "System.Collections.Immutable.IImmutableSet<T>")
        {
            elementType = named.TypeArguments[0];
            return true;
        }

        return false;
    }

    private static bool TryGetMapTypeArguments(ITypeSymbol type, out ITypeSymbol? keyType, out ITypeSymbol? valueType)
    {
        keyType = null;
        valueType = null;
        if (type is not INamedTypeSymbol named)
        {
            return false;
        }

        string genericName = named.ConstructedFrom.ToDisplayString();
        if (genericName is
            "System.Collections.Generic.Dictionary<TKey, TValue>" or
            "System.Collections.Generic.SortedDictionary<TKey, TValue>" or
            "System.Collections.Generic.SortedList<TKey, TValue>" or
            "System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>" or
            "System.Collections.Generic.IDictionary<TKey, TValue>" or
            "System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>" or
            "Apache.Fory.NullableKeyDictionary<TKey, TValue>")
        {
            keyType = named.TypeArguments[0];
            valueType = named.TypeArguments[1];
            return true;
        }

        return false;
    }

    private static uint? TryResolvePackedArrayTypeIdForElement(ITypeSymbol elementType)
    {
        (bool isNullable, ITypeSymbol unwrapped) = UnwrapNullable(elementType);
        if (isNullable)
        {
            return null;
        }

        uint elementTypeId = ClassifyType(unwrapped).TypeId;
        return elementTypeId switch
        {
            9 => 41,  // byte -> binary
            1 => 43,  // bool -> bool array
            2 => 44,  // sbyte -> int8 array
            3 => 45,  // short -> int16 array
            5 => 46,  // int -> int32 array
            7 => 47,  // long -> int64 array
            10 => 49, // ushort -> uint16 array
            12 => 50, // uint -> uint32 array
            14 => 51, // ulong -> uint64 array
            19 => 55, // float -> float32 array
            20 => 56, // double -> float64 array
            _ => null,
        };
    }

    private static (bool, ITypeSymbol) UnwrapNullable(ITypeSymbol type)
    {
        if (type is INamedTypeSymbol named &&
            named.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
        {
            return (true, named.TypeArguments[0]);
        }

        if (type.IsReferenceType && type.NullableAnnotation == NullableAnnotation.Annotated)
        {
            return (true, type.WithNullableAnnotation(NullableAnnotation.NotAnnotated));
        }

        return (false, type);
    }

    private static string BoolLiteral(bool value) => value ? "true" : "false";

    private static string EscapeString(string value) => value.Replace("\\", "\\\\").Replace("\"", "\\\"");

    private static string ToSnakeCase(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            return name;
        }

        StringBuilder sb = new(name.Length + 4);
        for (int i = 0; i < name.Length; i++)
        {
            char c = name[i];
            if (char.IsUpper(c))
            {
                if (i > 0)
                {
                    bool prevUpper = char.IsUpper(name[i - 1]);
                    bool nextUpperOrEnd = i + 1 >= name.Length || char.IsUpper(name[i + 1]);
                    if (!prevUpper || !nextUpperOrEnd)
                    {
                        sb.Append('_');
                    }
                }

                sb.Append(char.ToLowerInvariant(c));
            }
            else
            {
                sb.Append(c);
            }
        }

        return sb.ToString();
    }

    private static string Sanitize(string name)
    {
        StringBuilder sb = new(name.Length + 8);
        foreach (char c in name)
        {
            sb.Append(char.IsLetterOrDigit(c) ? c : '_');
        }

        return sb.ToString();
    }

    private sealed class TypeResolution
    {
        public TypeResolution(bool supported, TypeClassification classification)
        {
            Supported = supported;
            Classification = classification;
        }

        public bool Supported { get; }
        public TypeClassification Classification { get; }
    }

    private sealed class TypeClassification
    {
        public TypeClassification(
            uint typeId,
            bool isPrimitive,
            bool isBuiltIn,
            bool isCollection,
            bool isMap,
            bool isCompressedNumeric,
            int primitiveSize)
        {
            TypeId = typeId;
            IsPrimitive = isPrimitive;
            IsBuiltIn = isBuiltIn;
            IsCollection = isCollection;
            IsMap = isMap;
            IsCompressedNumeric = isCompressedNumeric;
            PrimitiveSize = primitiveSize;
        }

        public uint TypeId { get; }
        public bool IsPrimitive { get; }
        public bool IsBuiltIn { get; }
        public bool IsCollection { get; }
        public bool IsMap { get; }
        public bool IsCompressedNumeric { get; }
        public int PrimitiveSize { get; }
    }

    private sealed class TypeMetaFieldTypeModel
    {
        public TypeMetaFieldTypeModel(
            string typeIdExpr,
            bool nullable,
            bool trackRefByContext,
            ImmutableArray<TypeMetaFieldTypeModel> generics)
        {
            TypeIdExpr = typeIdExpr;
            Nullable = nullable;
            TrackRefByContext = trackRefByContext;
            Generics = generics;
        }

        public string TypeIdExpr { get; }
        public bool Nullable { get; }
        public bool TrackRefByContext { get; }
        public ImmutableArray<TypeMetaFieldTypeModel> Generics { get; }
    }

    private sealed class TypeModel
    {
        public TypeModel(
            string typeName,
            string serializerName,
            DeclKind kind,
            ImmutableArray<MemberModel> members,
            ImmutableArray<MemberModel> sortedMembers)
        {
            TypeName = typeName;
            SerializerName = serializerName;
            Kind = kind;
            Members = members;
            SortedMembers = sortedMembers;
        }

        public string TypeName { get; }
        public string SerializerName { get; }
        public DeclKind Kind { get; }
        public ImmutableArray<MemberModel> Members { get; }
        public ImmutableArray<MemberModel> SortedMembers { get; }
    }

    private sealed class MemberModel
    {
        public MemberModel(
            string name,
            string fieldIdentifier,
            int originalIndex,
            MemberDeclKind declKind,
            string typeName,
            bool isNullable,
            bool isNullableValueType,
            short? fieldId,
            TypeClassification classification,
            int group,
            bool isCollection,
            bool useDictionaryTypeInfoCache,
            bool isRefType,
            bool needsFieldTypeInfo,
            DynamicAnyKind dynamicAnyKind,
            TypeMetaFieldTypeModel typeMeta)
        {
            Name = name;
            FieldIdentifier = fieldIdentifier;
            OriginalIndex = originalIndex;
            DeclKind = declKind;
            TypeName = typeName;
            IsNullable = isNullable;
            IsNullableValueType = isNullableValueType;
            FieldId = fieldId;
            Classification = classification;
            Group = group;
            IsCollection = isCollection;
            UseDictionaryTypeInfoCache = useDictionaryTypeInfoCache;
            IsRefType = isRefType;
            NeedsFieldTypeInfo = needsFieldTypeInfo;
            DynamicAnyKind = dynamicAnyKind;
            TypeMeta = typeMeta;
        }

        public string Name { get; }
        public string FieldIdentifier { get; }
        public int OriginalIndex { get; }
        public MemberDeclKind DeclKind { get; }
        public string TypeName { get; }
        public bool IsNullable { get; }
        public bool IsNullableValueType { get; }
        public short? FieldId { get; }
        public TypeClassification Classification { get; }
        public int Group { get; }
        public bool IsCollection { get; }
        public bool UseDictionaryTypeInfoCache { get; }
        public bool IsRefType { get; }
        public bool NeedsFieldTypeInfo { get; }
        public DynamicAnyKind DynamicAnyKind { get; }
        public TypeMetaFieldTypeModel TypeMeta { get; }
    }

    private enum MemberDeclKind
    {
        Field,
        Property,
    }

    private enum DeclKind
    {
        Unknown,
        Class,
        Struct,
        Enum,
    }

    private enum DynamicAnyKind
    {
        None,
        AnyValue,
    }

    private enum FieldEncoding
    {
        None = -1,
        Varint = 0,
        Fixed = 1,
        Tagged = 2,
    }
}
