using System.Collections.Generic;
using System.Text;

namespace AyCode.Core.Serializers.SourceGenerator;

/// <summary>
/// Reader-side emit pass: generates the <c>IGeneratedBinaryReader</c> implementation for each
/// <c>[AcBinarySerializable]</c> type. Emits <c>ReadProperties</c> (inline property reads with marker
/// dispatch) and <c>ReadObject</c> (entry point with cache-index registration).
///
/// <para>Sub-passes:</para>
/// <list type="bullet">
///   <item><c>EmitReadProp</c> — per-property read emit (markerless + markered variants).</item>
///   <item><c>EmitReadString</c> — H2Q6 string-tier marker dispatch (FixStrAscii + tier-tables +
///   intern cases gated by <c>EnableInternStringFeature</c>).</item>
///   <item><c>EmitReadComplex</c> — Object / ObjectRef* / FixObj-slot dispatch for IId-typed children.</item>
///   <item><c>EmitReadCollection</c> / <c>EmitReadCollectionInline</c> / <c>EmitReadCollectionElement</c> /
///   <c>EmitReadNonComplexCollectionElement</c> — collection-shape inline reading.</item>
///   <item><c>EmitReadDictionary</c> / <c>EmitReadDictElement</c> — dict-shape inline reading.</item>
///   <item><c>EmitReadMarkeredValue</c> / <c>EmitReadMarkeredValueForKind</c> — primitive value-with-marker reads.</item>
///   <item><c>EmitReadMarkerless</c> — markerless primitive reads (FastMode + per-property markerless types).</item>
/// </list>
/// </summary>
public partial class AcBinarySourceGenerator
{
    #region Reader Code Generation

    /// <summary>
    /// Generates the IGeneratedBinaryReader implementation for a type.
    /// Phase 1: handles markerless path (no UseMetadata). UseMetadata/ChainMode → runtime fallback.
    /// Eliminates: GetWrapper dictionary lookup, CreateInstance delegate, property setter delegates,
    /// AccessorType switch dispatch, ReadValue dispatch table.
    /// </summary>
    private static string GenReader(SerializableClassInfo ci)
    {
        var sb = new StringBuilder(4096);
        sb.AppendLine("// <auto-generated/>");
        sb.AppendLine("#nullable enable");
        sb.AppendLine("using System.Runtime.CompilerServices;");
        sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
        sb.AppendLine();
        if (!string.IsNullOrEmpty(ci.Namespace))
            sb.AppendLine($"namespace {ci.Namespace};");
        sb.AppendLine();
        sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedReader : IGeneratedBinaryReader");
        sb.AppendLine("{");
        sb.AppendLine($"    internal static readonly {ci.ClassName}_GeneratedReader Instance = new();");
        sb.AppendLine();

        // ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
        // No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
        // already prevents pathological depth in well-formed payloads.
        sb.AppendLine("    public void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context)");
        sb.AppendLine("        where TInput : struct, IBinaryInputBase");
        sb.AppendLine("    {");
        sb.AppendLine($"        var obj = Unsafe.As<{ci.FullTypeName}>(value);");

        // Emit property reads — markerless for primitive types, markered for the rest
        foreach (var p in ci.Properties)
        {
            sb.AppendLine();
            EmitReadProp(sb, p, "        ", ci.EnableMetadata, ci.EnableInternString);
        }

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

        // ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
        sb.AppendLine("    public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int cacheIndex)");
        sb.AppendLine("        where TInput : struct, IBinaryInputBase");
        sb.AppendLine("    {");
        sb.AppendLine($"        var obj = new {ci.FullTypeName}();");
        sb.AppendLine("        if (cacheIndex >= 0)");
        sb.AppendLine("            context.RegisterInternedValueAt(cacheIndex, obj);");
        sb.AppendLine("        ReadProperties<TInput>(obj, context);");
        sb.AppendLine("        return obj;");
        sb.AppendLine("    }");
        sb.AppendLine("}");
        return sb.ToString();
    }

