using System.Reflection;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
namespace AyCode.Services.SignalRs;
///
/// Base interface for SignalR message parameters (metadata).
///
public interface ISignalParams { }
///
/// SignalR message metadata + optional method parameters.
/// Travels as a separate hub argument (AcBinary serialized).
/// Parameters and data are independent — both can be null or filled in any direction.
///
/// Parameter serialization follows the PostDataJson pattern:
/// - Wire format: Parameters (byte[]) — AcBinary fast-path
/// - Client: SetParameterValues(object[]) packs object[] → byte[][] → byte[]
/// - Server: GetParameterValues(ParameterInfo[]) unpacks byte[] → byte[][] → object[]
/// The protocol never sees byte[][] — only byte[].
///
[AcBinarySerializable]
public class SignalParams : ISignalParams
{
public SignalResponseStatus Status { get; set; }
public AcSerializerType DataSerializerType { get; set; }
///
/// Wire format: serialized byte[][] as a single byte[].
/// AcBinary handles this as byte[] fast-path (zero-copy potential).
/// Use SetParameterValues/GetParameterValues for typed access.
///
public byte[]? Parameters { get; set; }
///
/// Client sets true when requesting raw byte[] (e.g. DataSource populate/merge).
/// Server: reads this from client's SignalParams → serializes object → byte[] directly.
/// Protocol: reads this from response SignalParams → skips deserialization, returns raw byte[].
/// Result: single serialize (server) → single deserialize (consumer). No double ser/deser.
///
public bool IsRawBytesData { get; set; }
///
/// Cached deserialized byte[][] from Parameters.
///
private byte[][]? _parameterValues;
///
/// Client-side: packs object[] into Parameters (byte[]).
/// Each parameter is individually binary-serialized via ToBinary().
///
/// PERF: N× ToBinary() = N× context pool acquire/release + N× ArrayBinaryOutput allocation.
/// For many small primitives (int, bool) this overhead may exceed a single bulk serialization call.
/// Consider a batch fast-path (single context, write all params) if benchmarks confirm regression.
///
public void SetParameterValues(object[] parameters)
{
// N× pool roundtrip — see PERF note in summary
var paramBytes = new byte[parameters.Length][];
for (var i = 0; i < parameters.Length; i++)
{
// Pass explicit runtime type — parameters[i] is statically object, so the generic
// ToBinary() overload would infer T = object and emit an object-typed wire payload
// (instead of the concrete int/string/DTO type). Explicit null-handling avoids the
// null-conditional + fallback indirection and produces the Null marker directly.
var p = parameters[i];
paramBytes[i] = p == null ? [BinaryTypeCode.Null] : p.ToBinary(p.GetType());
}
_parameterValues = paramBytes;
Parameters = paramBytes.ToBinary();
}
///
/// Server-side: unpacks Parameters (byte[]) into typed object[].
/// Each parameter is deserialized with BinaryTo(targetType) from method signature.
/// Fills trailing optional parameters with defaults, throws for missing required ones.
/// Caches the intermediate byte[][] for repeated access.
///
/// NOTE: Currently AcBinary only. JSON parameter deserialization not supported
/// (would require dispatching on serializer type + AcJsonSerializer reference).
///
/// PERF: Non-generic BinaryTo(Type) uses ThreadLocal + ConcurrentDictionary type cache per call.
/// N parameters = N× pool roundtrip + N× type-dispatch lookup.
/// For many small primitives this may be slower than a single bulk deserialization.
/// Consider a batch fast-path (single context, read all params) if benchmarks confirm regression.
///
public object[]? GetParameterValues(ParameterInfo[] paramInfos)
{
if (paramInfos is not { Length: > 0 })
return null;
// Deserialize byte[] → byte[][] (cached)
_parameterValues ??= Parameters is { Length: > 0 } ? Parameters.BinaryTo() : null;
if (_parameterValues is null or { Length: 0 })
return null;
// N× pool roundtrip + type-dispatch — see PERF note in summary
var result = new object?[paramInfos.Length];
for (var i = 0; i < _parameterValues.Length && i < paramInfos.Length; i++)
result[i] = _parameterValues[i].BinaryTo(paramInfos[i].ParameterType);
// Fill trailing optional parameters with defaults
for (var i = _parameterValues.Length; i < paramInfos.Length; i++)
result[i] = paramInfos[i].HasDefaultValue ? paramInfos[i].DefaultValue : null;
return result;
}
}