using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using AyCode.Core.Interfaces;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using static AyCode.Core.Helpers.JsonUtilities;
using static AyCode.Core.Serializers.Binaries.AcBinaryDeserializer;

namespace AyCode.Core.Extensions;

/// <summary>
/// High-performance Base62 encoder for compact $id/$ref values.
/// </summary>
internal static class Base62
{
    private const string Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static string Encode(long value)
    {
        if (value == 0) return "0";
        
        var isNegative = value < 0;
        if (isNegative) value = -value;
        
        Span<char> buffer = stackalloc char[16];
        var index = buffer.Length;
        
        while (value > 0)
        {
            buffer[--index] = Alphabet[(int)(value % 62)];
            value /= 62;
        }
        
        if (isNegative) buffer[--index] = '-';
        
        return new string(buffer[index..]);
    }
}

/// <summary>
/// High-performance hybrid reference resolver using Base62 encoded semantic IDs.
/// </summary>
public class HybridReferenceResolver : IReferenceResolver
{
    internal Dictionary<string, object>? _idToObject;
    internal Dictionary<object, string>? _objectToId;
    internal HashSet<string>? _referencedIds;
    
    private int _nextNumericId = 1;
    private static readonly ConcurrentDictionary<Type, Func<object, object?>> IdGetterCache = new();

    public bool IsForMerge { get; }
    private readonly int _estimatedObjectCount;

    public HybridReferenceResolver(bool isForMerge = false, int estimatedObjectCount = 64)
    {
        IsForMerge = isForMerge;
        _estimatedObjectCount = estimatedObjectCount;
    }

    internal HashSet<string> ReferencedIds => _referencedIds ??= 
        new HashSet<string>(_estimatedObjectCount / 4, StringComparer.Ordinal);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private Dictionary<string, object> GetIdToObject() => 
        _idToObject ??= new Dictionary<string, object>(_estimatedObjectCount, StringComparer.Ordinal);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private Dictionary<object, string> GetObjectToId() => 
        _objectToId ??= new Dictionary<object, string>(_estimatedObjectCount, AyCode.Core.Serializers.ReferenceEqualityComparer.Instance);

    public void AddReference(object context, string reference, object value)
    {
        GetIdToObject()[reference] = value;
        GetObjectToId()[value] = reference;
    }

    public string GetReference(object context, object value)
    {
        var objectToId = GetObjectToId();
        if (objectToId.TryGetValue(value, out var existingId))
        {
            if (!IsForMerge) ReferencedIds.Add(existingId);
            return existingId;
        }

        var type = value.GetType();
        var (isId, idType) = GetIdInfo(type);

        string newRef;
        if (isId && idType != null)
        {
            var idGetter = GetOrCreateIdGetter(type);
            var idValue = idGetter(value);

            if (idValue != null && !IsDefaultValue(idValue, idType))
            {
                var typeId = TypeCache.GetTypeId(type);
                var objectIdAsLong = TypeCache.IdToLong(idValue);
                var semanticId = TypeCache.CreateSemanticId(typeId, objectIdAsLong);
                newRef = Base62.Encode(semanticId);
            }
            else
                newRef = Base62.Encode(-_nextNumericId++);
        }
        else
            newRef = Base62.Encode(-_nextNumericId++);

        GetIdToObject()[newRef] = value;
        objectToId[value] = newRef;

        return newRef;
    }

    public bool IsReferenced(object context, object value) => _objectToId?.ContainsKey(value) ?? false;

    public object ResolveReference(object context, string reference) =>
        _idToObject != null && _idToObject.TryGetValue(reference, out var value) ? value : null!;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static Func<object, object?> GetOrCreateIdGetter(Type type) =>
        IdGetterCache.GetOrAdd(type, static t =>
        {
            var prop = t.GetProperty("Id");
            if (prop == null) return static _ => null;
            var getMethod = prop.GetGetMethod();
            if (getMethod == null) return static _ => null;
            return obj => getMethod.Invoke(obj, null);
        });
}

