// 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.Serialization;
using System.Formats.Nrbf;

namespace System.Private.Windows.Core.BinaryFormat;

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

/// <summary>
///  Deserializer for <see cref="ClassRecord"/>s that use <see cref="SerializationInfo"/> to initialize class state.
/// </summary>
/// <remarks>
///  <para>
///   This is used either because the class implements <see cref="ISerializable"/> or because a surrogate was used.
///  </para>
/// </remarks>
internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDeserializer
{
    private readonly ClassRecord _classRecord;
    private readonly SerializationInfo _serializationInfo;
    private readonly ISerializationSurrogate? _surrogate;
    private readonly IEnumerator<string> _memberNamesIterator;
    private bool _canIterate;

    internal ClassRecordSerializationInfoDeserializer(
        ClassRecord classRecord,
        object @object,
        Type type,
        ISerializationSurrogate? surrogate,
        IDeserializer deserializer) : base(classRecord, @object, deserializer)
    {
        _classRecord = classRecord;
        _surrogate = surrogate;
        _serializationInfo = new(type, DefaultConverter);
        _memberNamesIterator = _classRecord.MemberNames.GetEnumerator();
        _canIterate = _memberNamesIterator.MoveNext(); // start the iterator
    }

    internal override SerializationRecordId Continue()
    {
        if (_canIterate)
        {
            do
            {
                string memberName = _memberNamesIterator.Current;
                (object? memberValue, SerializationRecordId reference) = UnwrapMemberValue(_classRecord.GetRawValue(memberName));

                if (s_missingValueSentinel == memberValue)
                {
                    // Record has not been encountered yet, need to pend iteration.
                    return reference;
                }

                if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference))
                {
                    Deserializer.PendValueUpdater(new SerializationInfoValueUpdater(
                        _classRecord.Id,
                        reference,
                        _serializationInfo,
                        memberName));
                }

                _serializationInfo.AddValue(memberName, memberValue);
            }
            while (_memberNamesIterator.MoveNext());

            _canIterate = false;
        }

        // We can't complete these in the same way we do with direct field sets as user code can dereference the
        // reference type members from the SerializationInfo that aren't fully completed (due to cycles). With direct
        // field sets it doesn't matter if the referenced object isn't fully completed. Waiting until the graph is
        // fully parsed to allow cycles the best chance to resolve as much as possible without having to walk the
        // entire graph from this point to make a determination.
        //
        // The same issue applies to "complete" events, which is why we pend them as well.
        //
        // If we were confident that there were no cycles in the graph to this point we could apply directly
        // if there were no pending value types (which should also not happen if there are no cycles).

        PendingSerializationInfo pending = new(_classRecord.Id, _serializationInfo, _surrogate);
        Deserializer.PendSerializationInfo(pending);

        // No more missing member refs.
        return default;
    }
}

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