using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace AyCode.Core.Serializers;

/// <summary>
/// Base class for all serializer contexts.
/// Provides GetWrapper for type metadata access with per-context tracking state.
/// GlobalMetadataCache stores metadata (thread-safe), wrappers store per-context tracking.
/// </summary>
/// <typeparam name="TMetadata">The concrete metadata type.</typeparam>
/// <typeparam name="TOptions">The concrete options type.</typeparam>
public abstract class AcSerializerContextBase<TMetadata, TOptions> 
    where TMetadata : TypeMetadataBase
    where TOptions : AcSerializerOptions
{
    /// <summary>
    /// The options used for this context. Set during Reset.
    /// </summary>
    public TOptions Options { get; private set; } = null!;

    public byte _maxDepth;

    public ReferenceHandlingMode ReferenceHandling;

    /// <summary>
    /// Pre-computed: ReferenceHandling == OnlyId (not All). When true, only IId types are tracked.
    /// </summary>
    private bool _hasIdHandling;

    /// <summary>
    /// Pre-computed: ReferenceHandling != None. Avoids Options field chain per call.
    /// </summary>
    internal bool HasRefHandling;
    /// <summary>
    /// Pre-computed: ReferenceHandling == All. When true, all reference types are tracked.
    /// </summary>
    internal bool HasAllRefHandling;

    public bool ThrowOnCircularReference => Options.ThrowOnCircularReference;
    /// <summary>
    /// Global shared cache for metadata (thread-safe, shared across all contexts).
    /// Generic specialization ensures separate cache per TMetadata type.
    /// </summary>
    private static readonly ConcurrentDictionary<Type, TMetadata> GlobalMetadataCache = new();

    /// <summary>
    /// Per-context wrappers containing metadata + tracking state.
    /// </summary>
    private readonly Dictionary<Type, TypeMetadataWrapper<TMetadata>> _wrappers = new();

    /// <summary>
    /// Slot-indexed wrapper cache. Shared between SGen types (RuntimeSlotCount+) and
    /// runtime polymorphic type cache (0..RuntimeSlotCount-1).
    /// Avoids dictionary lookup — direct array access (~1-2ns vs ~15-25ns).
    /// Not cleared on pool return: wrapper references are stable across serialization calls.
    /// </summary>
    private TypeMetadataWrapper<TMetadata>?[]? _wrapperSlots;

    /// <summary>
    /// Custom delegate carrying DAMs annotation on its <see cref="Type"/> parameter — required for
    /// trimmer/AOT correctness. Standard <c>Func&lt;Type, TMetadata&gt;</c> drops DAMs at the delegate
    /// signature, breaking the propagation chain from <see cref="GetWrapper"/> down through
    /// <c>TypeMetadataBase</c>'s <c>CreateCompiledConstructor</c>. This delegate type preserves it.
    /// </summary>
    public delegate TMetadata MetadataFactoryDelegate(
        [DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type);

    /// <summary>
    /// Factory function to create metadata. Implemented by derived class.
    /// </summary>
    protected abstract MetadataFactoryDelegate MetadataFactory { get; }

    //[MethodImpl(MethodImplOptions.AggressiveInlining)]
    //public bool UseTypeReferenceHandling(Type type)
    //{
    //    var wrapper = GetWrapper(type);
    //    return UseTypeReferenceHandling(wrapper.Metadata);
    //}

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool UseTypeReferenceHandling(TMetadata metaData)
    {
        return HasRefHandling && (metaData.IsIId || !_hasIdHandling);
    }

    #region Wrapper Access

    /// <summary>
    /// Gets or creates a wrapper for the specified type.
    /// The wrapper contains metadata (from GlobalMetadataCache) + per-context tracking state.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public TypeMetadataWrapper<TMetadata> GetWrapper([DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type)
    {
        if (_wrappers.TryGetValue(type, out var wrapper))
            return wrapper;

        return GetWrapperSlow(type);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private TypeMetadataWrapper<TMetadata> GetWrapperSlow([DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type)
    {
        // Get metadata from global cache (thread-safe).
        // ConcurrentDictionary.GetOrAdd's Func<TKey, TValue> overload drops DAMs at the delegate
        // signature — but our MetadataFactoryDelegate type carries the annotation, and the local
        // `type` variable still has DAMs in scope here, so the trimmer accepts the call without warning.
        var metadata = GlobalMetadataCache.GetOrAdd(type, MetadataFactoryShim);

        // Create wrapper with metadata + tracking state (per-context)
        var wrapper = new TypeMetadataWrapper<TMetadata>(metadata);
        _wrappers[type] = wrapper;
        return wrapper;
    }

    /// <summary>
    /// Shim that bridges the standard <c>Func&lt;Type, TMetadata&gt;</c> required by
    /// <see cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey, Func{TKey, TValue})"/> and our
    /// DAMs-aware <see cref="MetadataFactoryDelegate"/>. The trimmer cannot prove the conversion
    /// preserves DAMs because <c>Func</c>'s parameter has no annotation — but in practice the
    /// metadata factory only ever receives types that flowed through <see cref="GetWrapper"/>'s
    /// DAMs-annotated parameter, so the runtime contract is preserved. Suppression is bounded.
    /// </summary>
    [UnconditionalSuppressMessage("Trimming", "IL2067",
        Justification = "Func<Type,T> drops DAMs at the delegate signature. The metadata factory is only "
                      + "invoked with types that originated from a DAMs-annotated GetWrapper parameter; "
                      + "the runtime contract holds even though static analysis can't trace it through Func.")]
    private TMetadata MetadataFactoryShim(Type type) => MetadataFactory(type);

    /// <summary>
    /// Gets or creates a wrapper for the specified type using a slot index.
    /// Slot checked first (array access ~1-2ns), falls back to dictionary if slot empty.
    /// Used by both SGen (compile-time slot) and runtime polymorphic cache (0..RuntimeSlotCount-1).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public TypeMetadataWrapper<TMetadata> GetWrapper(
        [DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] Type type,
        int slotIndex)
    {
        var slots = _wrapperSlots!;
        var wrapper = slots[slotIndex];
        if (wrapper != null)
            return wrapper;

        wrapper = GetWrapper(type);
        slots[slotIndex] = wrapper;
        return wrapper;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected TypeMetadataWrapper<TMetadata>? GetWrapperSlot(int slotIndex)
        => _wrapperSlots![slotIndex];

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected void SetWrapperSlot(int slotIndex, TypeMetadataWrapper<TMetadata> wrapper)
        => _wrapperSlots![slotIndex] = wrapper;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected void ClearWrapperSlots(int count)
        => Array.Clear(_wrapperSlots!, 0, count);

    /// <summary>
    /// Pre-allocates the wrapper slot array with the known total slot count.
    /// Called once from derived context constructor after all AllocateWrapperSlot() calls have completed.
    /// </summary>
    protected void InitializeWrapperSlots(int count)
    {
        if (count > 0)
            _wrapperSlots = new TypeMetadataWrapper<TMetadata>?[count];
    }

    #endregion

    #region Wrapper Iteration (for footer writing)

    /// <summary>
    /// Iterates all wrappers and collects InternEntry data for ID tracking footer.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public Dictionary<Type, TypeMetadataWrapper<TMetadata>>.ValueCollection GetWrappers() => _wrappers.Values;

    /// <summary>
    /// Returns wrappers as span for direct indexed access without allocation.
    /// Uses CollectionsMarshal for zero-copy access to dictionary values.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int GetWrapperCount() => _wrappers.Count;

    #endregion

    #region Reset

    /// <summary>
    /// Resets context for new operation. Sets options only.
    /// </summary>
    public virtual void Reset(TOptions options)
    {
        Options = options;

        ReferenceHandling = options.ReferenceHandling;

        _hasIdHandling = ReferenceHandling == ReferenceHandlingMode.OnlyId;
        HasRefHandling = ReferenceHandling != ReferenceHandlingMode.None;
        HasAllRefHandling = ReferenceHandling == ReferenceHandlingMode.All;
        
        _maxDepth = Options.MaxDepth;
    }

    /// <summary>
    /// Clears tracking state for pool return. Returns IdentityMap arrays to pool.
    /// </summary>
    public virtual void Clear()
    {
        //System.Diagnostics.Debug.WriteLine($"Wrappers count: {_wrappers.Count}");
        //Console.WriteLine($"Wrappers count: {_wrappers.Count}");

        foreach (var wrapper in _wrappers.Values)
        {
            wrapper.ResetTracking(Options.UseAsync);
        }
    }

    #endregion
}