internal static class JsonReferencePostProcessor
{
    private const string IdMarker = "\"$id\"";

    public static string RemoveUnreferencedIds(string json, HashSet<string>? referencedIds)
    {
        if (!json.Contains(IdMarker)) return json;
        return referencedIds == null || referencedIds.Count == 0 
            ? RemoveAllIdsSpan(json) 
            : RemoveUnreferencedIdsSpan(json, referencedIds);
    }

    private static string RemoveAllIdsSpan(string json)
    {
        var sb = new StringBuilder(json.Length);
        var lastCopyEnd = 0;
        var searchStart = 0;
        
        while (searchStart < json.Length)
        {
            var idIndex = json.IndexOf(IdMarker, searchStart, StringComparison.Ordinal);
            if (idIndex < 0) break;
            
            if (idIndex > lastCopyEnd) sb.Append(json, lastCopyEnd, idIndex - lastCopyEnd);
            
            var endIndex = SkipIdEntry(json, idIndex);
            lastCopyEnd = endIndex;
            searchStart = endIndex;
        }
        
        if (lastCopyEnd < json.Length) sb.Append(json, lastCopyEnd, json.Length - lastCopyEnd);
        
        return sb.Length == json.Length ? json : sb.ToString();
    }

    private static string RemoveUnreferencedIdsSpan(string json, HashSet<string> referencedIds)
    {
        var sb = new StringBuilder(json.Length);
        var lastCopyEnd = 0;
        var searchStart = 0;
        
        while (searchStart < json.Length)
        {
            var idIndex = json.IndexOf(IdMarker, searchStart, StringComparison.Ordinal);
            if (idIndex < 0) break;
            
            var valueStart = idIndex + IdMarker.Length;
            while (valueStart < json.Length && (json[valueStart] == ' ' || json[valueStart] == ':'))
                valueStart++;
            
            string? idValue = null;
            var valueEnd = valueStart;
            
            if (valueStart < json.Length && json[valueStart] == '"')
            {
                valueStart++;
                valueEnd = valueStart;
                while (valueEnd < json.Length && json[valueEnd] != '"') valueEnd++;
                idValue = json.Substring(valueStart, valueEnd - valueStart);
                valueEnd++;
            }
            
            while (valueEnd < json.Length && (json[valueEnd] == ' ' || json[valueEnd] == ','))
                valueEnd++;
            
            if (idValue != null && referencedIds.Contains(idValue))
                searchStart = valueEnd;
            else
            {
                if (idIndex > lastCopyEnd) sb.Append(json, lastCopyEnd, idIndex - lastCopyEnd);
                lastCopyEnd = valueEnd;
                searchStart = valueEnd;
            }
        }
        
        if (lastCopyEnd == 0) return json;
        if (lastCopyEnd < json.Length) sb.Append(json, lastCopyEnd, json.Length - lastCopyEnd);
        
        return sb.ToString();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int SkipIdEntry(string json, int idIndex)
    {
        var pos = idIndex + IdMarker.Length;
        while (pos < json.Length && (json[pos] == ' ' || json[pos] == ':')) pos++;
        if (pos < json.Length && json[pos] == '"')
        {
            pos++;
            while (pos < json.Length && json[pos] != '"') pos++;
            if (pos < json.Length) pos++;
        }
        while (pos < json.Length && (json[pos] == ' ' || json[pos] == ',')) pos++;
        return pos;
    }

    public static HashSet<string> CollectReferencedIds(string json)
    {
        const string refMarker = "\"$ref\"";
        var result = new HashSet<string>(StringComparer.Ordinal);
        var searchStart = 0;
        
        while (searchStart < json.Length)
        {
            var refIndex = json.IndexOf(refMarker, searchStart, StringComparison.Ordinal);
            if (refIndex < 0) break;
            
            var valueStart = refIndex + refMarker.Length;
            while (valueStart < json.Length && (json[valueStart] == ' ' || json[valueStart] == ':'))
                valueStart++;
            
            if (valueStart < json.Length && json[valueStart] == '"')
            {
                valueStart++;
                var valueEnd = valueStart;
                while (valueEnd < json.Length && json[valueEnd] != '"') valueEnd++;
                if (valueEnd > valueStart) result.Add(json.Substring(valueStart, valueEnd - valueStart));
                searchStart = valueEnd + 1;
            }
            else
                searchStart = valueStart;
        }
        
        return result;
    }
}

internal sealed class PooledStringWriter : StringWriter
{
    private static readonly ObjectPool<StringBuilder> StringBuilderPool = 
        new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy { InitialCapacity = 4096, MaximumRetainedCapacity = 4 * 1024 * 1024 });
    
