// 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;

namespace System.Private.Windows.Core.BinaryFormat;

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

internal abstract partial class ObjectRecordDeserializer
{
    protected static FormatterConverter DefaultConverter { get; } = new();

    // Used to indicate that the value is missing from the deserialized objects.
    private protected static object s_missingValueSentinel = new();

    internal SerializationRecord ObjectRecord { get; }

    [AllowNull]
    internal object Object { get; private protected set; }

    private protected IDeserializer Deserializer { get; }

    private protected ObjectRecordDeserializer(SerializationRecord objectRecord, IDeserializer deserializer)
    {
        Deserializer = deserializer;
        ObjectRecord = objectRecord;
    }

    /// <summary>
    ///  Continue parsing.
    /// </summary>
    /// <returns>The id that is necessary to complete parsing or <see cref="SerializationRecordId"/> default value if complete.</returns>
    internal abstract SerializationRecordId Continue();

    /// <summary>
    ///  Gets the actual object for a member value primitive or record. Returns <see cref="s_missingValueSentinel"/> if
    ///  the object record has not been encountered yet.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private protected (object? value, SerializationRecordId id) UnwrapMemberValue(object? memberValue)
    {
        if (memberValue is null) // NrbfDecoder does not return NullRecord, just null
        {
            return (null, default);
        }
        else if (memberValue is not SerializationRecord serializationRecord) // a primitive value
        {
            return (memberValue, default(SerializationRecordId));
        }
        else if (serializationRecord.RecordType is SerializationRecordType.BinaryObjectString)
        {
            PrimitiveTypeRecord<string> stringRecord = (PrimitiveTypeRecord<string>)serializationRecord;
            return (stringRecord.Value, stringRecord.Id);
        }
        else if (serializationRecord.RecordType is SerializationRecordType.MemberPrimitiveTyped)
        {
            return (((PrimitiveTypeRecord)serializationRecord).Value, default(SerializationRecordId));
        }
        else
        {
            // ClassRecords & ArrayRecords
            return TryGetObject(serializationRecord.Id);
        }

        (object? value, SerializationRecordId id) TryGetObject(SerializationRecordId id)
        {
            if (!Deserializer.DeserializedObjects.TryGetValue(id, out object? value))
            {
                return (s_missingValueSentinel, id);
            }

            ValidateNewMemberObjectValue(value);
            return (value, id);
        }
    }

    /// <summary>
    ///  Called for new non-primitive reference types.
    /// </summary>
    private protected virtual void ValidateNewMemberObjectValue(object value) { }

    /// <summary>
    ///  Returns <see langword="true"/> if the given record's value needs an updater applied.
    /// </summary>
        // Null Id is a primitive value.
    private protected bool DoesValueNeedUpdated(object value, SerializationRecordId valueRecord) =>
        !valueRecord.Equals(default)
            // IObjectReference is going to have its object replaced.
            && (value is IObjectReference
                // Value types that aren't "complete" need to be reapplied.
                || (Deserializer.IncompleteObjects.Contains(valueRecord) && value.GetType().IsValueType));

    [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.ClassRecordParser.Create(ClassRecord, IDeserializer)")]
    internal static ObjectRecordDeserializer Create(SerializationRecord record, IDeserializer deserializer) => record switch
    {
        ClassRecord classRecord => ClassRecordDeserializer.Create(classRecord, deserializer),
        _ => new ArrayRecordDeserializer((ArrayRecord)record, deserializer),
    };
}

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