    /// <summary>
    /// Emits inline read code for a single property.
    /// Markerless types: read raw value directly (no type code in stream).
    /// Markered types: read type code byte, then dispatch.
    /// Mirrors the serializer's EmitProp symmetry.
    /// </summary>
    private static void EmitReadProp(StringBuilder sb, PropInfo p, string i, bool enableMetadata, bool enableInternString)
    {
        var a = $"obj.{p.Name}";

        // Markerless types: read raw value directly — mirrors EmitMarkerless in writer
        if (IsMarkerless(p.TypeKind))
        {
            if (p.TypeKind == PropertyTypeKind.Enum)
                sb.AppendLine($"{i}{{ var ev = context.ReadVarInt(); {a} = Unsafe.As<int, {p.TypeNameForTypeof}>(ref ev); }}");
            else
                EmitReadMarkerless(sb, p.TypeKind, a, i);
            return;
        }

        // ACCORE-BIN-T-K9M3 — caller-driven string marker dispatch. SGen-emit reads the marker byte
        // locally + handles FastWire on a separate branch; BinaryDeserializationContext.TryReadStringProperty
        // decodes every non-interning marker (FixStrAscii / StringAscii / StringSmall/Medium/Big / Null /
        // StringEmpty) in one inlinable body. The 3 interning markers go through TryReadStringColdPath
        // (AggressiveOptimization, Tier-1 direct). enableInternString gates the `|| TryReadStringColdPath`
        // emit: interning-enabled types get the short-circuit; non-interning types omit the cold call
        // entirely — the writer never produces interning markers for them, so TryReadStringProperty alone
        // is total. PropertySkip / unknown → TryReadStringProperty returns false → property left at
        // default (don't-touch contract preserved).
        if (p.TypeKind == PropertyTypeKind.String)
        {
            sb.AppendLine($"{i}if (context.FastWire)");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    {a} = context.ReadStringUtf16Markerless()!;");
            sb.AppendLine($"{i}}}");
            sb.AppendLine($"{i}else");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    var tc_{p.Name} = context.ReadByte();");
            sb.AppendLine($"{i}    string? v_{p.Name};");
            if (enableInternString)
                sb.AppendLine($"{i}    if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}) || context.TryReadStringColdPath(tc_{p.Name}, out v_{p.Name}))");
            else
                sb.AppendLine($"{i}    if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}))");
            sb.AppendLine($"{i}    {{");
            sb.AppendLine($"{i}        {a} = v_{p.Name}!;");
            sb.AppendLine($"{i}    }}");
            sb.AppendLine($"{i}}}");
            return;
        }

        // Markered types: read type code, then dispatch
        var tc = $"tc_{p.Name}";
        sb.AppendLine($"{i}var {tc} = context.ReadByte();");

        // PropertySkip → leave default
        sb.AppendLine($"{i}if ({tc} != BinaryTypeCode.PropertySkip)");
        sb.AppendLine($"{i}{{");

        // Nullable value types
        if (IsNullableVTKind(p.TypeKind))
        {
            sb.AppendLine($"{i}    if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
            sb.AppendLine($"{i}    else");
            sb.AppendLine($"{i}    {{");
            EmitReadMarkeredValue(sb, Underlying(p.TypeKind), a, tc, i + "        ", p, nullable: true);
            sb.AppendLine($"{i}    }}");
        }
        else
        {
            switch (p.TypeKind)
            {
                case PropertyTypeKind.String:
                    EmitReadString(sb, a, tc, i + "    ", enableInternString);
                    break;

                case PropertyTypeKind.Complex:
                    EmitReadComplex(sb, p, a, tc, i + "    ");
                    break;

                case PropertyTypeKind.Collection:
                    EmitReadCollection(sb, p, a, tc, i + "    ", enableInternString);
                    break;

                case PropertyTypeKind.Dictionary:
                    EmitReadDictionary(sb, p, a, tc, i + "    ", enableInternString);
                    break;

                default:
                    // Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
                    sb.AppendLine($"{i}    context._position--;");
                    if (p.IsNullable)
                        sb.AppendLine($"{i}    {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
                    else
                        sb.AppendLine($"{i}    {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
                    break;
            }
        }

        sb.AppendLine($"{i}}}");
    }

    /// <summary>
    /// Emits raw value read — no type code in stream. Mirrors EmitMarkerless exactly.
    /// </summary>
    private static void EmitReadMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
    {
        switch (k)
        {
            case PropertyTypeKind.Int32:          sb.AppendLine($"{i}{a} = context.ReadVarInt();"); break;
            case PropertyTypeKind.Int64:          sb.AppendLine($"{i}{a} = context.ReadVarLong();"); break;
            case PropertyTypeKind.Double:         sb.AppendLine($"{i}{a} = context.ReadDoubleUnsafe();"); break;
            case PropertyTypeKind.Single:         sb.AppendLine($"{i}{a} = context.ReadSingleUnsafe();"); break;
            case PropertyTypeKind.Decimal:        sb.AppendLine($"{i}{a} = context.ReadDecimalUnsafe();"); break;
            case PropertyTypeKind.DateTime:       sb.AppendLine($"{i}{a} = context.ReadDateTimeUnsafe();"); break;
            case PropertyTypeKind.Guid:           sb.AppendLine($"{i}{a} = context.ReadGuidUnsafe();"); break;
            case PropertyTypeKind.Byte:           sb.AppendLine($"{i}{a} = context.ReadByte();"); break;
            case PropertyTypeKind.Int16:          sb.AppendLine($"{i}{a} = context.ReadInt16Unsafe();"); break;
            case PropertyTypeKind.UInt16:         sb.AppendLine($"{i}{a} = context.ReadUInt16Unsafe();"); break;
            case PropertyTypeKind.UInt32:         sb.AppendLine($"{i}{a} = context.ReadVarUInt();"); break;
            case PropertyTypeKind.UInt64:         sb.AppendLine($"{i}{a} = context.ReadVarULong();"); break;
            case PropertyTypeKind.TimeSpan:       sb.AppendLine($"{i}{a} = new System.TimeSpan(context.ReadRaw<long>());"); break;
            case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}{a} = context.ReadDateTimeOffsetUnsafe();"); break;
            case PropertyTypeKind.Boolean:        sb.AppendLine($"{i}{a} = context.ReadByte() != 0;"); break;
        }
    }

    /// <summary>
    /// Emits inline string read from type code. Handles all H2Q6 (v3 wire format) string markers:
    /// FixStr (short-form universal, 135-166), String (long-form universal, 167),
    /// StringUtf16 (FastWire marker, 91),
    /// StringInternFirstSmall/Medium (interning tiers, 104/105),
    /// StringInterned (cache ref, 92), StringEmpty (93), Null.
    ///
    /// FixStr is checked first as the hot path for short strings; non-ASCII
    /// tier markers carry both <c>charLen</c> and <c>utf8Len</c> in fixed-width headers (1-pass decode).
    /// </summary>
    private static void EmitReadString(StringBuilder sb, string a, string tc, string i, bool enableInternString)
    {
        // FixStr is the hot path — short-form universal marker with charLength in the marker.
        sb.AppendLine($"{i}if (BinaryTypeCode.IsFixStr({tc}))");
        sb.AppendLine($"{i}{{");
        sb.AppendLine($"{i}    {a} = context.ReadUniversalFixStr({tc});");
        sb.AppendLine($"{i}}}");
        // Switch gives O(1) dispatch via JIT jump table for the remaining markers.
        sb.AppendLine($"{i}else switch ({tc})");
        sb.AppendLine($"{i}{{");
        // Interning case (2nd+ occurrence ref) — only emit when EnableInternStringFeature is enabled
        // on this type. When disabled, the writer never emits StringInterned markers for this type's
        // properties, so the reader doesn't need to handle them. ACCORE-BIN-T-K9M3 Phase C.
        if (enableInternString)
        {
            sb.AppendLine($"{i}    case BinaryTypeCode.StringInterned:");
            sb.AppendLine($"{i}        {a} = context.GetInternedString((int)context.ReadVarUInt());");
            sb.AppendLine($"{i}        break;");
        }
        // StringUtf16 marker + String. Wire-decode body is shared with the runtime path
        // (TypeReaderTable + cross-type populate) — see context.ReadStringUtf16Marker()
        // and ReadUniversalLongString.
        // These markers are feature-independent: writer emits them on any string property regardless of
        // intern setting (intern is opt-in per-property via [AcStringIntern] + InternBit).
        sb.AppendLine($"{i}    case BinaryTypeCode.StringUtf16:");
        sb.AppendLine($"{i}        {a} = context.ReadStringUtf16Marker();");
        sb.AppendLine($"{i}        break;");
        sb.AppendLine($"{i}    case BinaryTypeCode.String:");
        sb.AppendLine($"{i}        {a} = context.ReadUniversalLongString();");
        sb.AppendLine($"{i}        break;");
        // Interning first-occurrence cases — see comment above.
        if (enableInternString)
        {
            sb.AppendLine($"{i}    case BinaryTypeCode.StringInternFirstSmall:");
            sb.AppendLine($"{i}        {a} = context.ReadAndRegisterInternedStringSmall();");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    case BinaryTypeCode.StringInternFirstMedium:");
            sb.AppendLine($"{i}        {a} = context.ReadAndRegisterInternedStringMedium();");
            sb.AppendLine($"{i}        break;");
        }
        sb.AppendLine($"{i}    case BinaryTypeCode.Null:");
        sb.AppendLine($"{i}        {a} = null;");
        sb.AppendLine($"{i}        break;");
        sb.AppendLine($"{i}    case BinaryTypeCode.StringEmpty:");
        sb.AppendLine($"{i}        {a} = string.Empty;");
        sb.AppendLine($"{i}        break;");
        sb.AppendLine($"{i}}}");
    }

    /// <summary>
    /// Emits inline read for a Complex property.
    /// SGen reader only runs in non-metadata mode → ObjectWithMetadata never appears.
    /// Compile-time ChildNeedsRefScan eliminates ObjectRefFirst/ObjectRef when provably unused.
    /// Non-nullable + no ref → ZERO branches (tc consumed but ignored).
    /// No SGen → runtime fallback via ReadValueGenerated.
    /// </summary>
    private static void EmitReadComplex(StringBuilder sb, PropInfo p, string a, string tc, string i)
    {
        if (!p.HasGeneratedWriter)
        {
            // No SGen reader — runtime fallback (rewind + ReadValueGenerated)
            if (p.IsNullable)
            {
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
                sb.AppendLine($"{i}else");
                sb.AppendLine($"{i}{{");
                sb.AppendLine($"{i}    context._position--;");
                sb.AppendLine($"{i}    {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
                sb.AppendLine($"{i}}}");
            }
            else
            {
                sb.AppendLine($"{i}context._position--;");
                sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
            }
            return;
        }

        var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
        var cast = $"({p.TypeNameForTypeof})";

        // Ref-aware switch decision routed through RefAwareEmitPredicate — single source of truth shared
        // with the writer-side EmitDirectCollectionWrite + the sibling EmitReadCollectionElement. The
        // decision depends EXCLUSIVELY on the child compile-time fact `ChildNeedsRefScan` — the parent
        // EnableRefHandlingFeature flag is NOT a factor here (it governs only the parent's SELF-tracking
        // emit in the scan pass, not the marker dispatch for child property reads). Asymmetry-bug fix:
        // see AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
        if (!RefAwareEmitPredicate.ChildEmitsRefMarker(p))
        {
            // Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
            // Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
            // FixObj slot bytes (0..SlotCount-1) are also valid markers here — populate slot cache
            // to keep _nextRuntimeSlot in sync with the serializer's _nextTypeSlot counter.
            if (p.IsNullable)
            {
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
                sb.AppendLine($"{i}else");
                sb.AppendLine($"{i}{{");
                sb.AppendLine($"{i}    if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
                sb.AppendLine($"{i}    var rc_{p.Name} = new {p.TypeNameForTypeof}();");
                sb.AppendLine($"{i}    {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
                sb.AppendLine($"{i}    {a} = rc_{p.Name};");
                sb.AppendLine($"{i}}}");
            }
            else
            {
                // ZERO branches — tc is always Object or FixObj
                sb.AppendLine($"{i}{{");
                sb.AppendLine($"{i}    if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
                sb.AppendLine($"{i}    var rc_{p.Name} = new {p.TypeNameForTypeof}();");
                sb.AppendLine($"{i}    {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
                sb.AppendLine($"{i}    {a} = rc_{p.Name};");
                sb.AppendLine($"{i}}}");
            }
        }
        else
        {
            // Ref tracking possible — switch on tc (Object / ObjectRefFirst / [Null] / ObjectRef / <Object).
            // The 4 known TypeCode constants are emitted as switch cases — the JIT compiles them as a
            // jump-table for O(1) dispatch (vs the previous if-else chain's sequential ==-compares).
            // The polymorphic FixObj range-check (tc < Object) goes into the default branch — runtime
            // bridge path is rare on a typical SGen graph, so default fall-through is acceptable.
            // Inline: parent creates instance + handles cache registration.
            sb.AppendLine($"{i}switch ({tc})");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    case BinaryTypeCode.Object:");
            sb.AppendLine($"{i}    {{");
            sb.AppendLine($"{i}        var rc_{p.Name} = new {p.TypeNameForTypeof}();");
            sb.AppendLine($"{i}        {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
            sb.AppendLine($"{i}        {a} = rc_{p.Name};");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    }}");
            sb.AppendLine($"{i}    case BinaryTypeCode.ObjectRefFirst:");
            sb.AppendLine($"{i}    {{");
            sb.AppendLine($"{i}        var ci_{p.Name} = (int)context.ReadVarUInt();");
            sb.AppendLine($"{i}        var rc_{p.Name} = new {p.TypeNameForTypeof}();");
            sb.AppendLine($"{i}        context.RegisterInternedValueAt(ci_{p.Name}, rc_{p.Name});");
            sb.AppendLine($"{i}        {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
            sb.AppendLine($"{i}        {a} = rc_{p.Name};");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    }}");
            if (p.IsNullable)
                sb.AppendLine($"{i}    case BinaryTypeCode.Null: break;");
            sb.AppendLine($"{i}    case BinaryTypeCode.ObjectRef:");
            sb.AppendLine($"{i}        {a} = {cast}context.GetInternedObject((int)context.ReadVarUInt())!;");
            sb.AppendLine($"{i}        break;");
            // FixObj slot (0..SlotCount-1): same type via FixObj marker (non-meta, non-ref mode).
            // Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
            sb.AppendLine($"{i}    default:");
            sb.AppendLine($"{i}        if ({tc} < BinaryTypeCode.Object)");
            sb.AppendLine($"{i}        {{");
            sb.AppendLine($"{i}            context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc});");
            sb.AppendLine($"{i}            if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1;");
            sb.AppendLine($"{i}            var rc_{p.Name} = new {p.TypeNameForTypeof}();");
            sb.AppendLine($"{i}            {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
            sb.AppendLine($"{i}            {a} = rc_{p.Name};");
            sb.AppendLine($"{i}        }}");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}}}");
        }
    }

    /// <summary>
    /// Returns true when collection element reading can be inlined (no runtime ReadValue dispatch needed).
    /// </summary>
    private static bool CanInlineCollectionRead(PropInfo p)
    {
        if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter) return true;
        if (p.ElementKind == PropertyTypeKind.String) return true;
        if (p.ElementKind == PropertyTypeKind.Enum) return true;
        if (IsMarkerless(p.ElementKind)) return true; // all primitives
        return false;
    }

    /// <summary>
    /// Emits inline read for a Collection property.
    /// Known collection kind + inlineable element → inline Array loop with direct element reads.
    /// Else → runtime fallback via ReadValueGenerated.
    /// </summary>
    private static void EmitReadCollection(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
    {
        // Check if we can inline: known collection shape + inlineable element type
        if (p.CollectionKind != null && CanInlineCollectionRead(p))
        {
            EmitReadCollectionInline(sb, p, a, tc, i, enableInternString);
            return;
        }

        // Runtime fallback
        if (p.IsNullable)
        {
            sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
            sb.AppendLine($"{i}else");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    context._position--;");
            sb.AppendLine($"{i}    {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
            sb.AppendLine($"{i}}}");
        }
        else
        {
            sb.AppendLine($"{i}context._position--;");
            sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
        }
    }

    /// <summary>
    /// Emits inline read for a Dictionary property.
    /// Wire format: [Dictionary][VarUInt count][key₁ value₁ key₂ value₂ ...].
    /// Keys and values are read inline when their types are known (primitive/string/Complex+SGen).
    /// </summary>
    private static void EmitReadDictionary(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
    {
        var s = p.Name;
        var keyType = p.DictKeyTypeName ?? "object";
        var valType = p.DictValueTypeName ?? "object";

        // Can we inline key/value reads?
        var canInlineKey = p.DictKeyKind == PropertyTypeKind.String || IsMarkerless(p.DictKeyKind) || p.DictKeyKind == PropertyTypeKind.Enum;
        var canInlineValue = p.DictValueKind == PropertyTypeKind.String || IsMarkerless(p.DictValueKind) || p.DictValueKind == PropertyTypeKind.Enum
            || (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter);
        var canInline = canInlineKey || canInlineValue; // partial inline is still beneficial

        if (p.IsNullable)
        {
            sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
            sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Dictionary)");
        }
        else
        {
            sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Dictionary)");
        }

        sb.AppendLine($"{i}{{");
        sb.AppendLine($"{i}    var cnt_{s} = (int)context.ReadVarUInt();");
        sb.AppendLine($"{i}    var dict_{s} = new System.Collections.Generic.Dictionary<{keyType}, {valType}>(cnt_{s});");
        sb.AppendLine($"{i}    for (var di_{s} = 0; di_{s} < cnt_{s}; di_{s}++)");
        sb.AppendLine($"{i}    {{");

        // Read key
        if (canInlineKey)
            EmitReadDictElement(sb, p.DictKeyKind, keyType, $"dk_{s}", s, i + "        ", null, false, enableInternString);
        else
            sb.AppendLine($"{i}        var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}))!;");

        // Read value
        if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter)
        {
            var valReader = p.DictValueWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
            var vtc = $"vtc_{s}";
            sb.AppendLine($"{i}        var {vtc} = context.ReadByte();");
            sb.AppendLine($"{i}        {valType}? dv_{s} = null;");
            sb.AppendLine($"{i}        if ({vtc} == BinaryTypeCode.Object)");
            sb.AppendLine($"{i}        {{");
            sb.AppendLine($"{i}            var rv_{s} = new {valType}();");
            sb.AppendLine($"{i}            {valReader}.Instance.ReadProperties(rv_{s}, context);");
            sb.AppendLine($"{i}            dv_{s} = rv_{s};");
            sb.AppendLine($"{i}        }}");
            // ObjectRefFirst / ObjectRef cases — routed through RefAwareEmitPredicate. Single source of
            // truth shared with EmitReadComplex / EmitReadCollectionElement / EmitDirectCollectionWrite.
            // The decision depends EXCLUSIVELY on the dict-value compile-time fact `DictValueNeedsRefScan`
            // — the parent EnableRefHandlingFeature flag is NOT a factor here (it governs only the parent's
            // SELF-tracking emit in the scan pass, GenWriter.cs:140). Symmetric with the writer-side
            // dict-value emit. Asymmetry-bug fix: see AcBinarySerializerIIdReferenceTests
            // .Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
            if (RefAwareEmitPredicate.DictValueEmitsRefMarker(p))
            {
                sb.AppendLine($"{i}        else if ({vtc} == BinaryTypeCode.ObjectRefFirst)");
                sb.AppendLine($"{i}        {{");
                sb.AppendLine($"{i}            var rci_{s} = (int)context.ReadVarUInt();");
                sb.AppendLine($"{i}            var rv_{s} = new {valType}();");
                sb.AppendLine($"{i}            context.RegisterInternedValueAt(rci_{s}, rv_{s});");
                sb.AppendLine($"{i}            {valReader}.Instance.ReadProperties(rv_{s}, context);");
                sb.AppendLine($"{i}            dv_{s} = rv_{s};");
                sb.AppendLine($"{i}        }}");
                sb.AppendLine($"{i}        else if ({vtc} == BinaryTypeCode.ObjectRef)");
                sb.AppendLine($"{i}            dv_{s} = ({valType})context.GetInternedObject((int)context.ReadVarUInt())!;");
            }
            sb.AppendLine($"{i}        else if ({vtc} != BinaryTypeCode.Null)");
            sb.AppendLine($"{i}        {{");
            sb.AppendLine($"{i}            context._position--;");
            sb.AppendLine($"{i}            dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");
            sb.AppendLine($"{i}        }}");
        }
        else if (canInlineValue)
            EmitReadDictElement(sb, p.DictValueKind, valType, $"dv_{s}", s, i + "        ", null, true, enableInternString);
        else
            sb.AppendLine($"{i}        var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");

        // Add to dictionary
        sb.AppendLine($"{i}        if (dk_{s} != null) dict_{s}[dk_{s}] = dv_{s}!;");

        sb.AppendLine($"{i}    }}");
        sb.AppendLine($"{i}    {a} = dict_{s};");
        sb.AppendLine($"{i}}}");
    }

    /// <summary>
    /// Emits inline read for a single dictionary key or value element.
    /// Reads type code byte, then dispatches based on element kind.
    /// </summary>
    private static void EmitReadDictElement(StringBuilder sb, PropertyTypeKind kind, string typeName, string varName, string propSuffix, string i, PropInfo? p, bool isRefType, bool enableInternString)
    {
        var etc = $"{varName}_tc";
        sb.AppendLine($"{i}var {etc} = context.ReadByte();");

        if (kind == PropertyTypeKind.String)
        {
            sb.AppendLine($"{i}{typeName}? {varName} = null;");
            EmitReadString(sb, varName, etc, i, enableInternString);
        }
        else if (kind == PropertyTypeKind.Enum)
        {
            sb.AppendLine($"{i}{typeName} {varName} = default;");
            sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    var eb = context.ReadByte();");
            sb.AppendLine($"{i}    int eiv;");
            sb.AppendLine($"{i}    if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
            sb.AppendLine($"{i}    else eiv = context.ReadVarInt();");
            sb.AppendLine($"{i}    {varName} = ({typeName})(object)eiv;");
            sb.AppendLine($"{i}}}");
            sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {varName} = ({typeName})(object)BinaryTypeCode.DecodeTinyInt({etc});");
        }
        else
        {
            // Primitive value type — never nullable
            sb.AppendLine($"{i}{typeName} {varName} = default;");
            EmitReadMarkeredValueForKind(sb, kind, varName, etc, i);
        }
    }

    /// <summary>
    /// Emits markered value read by kind only (no PropInfo needed). For dict key/value inline reads.
    /// </summary>
    private static void EmitReadMarkeredValueForKind(StringBuilder sb, PropertyTypeKind k, string a, string tc, string i)
    {
        switch (k)
        {
            case PropertyTypeKind.Int32:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
                break;
            case PropertyTypeKind.Int64:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int64) {a} = context.ReadVarLong();");
                break;
            case PropertyTypeKind.Boolean:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.True) {a} = true;");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.False) {a} = false;");
                break;
            case PropertyTypeKind.Double:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float64) {a} = context.ReadDoubleUnsafe();");
                break;
            case PropertyTypeKind.Single:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float32) {a} = context.ReadSingleUnsafe();");
                break;
            case PropertyTypeKind.Decimal:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Decimal) {a} = context.ReadDecimalUnsafe();");
                break;
            case PropertyTypeKind.DateTime:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTime) {a} = context.ReadDateTimeUnsafe();");
                break;
            case PropertyTypeKind.Guid:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Guid) {a} = context.ReadGuidUnsafe();");
                break;
            case PropertyTypeKind.Byte:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (byte)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt8) {a} = context.ReadByte();");
                break;
            case PropertyTypeKind.Int16:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (short)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int16) {a} = context.ReadInt16Unsafe();");
                break;
            case PropertyTypeKind.UInt16:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ushort)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt16) {a} = context.ReadUInt16Unsafe();");
                break;
            case PropertyTypeKind.UInt32:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (uint)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt32) {a} = context.ReadVarUInt();");
                break;
            case PropertyTypeKind.UInt64:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ulong)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt64) {a} = context.ReadVarULong();");
                break;
            case PropertyTypeKind.TimeSpan:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.TimeSpan) {a} = context.ReadTimeSpanUnsafe();");
                break;
            case PropertyTypeKind.DateTimeOffset:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTimeOffset) {a} = context.ReadDateTimeOffsetUnsafe();");
                break;
        }
    }

    /// <summary>
    /// Emits inline collection read: Array marker already consumed as tc.
    /// Reads count + loops with direct element reads (Complex with SGen, or primitive/string/enum).
    /// Eliminates per-element: ReadValue dispatch, ReadObjectCore dict lookup, Activator.CreateInstance.
    /// </summary>
    private static void EmitReadCollectionInline(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
    {
        var isComplexElement = p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter;
        var elemType = p.ElementFullTypeName!;
        var s = p.Name;

        // Null check
        if (p.IsNullable)
        {
            sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
            sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Array)");
        }
        else
        {
            sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Array)");
        }

        sb.AppendLine($"{i}{{");
        sb.AppendLine($"{i}    var cnt_{s} = (int)context.ReadVarUInt();");

        // Create collection + loop based on kind
        if (p.CollectionKind == "Array")
        {
            sb.AppendLine($"{i}    var col_{s} = new {elemType}[cnt_{s}];");
            sb.AppendLine($"{i}    for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
            sb.AppendLine($"{i}    {{");
            if (isComplexElement)
                EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + "        ", isArray: true, p.ElementNeedsRefScan, enableInternString);
            else
                EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + "        ", isArray: true, null, enableInternString);
            sb.AppendLine($"{i}    }}");
        }
        else if (p.CollectionKind == "Counted" && p.CollectionAddMethod != null)
        {
            // Concrete custom collection — use actual type + correct add method
            if (p.CollectionHasCapacityCtor)
                sb.AppendLine($"{i}    var col_{s} = new {p.TypeNameForTypeof}(cnt_{s});");
            else
                sb.AppendLine($"{i}    var col_{s} = new {p.TypeNameForTypeof}();");
            sb.AppendLine($"{i}    for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
            sb.AppendLine($"{i}    {{");
            if (isComplexElement)
                EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + "        ", isArray: false, p.ElementNeedsRefScan, enableInternString, p.CollectionAddMethod);
            else
                EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + "        ", isArray: false, p.CollectionAddMethod, enableInternString);
            sb.AppendLine($"{i}    }}");
        }
        else // List, IndexedCollection, Counted-interface → List<T> with Add
        {
            sb.AppendLine($"{i}    var col_{s} = new System.Collections.Generic.List<{elemType}>(cnt_{s});");
            sb.AppendLine($"{i}    for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
            sb.AppendLine($"{i}    {{");
            if (isComplexElement)
                EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + "        ", isArray: false, p.ElementNeedsRefScan, enableInternString);
            else
                EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + "        ", isArray: false, null, enableInternString);
            sb.AppendLine($"{i}    }}");
        }

        sb.AppendLine($"{i}    {a} = col_{s};");
        sb.AppendLine($"{i}}}");
    }

    /// <summary>
    /// Emits per-element read inside collection loop.
    /// SGen reader = non-metadata mode → no ObjectWithMetadata fallback.
    /// !needsRefScan → only Object/Null possible → 1 branch per element.
    /// </summary>
    private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemTypeName, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan, bool enableInternString, string? addMethod = null)
    {
        var etc = $"etc_{propSuffix}";
        sb.AppendLine($"{i}var {etc} = context.ReadByte();");

        var addCall = addMethod ?? "Add";
        var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.{addCall}(null!);";
        var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = re_{propSuffix};" : $"col_{propSuffix}.{addCall}(re_{propSuffix});";

        // Ref-aware switch decision routed through RefAwareEmitPredicate — single source of truth shared
        // with the writer-side EmitDirectCollectionWrite + EmitReadComplex. The decision depends
        // EXCLUSIVELY on the element compile-time fact `needsRefScan` — the parent EnableRefHandlingFeature
        // flag is NOT a factor here. Asymmetry-bug fix:
        // see AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
        if (!RefAwareEmitPredicate.ElementEmitsRefMarker(needsRefScan))
        {
            // No ref tracking → only Object, FixObj or Null in stream — inline ReadProperties
            // FixObj slot: populate slot cache to keep _nextRuntimeSlot in sync.
            sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}");
            sb.AppendLine($"{i}else");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    if ({etc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({elemTypeName}), {etc}); if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1; }}");
            sb.AppendLine($"{i}    var re_{propSuffix} = new {elemTypeName}();");
            sb.AppendLine($"{i}    {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
            sb.AppendLine($"{i}    {assignExpr}");
            sb.AppendLine($"{i}}}");
        }
        else
        {
            // Switch on etc (Object / ObjectRefFirst / Null / ObjectRef / <Object). The JIT emits the
            // 4 known TypeCode constants as a jump-table (O(1) dispatch); the polymorphic FixObj
            // range-check (etc < Object) goes into the default branch. Object hot-path stays first.
            sb.AppendLine($"{i}switch ({etc})");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    case BinaryTypeCode.Object:");
            sb.AppendLine($"{i}    {{");
            sb.AppendLine($"{i}        var re_{propSuffix} = new {elemTypeName}();");
            sb.AppendLine($"{i}        {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
            sb.AppendLine($"{i}        {assignExpr}");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    }}");
            sb.AppendLine($"{i}    case BinaryTypeCode.ObjectRefFirst:");
            sb.AppendLine($"{i}    {{");
            sb.AppendLine($"{i}        var ci_{propSuffix} = (int)context.ReadVarUInt();");
            sb.AppendLine($"{i}        var re_{propSuffix} = new {elemTypeName}();");
            sb.AppendLine($"{i}        context.RegisterInternedValueAt(ci_{propSuffix}, re_{propSuffix});");
            sb.AppendLine($"{i}        {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
            sb.AppendLine($"{i}        {assignExpr}");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    }}");
            sb.AppendLine($"{i}    case BinaryTypeCode.Null:");
            sb.AppendLine($"{i}        {assignNull}");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}    case BinaryTypeCode.ObjectRef:");
            if (isArray)
                sb.AppendLine($"{i}        col_{propSuffix}[{indexVar}] = {elemCast}context.GetInternedObject((int)context.ReadVarUInt())!;");
            else
                sb.AppendLine($"{i}        col_{propSuffix}.{addCall}({elemCast}context.GetInternedObject((int)context.ReadVarUInt())!);");
            sb.AppendLine($"{i}        break;");
            // FixObj slot (0..SlotCount-1): same type via FixObj marker.
            // Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
            sb.AppendLine($"{i}    default:");
            sb.AppendLine($"{i}        if ({etc} < BinaryTypeCode.Object)");
            sb.AppendLine($"{i}        {{");
            sb.AppendLine($"{i}            context.GetWrapper(typeof({elemTypeName}), {etc});");
            sb.AppendLine($"{i}            if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1;");
            sb.AppendLine($"{i}            var re_{propSuffix} = new {elemTypeName}();");
            sb.AppendLine($"{i}            {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
            sb.AppendLine($"{i}            {assignExpr}");
            sb.AppendLine($"{i}        }}");
            sb.AppendLine($"{i}        break;");
            sb.AppendLine($"{i}}}");
        }
    }

    /// <summary>
    /// Emits per-element read for non-Complex collection elements (String, primitive, Enum).
    /// Reads type code byte, then dispatches based on ElementKind.
    /// </summary>
    private static void EmitReadNonComplexCollectionElement(StringBuilder sb, PropInfo p, string indexVar, string propSuffix, string i, bool isArray, string? addMethod, bool enableInternString)
    {
        var addCall = addMethod ?? "Add";
        var elemType = p.ElementFullTypeName!;
        var colRef = $"col_{propSuffix}";

        // String element FastWire markerless fast-path — same wire as property-level (int32 sentinel header).
        // All FastWire string writes funnel through `WriteStringWithDispatch.FastWire = WriteStringUtf16Markerless`,
        // so collection elements use the same markerless format. Skips the etc-read entirely in FastWire mode.
        if (p.ElementKind == PropertyTypeKind.String)
        {
            var tempVar = $"sv_{propSuffix}";
            sb.AppendLine($"{i}string? {tempVar};");
            sb.AppendLine($"{i}if (context.FastWire)");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    {tempVar} = context.ReadStringUtf16Markerless();");
            sb.AppendLine($"{i}}}");
            sb.AppendLine($"{i}else");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    var etc_{propSuffix} = context.ReadByte();");
            sb.AppendLine($"{i}    {tempVar} = null;");
            EmitReadString(sb, tempVar, $"etc_{propSuffix}", i + "    ", enableInternString);
            sb.AppendLine($"{i}}}");
            if (isArray)
                sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar}!;");
            else
                sb.AppendLine($"{i}{colRef}.{addCall}({tempVar}!);");
            return;
        }

        var etc = $"etc_{propSuffix}";
        sb.AppendLine($"{i}var {etc} = context.ReadByte();");

        if (p.ElementKind == PropertyTypeKind.Enum)
        {
            // Enum element: Enum marker or TinyInt
            var tempVar = $"ev_{propSuffix}";
            sb.AppendLine($"{i}{elemType} {tempVar} = default;");
            sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
            sb.AppendLine($"{i}{{");
            sb.AppendLine($"{i}    var eb = context.ReadByte();");
            sb.AppendLine($"{i}    int eiv;");
            sb.AppendLine($"{i}    if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
            sb.AppendLine($"{i}    else eiv = context.ReadVarInt();");
            sb.AppendLine($"{i}    {tempVar} = ({elemType})(object)eiv;");
            sb.AppendLine($"{i}}}");
            sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {tempVar} = ({elemType})(object)BinaryTypeCode.DecodeTinyInt({etc});");
            if (isArray)
                sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
            else
                sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
        }
        else
        {
            // Primitive element: read markered value
            var tempVar = $"pv_{propSuffix}";
            sb.AppendLine($"{i}{elemType} {tempVar} = default;");
            // Create a minimal PropInfo-like context for EmitReadMarkeredValue
            EmitReadMarkeredValue(sb, p.ElementKind, tempVar, etc, i, p, nullable: false);
            if (isArray)
                sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
            else
                sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
        }
    }

    /// <summary>
    /// Emits markered value read for primitive types (with type code already read).
    /// Handles TinyInt encoding for integer types.
    /// </summary>
    private static void EmitReadMarkeredValue(StringBuilder sb, PropertyTypeKind k, string a, string tc, string i, PropInfo p, bool nullable)
    {
        var assign = nullable ? $"{a} = " : $"{a} = ";
        switch (k)
        {
            case PropertyTypeKind.Int32:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {assign}context.ReadVarInt();");
                break;
            case PropertyTypeKind.Int64:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {assign}context.ReadVarInt();");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int64) {assign}context.ReadVarLong();");
                break;
            case PropertyTypeKind.Boolean:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.True) {assign}true;");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.False) {assign}false;");
                break;
            case PropertyTypeKind.Double:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float64) {assign}context.ReadDoubleUnsafe();");
                break;
            case PropertyTypeKind.Single:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float32) {assign}context.ReadSingleUnsafe();");
                break;
            case PropertyTypeKind.Decimal:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Decimal) {assign}context.ReadDecimalUnsafe();");
                break;
            case PropertyTypeKind.DateTime:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTime) {assign}context.ReadDateTimeUnsafe();");
                break;
            case PropertyTypeKind.Guid:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Guid) {assign}context.ReadGuidUnsafe();");
                break;
            case PropertyTypeKind.Byte:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(byte)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt8) {assign}context.ReadByte();");
                break;
            case PropertyTypeKind.Int16:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(short)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int16) {assign}context.ReadInt16Unsafe();");
                break;
            case PropertyTypeKind.UInt16:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(ushort)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt16) {assign}context.ReadUInt16Unsafe();");
                break;
            case PropertyTypeKind.UInt32:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(uint)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt32) {assign}context.ReadVarUInt();");
                break;
            case PropertyTypeKind.UInt64:
                sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(ulong)BinaryTypeCode.DecodeTinyInt({tc});");
                sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt64) {assign}context.ReadVarULong();");
                break;
            case PropertyTypeKind.Enum:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Enum)");
                sb.AppendLine($"{i}{{");
                sb.AppendLine($"{i}    var eb = context.ReadByte();");
                sb.AppendLine($"{i}    int ev;");
                sb.AppendLine($"{i}    if (BinaryTypeCode.IsTinyInt(eb)) ev = BinaryTypeCode.DecodeTinyInt(eb);");
                sb.AppendLine($"{i}    else ev = context.ReadVarInt();");
                sb.AppendLine($"{i}    {assign}({p.TypeNameForTypeof})(object)ev;");
                sb.AppendLine($"{i}}}");
                sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({tc})) {assign}({p.TypeNameForTypeof})(object)BinaryTypeCode.DecodeTinyInt({tc});");
                break;
            case PropertyTypeKind.TimeSpan:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.TimeSpan) {assign}context.ReadTimeSpanUnsafe();");
                break;
            case PropertyTypeKind.DateTimeOffset:
                sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTimeOffset) {assign}context.ReadDateTimeOffsetUnsafe();");
                break;
        }
    }

    #endregion
}