    private readonly StringBuilder _pooledBuilder;
    private bool _disposed;

    private PooledStringWriter(StringBuilder sb) : base(sb) => _pooledBuilder = sb;

    public static PooledStringWriter Rent()
    {
        var sb = StringBuilderPool.Get();
        sb.Clear();
        return new PooledStringWriter(sb);
    }

    protected override void Dispose(bool disposing)
    {
        if (!_disposed) { _disposed = true; StringBuilderPool.Return(_pooledBuilder); }
        base.Dispose(disposing);
    }
}

internal interface ObjectPool<T> where T : class { T Get(); void Return(T obj); }

internal sealed class DefaultObjectPool<T> : ObjectPool<T> where T : class
{
    [ThreadStatic] private static T? _threadLocalItem;
    private readonly ConcurrentQueue<T> _pool = new();
    private readonly IPooledObjectPolicy<T> _policy;
    private const int MaxPoolSize = 8;

    public DefaultObjectPool(IPooledObjectPolicy<T> policy) => _policy = policy;

    public T Get()
    {
        var item = _threadLocalItem;
        if (item != null) { _threadLocalItem = null; return item; }
        return _pool.TryDequeue(out item) ? item : _policy.Create();
    }

    public void Return(T obj)
    {
        if (!_policy.Return(obj)) return;
        if (_threadLocalItem == null) { _threadLocalItem = obj; return; }
        if (_pool.Count < MaxPoolSize) _pool.Enqueue(obj);
    }
}

internal interface IPooledObjectPolicy<T> { T Create(); bool Return(T obj); }

internal sealed class StringBuilderPooledObjectPolicy : IPooledObjectPolicy<StringBuilder>
{
    public int InitialCapacity { get; init; } = 256;
    public int MaximumRetainedCapacity { get; init; } = 4 * 1024 * 1024;
    public StringBuilder Create() => new(InitialCapacity);
    public bool Return(StringBuilder obj) { if (obj.Capacity > MaximumRetainedCapacity) return false; obj.Clear(); return true; }
}

public static class SerializeObjectExtensions
{
    private static readonly UnifiedMergeContractResolver SharedContractResolver = new();
    private static readonly Dictionary<object, object> EmptyContextDict = new();

    public static JsonSerializerSettings Options => new()
    {
        ContractResolver = SharedContractResolver,
        Context = new StreamingContext(StreamingContextStates.All, EmptyContextDict),
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
        ReferenceResolverProvider = () => new HybridReferenceResolver(),
        NullValueHandling = NullValueHandling.Ignore,
        MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead,
        Formatting = Formatting.None,
    };

    #region JSON Serialization

    /// <summary>
    /// Serialize object to JSON string with default options.
    /// </summary>
    public static string ToJson<T>(this T source) => AcJsonSerializer.Serialize(source);

    /// <summary>
    /// Serialize object to JSON string with specified options.
    /// </summary>
    public static string ToJson<T>(this T source, AcJsonSerializerOptions options) 
        => AcJsonSerializer.Serialize(source, options);

    public static string ToJson<T>(this IQueryable<T> source) where T : class, IAcSerializableToJson 
        => AcJsonSerializer.Serialize(source);

