using System.Runtime.CompilerServices;

namespace AyCode.Core.Serializers.Binaries;

public static partial class AcBinarySerializer
{
    internal sealed partial class BinarySerializationContext<TOutput>
        where TOutput : struct, IBinaryOutputBase
    {
        #region Property Writer Bridges — used by SGen generated code

        /// <summary>
        /// Writes an Int32 property value. UseMetadata: skip if 0, TinyInt or Int32+VarInt. Markerless: VarInt only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteInt32Property(int value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny)) { WriteByte(tiny); return; }
                WriteByte(BinaryTypeCode.Int32);
            }

            WriteVarInt(value);
        }

        /// <summary>
        /// Writes an Int64 property value. UseMetadata: skip if 0, int-range TinyInt or Int64+VarLong. Markerless: VarLong only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteInt64Property(long value)
        {
            if (UseMetadata)
            {
                if (value == 0L) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                if (value >= int.MinValue && value <= int.MaxValue)
                {
                    var iv = (int)value;
                    if (BinaryTypeCode.TryEncodeTinyInt(iv, out var tiny)) { WriteByte(tiny); return; }
                    WriteByte(BinaryTypeCode.Int32);
                    WriteVarInt(iv);
                }
                else
                {
                    WriteByte(BinaryTypeCode.Int64);
                    WriteVarLong(value);
                }
            }
            else
            {
                WriteVarLong(value);
            }
        }

        /// <summary>
        /// Writes a Boolean property value. UseMetadata: skip if false, else True. Markerless: 1/0 byte.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteBoolProperty(bool value)
        {
            if (UseMetadata)
                WriteByte(value ? BinaryTypeCode.True : BinaryTypeCode.PropertySkip);
            else
                WriteByte(value ? (byte)1 : (byte)0);
        }

        /// <summary>
        /// Writes a Double property value. UseMetadata: skip if 0.0, else Float64+Raw. Markerless: Raw only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteFloat64Property(double value)
        {
            if (UseMetadata)
            {
                if (value == 0.0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteTypeCodeAndRaw(BinaryTypeCode.Float64, value);
            }
            else
            {
                WriteRaw(value);
            }
        }

        /// <summary>
        /// Writes a Single property value. UseMetadata: skip if 0f, else Float32+Raw. Markerless: Raw only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteFloat32Property(float value)
        {
            if (UseMetadata)
            {
                if (value == 0f) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteTypeCodeAndRaw(BinaryTypeCode.Float32, value);
            }
            else
            {
                WriteRaw(value);
            }
        }

        /// <summary>
        /// Writes a Decimal property value. UseMetadata: skip if 0m, else Decimal+Bits. Markerless: DecimalBits only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDecimalProperty(decimal value)
        {
            if (UseMetadata)
            {
                if (value == 0m) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.Decimal);
            }

            WriteDecimalBits(value);
        }

        /// <summary>
        /// Writes a Guid property value. UseMetadata: skip if Empty, else Guid+Bits. Markerless: GuidBits only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteGuidProperty(Guid value)
        {
            if (UseMetadata)
            {
                if (value == Guid.Empty) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.Guid);
            }

            WriteGuidBits(value);
        }

        /// <summary>
        /// Writes a DateTime property value. UseMetadata: DateTime+Bits (no skip). Markerless: DateTimeBits only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDateTimeProperty(DateTime value)
        {
            if (UseMetadata)
            {
                WriteByte(BinaryTypeCode.DateTime);
            }

            WriteDateTimeBits(value);
        }

        /// <summary>
        /// Writes a Byte property value. UseMetadata: skip if 0, else UInt8+byte. Markerless: byte only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteByteProperty(byte value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.UInt8);
            }

            WriteByte(value);
        }

        /// <summary>
        /// Writes an Int16 property value. UseMetadata: skip if 0, else Int16+Raw. Markerless: Raw only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteInt16Property(short value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteTypeCodeAndRaw(BinaryTypeCode.Int16, value);
            }
            else
            {
                WriteRaw(value);
            }
        }

        /// <summary>
        /// Writes a UInt16 property value. UseMetadata: skip if 0, else UInt16+Raw. Markerless: Raw only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteUInt16Property(ushort value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, value);
            }
            else
            {
                WriteRaw(value);
            }
        }

        /// <summary>
        /// Writes a UInt32 property value. UseMetadata: skip if 0, else UInt32+VarUInt. Markerless: VarUInt only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteUInt32Property(uint value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.UInt32);
            }

            WriteVarUInt(value);
        }

        /// <summary>
        /// Writes a UInt64 property value. UseMetadata: skip if 0, else UInt64+VarULong. Markerless: VarULong only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteUInt64Property(ulong value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.UInt64);
            }

            WriteVarULong(value);
        }

        /// <summary>
        /// Writes an Enum property value (pre-cast to int). UseMetadata: skip if 0, else Enum+TinyInt/VarInt. Markerless: VarInt only.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteEnumInt32Property(int value)
        {
            if (UseMetadata)
            {
                if (value == 0) { WriteByte(BinaryTypeCode.PropertySkip); return; }
                WriteByte(BinaryTypeCode.Enum);
                if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny))
                    WriteByte(tiny);
                else
                {
                    WriteByte(BinaryTypeCode.Int32);
                    WriteVarInt(value);
                }
            }
            else
            {
                WriteVarInt(value);
            }
        }

        /// <summary>
        /// Writes a TimeSpan property value. UseMetadata: TimeSpan+Raw(Ticks). Markerless: Raw(Ticks) only. No skip.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteTimeSpanProperty(TimeSpan value)
        {
            if (UseMetadata)
                WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, value.Ticks);
            else
                WriteRaw(value.Ticks);
        }

        /// <summary>
        /// Writes a DateTimeOffset property value. UseMetadata: DateTimeOffset+Bits. Markerless: DateTimeOffsetBits only. No skip.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteDateTimeOffsetProperty(DateTimeOffset value)
        {
            if (UseMetadata)
            {
                WriteByte(BinaryTypeCode.DateTimeOffset);
            }

            WriteDateTimeOffsetBits(value);
        }

        #endregion

        #region Object Marker — SGen bridge for complex child writes
        // SGen selects the correct variant at compile-time based on ChildNeedsRefScan / ChildEnableMetadata / IsIId.
        // The 4th case (!ref && !meta) is handled inline by SGen: Object + WriteProperties (ZERO branches).
        // Marker methods write only the object marker bytes. WriteProperties is called directly in SGen
        // (preserving direct call — no interface dispatch on IGeneratedBinaryWriter).

        /// <summary>
        /// Ref tracking (IId) only, no metadata. Uses HasRefHandling.
        /// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal bool WriteObjectRefMarkerIId()
        {
            // Combined check+inc — TryEnterRecursion inc'd _recursionDepth on success.
            if (TryEnterRecursion(hasTruncatePath: true)) return false;

            if (HasRefHandling && TryConsumeWritePlanEntry(out var pe))
            {
                if (!pe.IsFirst)
                {
                    // 2nd-occurrence: ObjectRef written, no body to recurse into → undo the inc.
                    ExitRecursion();
                    WriteByte(BinaryTypeCode.ObjectRef);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    return false;
                }

                WriteByte(BinaryTypeCode.ObjectRefFirst);
                WriteVarUInt((uint)pe.CacheMapIndex);
                return true;
            }

            WriteByte(BinaryTypeCode.Object);
            return true;
        }

        /// <summary>
        /// Ref tracking (AllRef) only, no metadata. Uses HasAllRefHandling.
        /// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal bool WriteObjectRefMarkerAll()
        {
            // Combined check+inc — TryEnterRecursion inc'd _recursionDepth on success.
            if (TryEnterRecursion(hasTruncatePath: true)) return false;

            if (HasAllRefHandling && TryConsumeWritePlanEntry(out var pe))
            {
                if (!pe.IsFirst)
                {
                    // 2nd-occurrence: ObjectRef written, no body to recurse into → undo the inc.
                    ExitRecursion();
                    WriteByte(BinaryTypeCode.ObjectRef);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    return false;
                }

                WriteByte(BinaryTypeCode.ObjectRefFirst);
                WriteVarUInt((uint)pe.CacheMapIndex);
                return true;
            }

            WriteByte(BinaryTypeCode.Object);
            return true;
        }

        /// <summary>
        /// Metadata only, no ref tracking. Writes ObjectWithMetadata or Object marker.
        /// Returns <c>false</c> if depth-limit Truncate fired (caller skips WriteProperties), <c>true</c> otherwise.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal bool WriteObjectMetaMarker(object value, int wrapperSlot)
        {
            // Combined check+inc — no ref handling here, no 2nd-occurrence concern, always recurses on success.
            if (TryEnterRecursion(hasTruncatePath: true)) return false;

            if (UseMetadata)
            {
                var wrapper = GetWrapper(value.GetType(), wrapperSlot);
                var isFirstMeta = RegisterMetadataType(wrapper);
                WriteByte(BinaryTypeCode.ObjectWithMetadata);
                WriteInlineMetadata(wrapper.Metadata, isFirstMeta);
            }
            else
            {
                WriteByte(BinaryTypeCode.Object);
            }
            return true;
        }

        /// <summary>
        /// Full path (IId): ref tracking + metadata. Uses HasRefHandling.
        /// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
        /// </summary>
        internal bool WriteObjectFullMarkerIId(object value, int wrapperSlot)
        {
            // Combined check+inc FIRST — fires BEFORE any marker write so Truncate writes Null wire-correctly.
            // TryEnterRecursion inc'd _recursionDepth on success; 2nd-occurrence ObjectRef branch below undoes it.
            if (TryEnterRecursion(hasTruncatePath: true)) return false;

            var useMetadata = UseMetadata;
            bool isFirstMeta = false;
            BinarySerializeTypeMetadata? metadata = null;
            if (useMetadata)
            {
                var wrapper = GetWrapper(value.GetType(), wrapperSlot);
                isFirstMeta = RegisterMetadataType(wrapper);
                metadata = wrapper.Metadata;
            }

            if (HasRefHandling && TryConsumeWritePlanEntry(out var pe))
            {
                if (!pe.IsFirst)
                {
                    // 2nd-occurrence: ObjectRef written, no body to recurse into → undo the inc.
                    ExitRecursion();
                    WriteByte(BinaryTypeCode.ObjectRef);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    return false;
                }

                // 1st-occurrence ObjectRefFirst: writes marker + body via WriteProperties (inc already done)
                if (useMetadata)
                {
                    WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    WriteInlineMetadata(metadata!, isFirstMeta);
                }
                else
                {
                    WriteByte(BinaryTypeCode.ObjectRefFirst);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                }

                return true;
            }

            // Non-ref path: write marker + body via WriteProperties (inc already done)
            if (useMetadata)
            {
                WriteByte(BinaryTypeCode.ObjectWithMetadata);
                WriteInlineMetadata(metadata!, isFirstMeta);
            }
            else
            {
                WriteByte(BinaryTypeCode.Object);
            }

            return true;
        }

        /// <summary>
        /// Full path (AllRef): ref tracking + metadata. Uses HasAllRefHandling.
        /// Returns false if ObjectRef 2nd occurrence (caller must skip WriteProperties).
        /// </summary>
        internal bool WriteObjectFullMarkerAll(object value, int wrapperSlot)
        {
            // Combined check+inc FIRST — wire-correct Truncate semantics (Null instead of marker, no rewind).
            // TryEnterRecursion inc'd _recursionDepth on success; 2nd-occurrence ObjectRef branch below undoes it.
            if (TryEnterRecursion(hasTruncatePath: true)) return false;

            var useMetadata = UseMetadata;
            bool isFirstMeta = false;
            BinarySerializeTypeMetadata? metadata = null;
            if (useMetadata)
            {
                var wrapper = GetWrapper(value.GetType(), wrapperSlot);
                isFirstMeta = RegisterMetadataType(wrapper);
                metadata = wrapper.Metadata;
            }

            if (HasAllRefHandling && TryConsumeWritePlanEntry(out var pe))
            {
                if (!pe.IsFirst)
                {
                    // 2nd-occurrence: ObjectRef written, no body to recurse into → undo the inc.
                    ExitRecursion();
                    WriteByte(BinaryTypeCode.ObjectRef);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    return false;
                }

                // 1st-occurrence: writes marker + body via WriteProperties (inc already done)
                if (useMetadata)
                {
                    WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                    WriteInlineMetadata(metadata!, isFirstMeta);
                }
                else
                {
                    WriteByte(BinaryTypeCode.ObjectRefFirst);
                    WriteVarUInt((uint)pe.CacheMapIndex);
                }

                return true;
            }

            // Non-ref path: writes marker + body via WriteProperties (inc already done)
            if (useMetadata)
            {
                WriteByte(BinaryTypeCode.ObjectWithMetadata);
                WriteInlineMetadata(metadata!, isFirstMeta);
            }
            else
            {
                WriteByte(BinaryTypeCode.Object);
            }

            return true;
        }

        #endregion
    }
}
