// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Formats.Nrbf;
using System.Private.Windows.Core.Resources;

namespace System.Private.Windows.Core.BinaryFormat;

#pragma warning disable SYSLIB0050 // Type or member is obsolete

/// <summary>
///  Base class for deserializing <see cref="ClassRecord"/>s.
/// </summary>
internal abstract class ClassRecordDeserializer : ObjectRecordDeserializer
{
    private readonly bool _onlyAllowPrimitives;

    private protected ClassRecordDeserializer(ClassRecord classRecord, object @object, IDeserializer deserializer)
        : base(classRecord, deserializer)
    {
        Object = @object;

        // We want to be able to complete IObjectReference without having to evaluate their dependencies
        // for circular references. See ValidateNewMemberObjectValue below for more.
        _onlyAllowPrimitives = @object is IObjectReference;
    }

    [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
    internal static ObjectRecordDeserializer Create(ClassRecord classRecord, IDeserializer deserializer)
    {
        Type type = deserializer.TypeResolver.GetType(classRecord.TypeName);
        SerializationRecordId id = classRecord.Id;

        ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type);

        if (!type.IsSerializable && surrogate is null)
        {
            // SurrogateSelectors allow populating types that are not marked as serializable.
            throw new SerializationException(string.Format(SR.Serialization_TypeNotSerializable, type));
        }

        object @object =
#if NETCOREAPP
            RuntimeHelpers.GetUninitializedObject(type);
#else
            Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
#endif

        // Invoke any OnDeserializing methods.
        SerializationEvents.GetOnDeserializingForType(type, @object)?.Invoke(deserializer.Options.StreamingContext);

        ObjectRecordDeserializer? recordDeserializer;

        if (surrogate is not null || typeof(ISerializable).IsAssignableFrom(type))
        {
            recordDeserializer = new ClassRecordSerializationInfoDeserializer(classRecord, @object, type, surrogate, deserializer);
        }
        else
        {
            // Directly set fields for non-ISerializable types.
            recordDeserializer = new ClassRecordFieldInfoDeserializer(classRecord, @object, type, deserializer);
        }

        return recordDeserializer;
    }

    private protected override void ValidateNewMemberObjectValue(object value)
    {
        if (!_onlyAllowPrimitives)
        {
            return;
        }

        // The goal with this restriction is to know definitively that we can complete the contianing object when we
        // finish with it's members, even if it is going to be replaced with another instance (as IObjectReference does).
        // If there are no reference types we know that there is no way for references to this object getting it in an
        // in an unconverted state or converted with uncompleted state (due to some direct or indirect reference from
        // this object).
        //
        // If we wanted support to be fully open-ended we would have queue completion along with pending SerializationInfo
        // objects to rehydrate in the proper order (depth-first) and reject any case where the object is involved in
        // a cycle.

        Type type = value.GetType();
        if (type.IsArray)
        {
            type = type.GetElementType()!;
        }

        bool primitive = type.IsPrimitive || type.IsEnum || type == typeof(string);
        if (!primitive)
        {
            throw new SerializationException(string.Format(SR.Serialization_IObjectReferenceOnlyPrimivite, type));
        }
    }
}

#pragma warning restore SYSLIB0050 // Type or member is obsolete