    public static string ToJson<T>(this IQueryable<T> source, AcJsonSerializerOptions options) where T : class, IAcSerializableToJson 
        => AcJsonSerializer.Serialize(source, options);
    
    public static string ToJson<T>(this IEnumerable<T> source) where T : class, IAcSerializableToJson 
        => AcJsonSerializer.Serialize(source);

    public static string ToJson<T>(this IEnumerable<T> source, AcJsonSerializerOptions options) where T : class, IAcSerializableToJson 
        => AcJsonSerializer.Serialize(source, options);

    /// <summary>
    /// Deserialize JSON to object with default options.
    /// </summary>
    public static T? JsonTo<T>(this string json)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.Deserialize<T>(json);
    }

    /// <summary>
    /// Deserialize JSON to object with specified options.
    /// </summary>
    public static T? JsonTo<T>(this string json, AcJsonSerializerOptions options)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.Deserialize<T>(json, options);
    }

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to object with default options.
    /// Zero-allocation path - no string conversion needed.
    /// </summary>
    public static T? JsonTo<T>(this ReadOnlySpan<byte> utf8Json)
        => AcJsonDeserializer.Deserialize<T>(utf8Json);

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to object with specified options.
    /// Zero-allocation path - no string conversion needed.
    /// </summary>
    public static T? JsonTo<T>(this ReadOnlySpan<byte> utf8Json, AcJsonSerializerOptions options)
        => AcJsonDeserializer.Deserialize<T>(utf8Json, options);

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to object with default options.
    /// Zero-allocation path - no string conversion needed.
    /// </summary>
    public static T? JsonTo<T>(this byte[] utf8Json)
        => AcJsonDeserializer.Deserialize<T>(utf8Json.AsSpan());

    /// <summary>
    /// Deserialize UTF-8 encoded JSON bytes to object with specified options.
    /// Zero-allocation path - no string conversion needed.
    /// </summary>
    public static T? JsonTo<T>(this byte[] utf8Json, AcJsonSerializerOptions options)
        => AcJsonDeserializer.Deserialize<T>(utf8Json.AsSpan(), options);

    /// <summary>
    /// Deserialize JSON to specified type with default options.
    /// </summary>
    public static object? JsonTo(this string json, Type toType)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.Deserialize(json, toType);
    }

    /// <summary>
    /// Deserialize JSON to specified type with specified options.
    /// </summary>
    public static object? JsonTo(this string json, Type toType, AcJsonSerializerOptions options)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.Deserialize(json, toType, options);
    }

    /// <summary>
    /// Populate existing object from JSON with default options.
    /// </summary>
    public static void JsonTo(this string json, object target)
    {
        json = UnwrapJsonString(json);
        AcJsonDeserializer.Populate(json, target);
    }

    /// <summary>
    /// Populate existing object from JSON with specified options.
    /// </summary>
    public static void JsonTo(this string json, object target, AcJsonSerializerOptions options)
    {
        json = UnwrapJsonString(json);
        AcJsonDeserializer.Populate(json, target, options);
    }

    /// <summary>
    /// Create a deserialize chain that parses JSON once and allows multiple deserializations.
    /// Efficient for deserializing the same JSON to multiple different types.
    /// Use with 'using' statement or call Dispose() when done.
    /// </summary>
    public static IDeserializeChain<T> JsonToChain<T>(this string json)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.CreateDeserializeChain<T>(json);
    }

    /// <summary>
    /// Create a deserialize chain with options.
    /// </summary>
    public static IDeserializeChain<T> JsonToChain<T>(this string json, AcJsonSerializerOptions options)
    {
        json = UnwrapJsonString(json);
        return AcJsonDeserializer.CreateDeserializeChain<T>(json, options);
    }

    /// <summary>
    /// Create a populate chain that parses JSON once and allows populating multiple objects.
    /// Efficient for populating multiple objects from the same JSON source.
    /// Use with 'using' statement or call Dispose() when done.
    /// </summary>
    public static IDeserializeChain<object> JsonToChain(this string json, object target)
    {
        json = UnwrapJsonString(json);
        var chain = AcJsonDeserializer.CreateDeserializeChain<object>(json);
        chain.ThenPopulate(target);
        return chain;
    }

    /// <summary>
    /// Create a populate chain with options.
    /// </summary>
    public static IDeserializeChain<object> JsonToChain(this string json, object target, AcJsonSerializerOptions options)
    {
        json = UnwrapJsonString(json);
        var chain = AcJsonDeserializer.CreateDeserializeChain<object>(json, options);
        chain.ThenPopulate(target);
        return chain;
    }

    #endregion

    #region Any (JSON or Binary based on options)

    public static object ToAny<T>(this T source, AcSerializerOptions options)
    {
        if (options.SerializerType == AcSerializerType.Json) return ToJson(source, (AcJsonSerializerOptions)options);
        return ToBinary(source, (AcBinarySerializerOptions)options);
    }

    /// <summary>
    /// Deserialize data (JSON string or binary byte[]) to object based on options.
    /// </summary>
    public static T? AnyTo<T>(this object data, AcSerializerOptions options)
    {
        if (options.SerializerType == AcSerializerType.Json)
            return ((string)data).JsonTo<T>((AcJsonSerializerOptions)options);
        return ((byte[])data).BinaryTo<T>();
    }

    /// <summary>
    /// Deserialize data to specified type based on options.
    /// </summary>
    public static object? AnyTo(this object data, Type targetType, AcSerializerOptions options)
    {
        if (options.SerializerType == AcSerializerType.Json)
            return ((string)data).JsonTo(targetType, (AcJsonSerializerOptions)options);
        return ((byte[])data).BinaryTo(targetType);
    }

    /// <summary>
    /// Populate existing object from data based on options.
    /// </summary>
    public static void AnyTo<T>(this object data, T target, AcSerializerOptions options) where T : class
    {
        if (options.SerializerType == AcSerializerType.Json)
            ((string)data).JsonTo(target, (AcJsonSerializerOptions)options);
        else
            ((byte[])data).BinaryTo(target);
    }

    /// <summary>
    /// Populate existing object with merge semantics based on options.
    /// </summary>
    public static void AnyToMerge<T>(this object data, T target, AcSerializerOptions options) where T : class
    {
        if (options.SerializerType == AcSerializerType.Json)
            ((string)data).JsonTo(target, (AcJsonSerializerOptions)options);
        else
            ((byte[])data).BinaryToMerge(target);
    }

    #endregion

    #region Binary Serialization

    /// <summary>
    /// Serialize object to binary byte array with default options.
    /// </summary>
    public static byte[] ToBinary<T>(this T source) => AcBinarySerializer.Serialize(source);

    /// <summary>
    /// Serialize object to binary byte array with specified options.
    /// </summary>
    public static byte[] ToBinary<T>(this T source, AcBinarySerializerOptions options)
        => AcBinarySerializer.Serialize(source, options);

    /// <summary>
    /// Serialize object directly to an IBufferWriter for zero-copy scenarios.
    /// </summary>
    public static void ToBinary<T>(this T source, IBufferWriter<byte> writer)
        => AcBinarySerializer.Serialize(source, writer, AcBinarySerializerOptions.Default);

    /// <summary>
    /// Serialize object directly to an IBufferWriter with specified options.
    /// </summary>
    public static void ToBinary<T>(this T source, IBufferWriter<byte> writer, AcBinarySerializerOptions options)
        => AcBinarySerializer.Serialize(source, writer, options);

    /// <summary>
    /// Serialize object to binary byte array with explicit runtime type. Use this overload at
    /// heterogeneous <c>object?</c> call sites where the generic <see cref="ToBinary{T}(T)"/>
    /// would infer <c>T = object</c> and emit an object-typed wire payload instead of the concrete
    /// runtime type's encoding. Typical use: <c>value.ToBinary(value.GetType())</c> or
    /// <c>value.ToBinary(paramInfo.ParameterType)</c>.
    /// </summary>
    public static byte[] ToBinary(this object? source, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type)
        => AcBinarySerializer.Serialize(source, type, AcBinarySerializerOptions.Default);

    /// <summary>
    /// Serialize object to binary byte array with explicit runtime type and specified options.
    /// See <see cref="ToBinary(object?, Type)"/> for use-case rationale.
    /// </summary>
    public static byte[] ToBinary(this object? source, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, AcBinarySerializerOptions options)
        => AcBinarySerializer.Serialize(source, type, options);

    /// <summary>
    /// Serialize object directly to an IBufferWriter with explicit runtime type. Zero-copy variant.
    /// See <see cref="ToBinary(object?, Type)"/> for use-case rationale.
    /// </summary>
    public static void ToBinary(this object? source, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, IBufferWriter<byte> writer)
        => AcBinarySerializer.Serialize(source, type, writer, AcBinarySerializerOptions.Default);

    /// <summary>
    /// Serialize object directly to an IBufferWriter with explicit runtime type and specified options.
    /// See <see cref="ToBinary(object?, Type)"/> for use-case rationale.
    /// </summary>
    public static void ToBinary(this object? source, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, IBufferWriter<byte> writer, AcBinarySerializerOptions options)
        => AcBinarySerializer.Serialize(source, type, writer, options);

    /// <summary>
    /// Get the serialized binary size without allocating the final array.
    /// </summary>
    public static int GetBinarySize<T>(this T source)
        => AcBinarySerializer.GetSerializedSize(source, AcBinarySerializerOptions.Default);

    /// <summary>
    /// Get the serialized binary size with specified options.
    /// </summary>
    public static int GetBinarySize<T>(this T source, AcBinarySerializerOptions options)
        => AcBinarySerializer.GetSerializedSize(source, options);

    /// <summary>
    /// Deserialize binary data to object.
    /// </summary>
    public static T? BinaryTo<T>(this byte[] data)
        => AcBinaryDeserializer.Deserialize<T>(data);

    /// <summary>
    /// Deserialize binary data to specified type.
    /// </summary>
    public static object? BinaryTo(this byte[] data, Type targetType)
        => AcBinaryDeserializer.Deserialize(data, targetType);

    /// <summary>
    /// Populate existing object from binary data.
    /// </summary>
    public static void BinaryTo<T>(this byte[] data, T target) where T : class
        => AcBinaryDeserializer.Populate(data, target);

    /// <summary>
    /// Populate existing object from binary data with merge semantics for IId collections.
    /// </summary>
    public static void BinaryToMerge<T>(this byte[] data, T target) where T : class
        => AcBinaryDeserializer.PopulateMerge(data, target);

    /// <summary>
    /// Create a deserialize chain that parses binary data once and allows multiple deserializations.
    /// Efficient for deserializing the same binary to multiple different types.
    /// Use with 'using' statement or call Dispose() when done.
    /// </summary>
    public static IDeserializeChain<T> BinaryToChain<T>(this byte[] data)
        => AcBinaryDeserializer.CreateDeserializeChain<T>(data);

    /// <summary>
    /// Create a deserialize chain with options.
    /// </summary>
    public static IDeserializeChain<T> BinaryToChain<T>(this byte[] data, AcBinarySerializerOptions options)
        => AcBinaryDeserializer.CreateDeserializeChain<T>(data, options);

    /// <summary>
    /// Create a populate chain that parses binary data once and allows populating multiple objects.
    /// Efficient for populating multiple objects from the same binary source.
    /// Use with 'using' statement or call Dispose() when done.
    /// </summary>
    public static IDeserializeChain<T> BinaryToChain<T>(this byte[] data, T target) where T : class
    {
        var chain = AcBinaryDeserializer.CreateDeserializeChain<T>(data);
        chain.ThenPopulate(target);
        return chain;
    }

    /// <summary>
    /// Create a populate chain with options.
    /// </summary>
    public static IDeserializeChain<T> BinaryToChain<T>(this byte[] data, T target, AcBinarySerializerOptions options) where T : class
    {
        var chain = AcBinaryDeserializer.CreateDeserializeChain<T>(data, options);
        chain.ThenPopulate(target);
        return chain;
    }

    #endregion

    #region Clone and Copy (Binary-based, zero intermediate allocation)

    /// <summary>
    /// Clone object via binary serialization. Uses the byte[]-output overload (pool-context internally,
    /// 1 final allocation) — lighter than the IBufferWriter path (2 allocations: ArrayBufferWriter + buffer).
    /// </summary>
    public static TDestination? CloneTo<TDestination>(this object? src) where TDestination : class
    {
        if (src == null) return null;

        // Pass explicit runtime type — src is statically object?, so the generic Serialize<T> overload
        // would infer T = object and emit an object-typed wire payload, losing all concrete DTO properties.
        var bytes = AcBinarySerializer.Serialize(src, src.GetType(), AcBinarySerializerOptions.Default);
        return AcBinaryDeserializer.Deserialize<TDestination>(bytes);
    }

    /// <summary>
    /// Copy object properties to target via binary serialization. Uses the byte[]-output overload
    /// (pool-context internally, 1 final allocation) — lighter than the IBufferWriter path.
    /// </summary>
    public static void CopyTo(this object? src, object target)
    {
        if (src == null) return;

        // Pass explicit runtime type — src is statically object?, so the generic Serialize<T> overload
        // would infer T = object and emit an object-typed wire payload, losing all concrete DTO properties.
        var bytes = AcBinarySerializer.Serialize(src, src.GetType(), AcBinarySerializerOptions.Default);
        AcBinaryDeserializer.Populate(bytes, 0, bytes.Length, target);
    }

    #endregion
}

