using System;
using System.Runtime.CompilerServices;
using AyCode.Core.Helpers;
using AyCode.Core.Serializers.Binaries;

namespace AyCode.Core.Serializers;

/// <summary>
/// Wrapper that combines metadata with tracking state.
/// Each context has one wrapper per type - contains all type-specific info and state.
/// Uses typed fields instead of generics for zero-overhead Id operations.
/// </summary>
/// <typeparam name="TMetadata">The concrete metadata type (BinarySerializeTypeMetadata, etc.)</typeparam>
public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadataBase
{
    /// <summary>
    /// The cached metadata reference (from GlobalMetadataCache).
    /// </summary>
    public readonly TMetadata Metadata;
    
    /// <summary>
    /// Pre-cast Int32 getter for fast path (no cast needed in TryTrack).
    /// Set when IdAccessorType == Int32.
    /// </summary>
    internal readonly Func<object, int>? RefIdGetterInt32;
    
    /// <summary>
    /// Pre-cast Int64 getter for fast path.
    /// Set when IdAccessorType == Int64.
    /// </summary>
    internal readonly Func<object, long>? RefIdGetterInt64;
    
    /// <summary>
    /// Pre-cast Guid getter for fast path.
    /// Set when IdAccessorType == Guid.
    /// </summary>
    internal readonly Func<object, Guid>? RefIdGetterGuid;

    /// <summary>
    /// UseMetadata: has this type been seen in the current serialization session?
    /// false = not yet registered. Set by RegisterMetadataType, reset by ResetTracking.
    /// Used by both runtime and SGen paths (single wrapper per type per context).
    /// </summary>
    internal bool MetadataSeen;

    /// <summary>
    /// Polymorphic type tracking: has this runtime type been written as a polymorphic prefix?
    /// false = first occurrence → write ObjectWithTypeName (68) + full type name.
    /// true = repeated → write ObjectWithTypeIndex (70) + PolymorphicCacheIndex.
    /// Same pattern as MetadataSeen. Reset by ResetTracking.
    /// </summary>
    internal bool PolymorphicSeen;

    /// <summary>
    /// Unified type slot index for FixObj system. Used by both poly and non-poly types.
    /// -1 = not yet assigned. Set together with PolymorphicSeen = true.
    /// Reset by ResetTracking (per-session, slot order depends on stream encounter order).
    /// </summary>
    internal int PolymorphicCacheIndex = -1;

    /// <summary>
    /// UseMetadata cachemap: source property index → target PropertySetter.
    /// Per-context (wrapper-szintű), mert futásonként eltérő source type-pal találkozhat.
    /// Felépül a ReadObjectWithMetadata-ban, első előforduláskor.
    /// </summary>
    internal BinaryPropertySetterBase?[]? CacheMap;

    /// <summary>
    /// Pre-cached wrappers for complex (non-primitive, non-string) property types.
    /// Indexed by BinaryPropertyAccessorBase.ComplexPropertyIndex.
    /// Eliminates GetWrapper dictionary lookups in scan and write pass hot paths.
    /// Lazy-allocated on first access, cleared in ResetTracking.
    /// </summary>
    internal TypeMetadataWrapper<TMetadata>?[]? PropertyTypeWrappers;

    /// <summary>
    /// Source-generated binary writer for this type. Bypasses the runtime switch/delegate loop.
    /// Set via AcBinarySerializer.RegisterGeneratedWriter. Null = use runtime path.
    /// Checked once per object in WriteObject (not per property).
    /// </summary>
    internal IGeneratedBinaryWriter? GeneratedWriter;
    internal IGeneratedBinaryReader? GeneratedReader;

    /// <summary>
    /// Options-filtered subset of metadata.ReferenceProperties for the scan pass.
    /// Built lazily on first scan pass call, stable during session, cleared in ResetTracking.
    /// Filters applied at build time (0 runtime checks in scan loop):
    /// - StringInterningMode: None → strings excluded; Attribute → only [AcStringIntern(true)];
    ///   All → [AcStringIntern(false)] excluded
    /// - IsComplexType: always included
    /// 
    /// TODO: PropertyFilter support — when PropertyFilter is set, pre-filter with
    /// IsMetadataPhase=true (instance=null). Properties returning false are excluded.
    /// Instance-dependent filtering remains in write pass only.
    /// </summary>
    internal BinaryPropertyAccessorBase[]? FilteredReferenceProperties;

    #region Typed IdentityMaps - No generic type checks in hot path!

    /// <summary>
    /// Typed IdentityMap for Int32 IDs. Direct access, no type check.
    /// </summary>
    internal IdentityMap<int, InternEntry>? IdentityMapInt32;

    /// <summary>
    /// Typed IdentityMap for Int64 IDs. Direct access, no type check.
    /// </summary>
    internal IdentityMap<long, InternEntry>? IdentityMapInt64;

    /// <summary>
    /// Typed IdentityMap for Guid IDs. Direct access, no type check.
    /// </summary>
    internal IdentityMap<Guid, InternEntry>? IdentityMapGuid;

    #endregion

    /// <summary>
    /// BitArray for tracking small int32 IDs (0-65535) during serialization.
    /// Only used when IdAccessorType == Int32.
    /// </summary>
    internal ulong[]? SmallIdBitmap;

    private const int BitArraySize = 1024; // 1024 * 64 = 65,536 IDs

    /// <summary>
    /// Static fallback delegate for non-IId types.
    /// Shared across all wrappers to avoid allocation.
    /// </summary>
    private static readonly Func<object, int> HashCodeGetter = RuntimeHelpers.GetHashCode;
    

    /// <summary>
    /// Creates a new wrapper for the given metadata.
    /// Uses metadata.TypedIdGetter for IId types (cached, no allocation).
    /// Pre-casts typed getters for zero-overhead access in hot path.
    /// </summary>
    public TypeMetadataWrapper(TMetadata metadata)
    {
        Metadata = metadata;

        // Use cached delegate from metadata for IId types, static fallback for non-IId
        var refIdGetter = metadata.TypedIdGetter ?? HashCodeGetter;

        // Pre-cast typed getters based on IdAccessorType
        switch (metadata.IdAccessorType)
        {
            case IdAccessorType.Int32:
                RefIdGetterInt32 = (Func<object, int>)refIdGetter;
                break;
            case IdAccessorType.Int64:
                RefIdGetterInt64 = (Func<object, long>)refIdGetter;
                break;
            case IdAccessorType.Guid:
                RefIdGetterGuid = (Func<object, Guid>)refIdGetter;
                break;
        }

        // Pre-allocate PropertyTypeWrappers — eliminates null/resize checks from hot path
        if (metadata.ComplexPropertyCount > 0)
            PropertyTypeWrappers = new TypeMetadataWrapper<TMetadata>?[metadata.ComplexPropertyCount];

        // Lookup generated writer from registry (once per wrapper creation, not per serialization)
        GeneratedWriter = AcBinarySerializer.GeneratedWriterRegistry.TryGet(metadata.SourceType);
        GeneratedReader = AcBinaryDeserializer.GeneratedReaderRegistry.TryGet(metadata.SourceType);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool UseTypeReferenceHandling(ReferenceHandlingMode referenceHandling)
    {
        return referenceHandling != ReferenceHandlingMode.None && (Metadata.IsIId || referenceHandling == ReferenceHandlingMode.All);
    }

    /// <summary>
    /// Gets a pre-cached wrapper for a complex property type.
    /// Returns the cached wrapper if it matches the runtime type, null otherwise.
    /// </summary>
    /// <param name="complexPropertyIndex">Index from BinaryPropertyAccessorBase.ComplexPropertyIndex (-1 for non-complex)</param>
    /// <param name="runtimeType">The actual runtime type of the property value</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public TypeMetadataWrapper<TMetadata>? GetPropertyTypeWrapper(int complexPropertyIndex, Type runtimeType)
    {
        if (complexPropertyIndex < 0)
            return null;

        var cached = PropertyTypeWrappers![complexPropertyIndex];
        if (cached != null && cached.Metadata.SourceType == runtimeType)
            return cached;

        return null;
    }

    /// <summary>
    /// Caches a resolved wrapper for a complex property type.
    /// Overwrites any previous value (handles polymorphic types gracefully).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void SetPropertyTypeWrapper(int complexPropertyIndex, TypeMetadataWrapper<TMetadata> wrapper)
    {
        PropertyTypeWrappers![complexPropertyIndex] = wrapper;
    }

    /// <summary>
    /// Resets tracking state for reuse between serializations.
    /// Does not deallocate - just clears for reuse (pool-friendly).
    /// </summary>
    /// <param name="preRentBuckets">If true, pre-rent arrays at next capacity (useful for async Clear)</param>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void ResetTracking(bool preRentBuckets = false)
    {
        MetadataSeen = false;
        PolymorphicSeen = false;
        PolymorphicCacheIndex = -1;
        CacheMap = null;

        // Options may change between sessions (pool reuse) → rebuild on next scan
        FilteredReferenceProperties = null;

        if (SmallIdBitmap != null)
            Array.Clear(SmallIdBitmap);

        IdentityMapInt32?.Reset(preRentBuckets);
        IdentityMapInt64?.Reset(preRentBuckets);
        IdentityMapGuid?.Reset(preRentBuckets);
    }

    #region Serializer: TryTrack by typed Id → InternEntry with slotIndex

    /// <summary>
    /// Tries to track Int32 Id. Returns true if first occurrence.
    /// On 2+ occurrence: assigns CacheIndex immediately using ++nextCacheIndex.
    /// firstVisitIndex >= 0 only on exact 2nd occurrence (CacheIndex transition from -1).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryTrackInt32(int id, int visitIndex, ref int nextCacheIndex, out int cacheIndex, out int firstVisitIndex)
    {
        if (id == 0) { cacheIndex = -1; firstVisitIndex = -1; return true; } // Default Id - no tracking

        var map = IdentityMapInt32 ??= new IdentityMap<int, InternEntry>();
        if (!map.TryAdd(id, out var slotIndex))
        {
            // 2+ occurrence: assign CacheIndex immediately
            ref var entry = ref map.GetValueRef(slotIndex);
            if (entry.CacheIndex == -1)
            {
                entry.CacheIndex = nextCacheIndex++;
                entry.IsFirstWrite = true;
                firstVisitIndex = entry.FirstIndex;
            }
            else
            {
                firstVisitIndex = -1; // 3rd+ occurrence
            }
            cacheIndex = entry.CacheIndex;
            return false;
        }

        // 1st occurrence: store scan visit index
        ref var newEntry = ref map.GetValueRef(slotIndex);
        newEntry.FirstIndex = visitIndex;
        newEntry.CacheIndex = -1;
        cacheIndex = -1;
        firstVisitIndex = -1;
        return true;
    }

    /// <summary>
    /// Tries to track Int64 Id. Returns true if first occurrence.
    /// firstVisitIndex >= 0 only on exact 2nd occurrence.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryTrackInt64(long id, int visitIndex, ref int nextCacheIndex, out int cacheIndex, out int firstVisitIndex)
    {
        if (id == 0) { cacheIndex = -1; firstVisitIndex = -1; return true; }

        var map = IdentityMapInt64 ??= new IdentityMap<long, InternEntry>();
        if (!map.TryAdd(id, out var slotIndex))
        {
            ref var entry = ref map.GetValueRef(slotIndex);
            if (entry.CacheIndex == -1)
            {
                entry.CacheIndex = nextCacheIndex++;
                entry.IsFirstWrite = true;
                firstVisitIndex = entry.FirstIndex;
            }
            else
            {
                firstVisitIndex = -1;
            }
            cacheIndex = entry.CacheIndex;
            return false;
        }

        ref var newEntry = ref map.GetValueRef(slotIndex);
        newEntry.FirstIndex = visitIndex;
        newEntry.CacheIndex = -1;
        cacheIndex = -1;
        firstVisitIndex = -1;
        return true;
    }

    /// <summary>
    /// Tries to track Guid Id. Returns true if first occurrence.
    /// firstVisitIndex >= 0 only on exact 2nd occurrence.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryTrackGuid(Guid id, int visitIndex, ref int nextCacheIndex, out int cacheIndex, out int firstVisitIndex)
    {
        if (id == Guid.Empty) { cacheIndex = -1; firstVisitIndex = -1; return true; }

        var map = IdentityMapGuid ??= new IdentityMap<Guid, InternEntry>();
        if (!map.TryAdd(id, out var slotIndex))
        {
            ref var entry = ref map.GetValueRef(slotIndex);
            if (entry.CacheIndex == -1)
            {
                entry.CacheIndex = nextCacheIndex++;
                entry.IsFirstWrite = true;
                firstVisitIndex = entry.FirstIndex;
            }
            else
            {
                firstVisitIndex = -1;
            }
            cacheIndex = entry.CacheIndex;
            return false;
        }

        ref var newEntry = ref map.GetValueRef(slotIndex);
        newEntry.FirstIndex = visitIndex;
        newEntry.CacheIndex = -1;
        cacheIndex = -1;
        firstVisitIndex = -1;
        return true;
    }

    #endregion

    #region Lookup for Serialize Pass (after scan)

    /// <summary>
    /// Lookup Int32 Id entry. Returns ref to InternEntry for IsFirstWrite check.
    /// Use in serialize pass after scan pass has populated the map.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryGetEntryInt32(int id, out int slotIndex)
    {
        if (id == 0 || IdentityMapInt32 == null)
        {
            slotIndex = -1;
            return false;
        }
        return IdentityMapInt32.TryGetEntry(id, out slotIndex);
    }

    /// <summary>
    /// Lookup Int64 Id entry.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryGetEntryInt64(long id, out int slotIndex)
    {
        if (id == 0 || IdentityMapInt64 == null)
        {
            slotIndex = -1;
            return false;
        }
        return IdentityMapInt64.TryGetEntry(id, out slotIndex);
    }

    /// <summary>
    /// Lookup Guid Id entry.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool TryGetEntryGuid(Guid id, out int slotIndex)
    {
        if (id == Guid.Empty || IdentityMapGuid == null)
        {
            slotIndex = -1;
            return false;
        }
        return IdentityMapGuid.TryGetEntry(id, out slotIndex);
    }

    /// <summary>
    /// Get ref to InternEntry at slot for Int32 map.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref InternEntry GetEntryRefInt32(int slotIndex) => ref IdentityMapInt32!.GetValueRef(slotIndex);

    /// <summary>
    /// Get ref to InternEntry at slot for Int64 map.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref InternEntry GetEntryRefInt64(int slotIndex) => ref IdentityMapInt64!.GetValueRef(slotIndex);

    /// <summary>
    /// Get ref to InternEntry at slot for Guid map.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public ref InternEntry GetEntryRefGuid(int slotIndex) => ref IdentityMapGuid!.GetValueRef(slotIndex);

    #endregion
}