public class IgnoreAndRenamePropertySerializerContractResolver : DefaultContractResolver
{
    private readonly Dictionary<Type, HashSet<string>> _ignores = new();
    private readonly Dictionary<Type, HashSet<string>> _includes = new();
    private readonly Dictionary<Type, Dictionary<string, string>> _renames = new();

    public void IgnoreProperty(Type type, params string[] jsonPropertyNames)
    {
        if (!_ignores.TryGetValue(type, out var set)) { set = new HashSet<string>(StringComparer.Ordinal); _ignores[type] = set; }
        foreach (var prop in jsonPropertyNames) set.Add(prop);
    }

    public void IncludesProperty(Type type, params string[] jsonPropertyNames)
    {
        if (!_includes.TryGetValue(type, out var set)) { set = new HashSet<string>(StringComparer.Ordinal); _includes[type] = set; }
        foreach (var prop in jsonPropertyNames) set.Add(prop);
    }

    public void RenameProperty(Type type, string propertyName, string newJsonPropertyName)
    {
        if (!_renames.TryGetValue(type, out var dict)) { dict = new Dictionary<string, string>(StringComparer.Ordinal); _renames[type] = dict; }
        dict[propertyName] = newJsonPropertyName;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (IsIgnored(property.DeclaringType, property.PropertyName) || !IsIncluded(property.DeclaringType, property.PropertyName))
        { property.ShouldSerialize = _ => false; property.Ignored = true; }
        if (IsRenamed(property.DeclaringType, property.PropertyName, out var newName)) property.PropertyName = newName;
        return property;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool IsIgnored(Type? type, string? name) => type != null && name != null && _ignores.TryGetValue(type, out var set) && set.Contains(name);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool IsIncluded(Type? type, string? name) => _includes.Count == 0 || (type != null && name != null && _includes.TryGetValue(type, out var set) && set.Contains(name));

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private bool IsRenamed(Type? type, string? name, out string? newName)
    {
        if (type != null && name != null && _renames.TryGetValue(type, out var renames) && renames.TryGetValue(name, out newName)) return true;
        newName = null; return false;
    }
}
