using System;
using System.Collections.Generic;
using System.Linq;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Tests.TestModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace AyCode.Core.Tests.Serialization;

/// <summary>
/// Tests for IId-based reference handling in Binary serializer.
/// Two scenarios:
/// 1. Same instance referenced multiple times (object identity)
/// 2. Different instances with same IId.Id (IId-based deduplication)
/// 
/// Tests verify BOTH:
/// - Serialized output uses ObjectRef (not redundant full objects)
/// - Deserialized result maintains reference identity
/// </summary>
[TestClass]
public class AcBinarySerializerIIdReferenceTests
{
    #region Helper Methods

    /// <summary>
    /// Counts occurrences of ObjectRef in binary data.
    /// Uses BinaryTypeCode.ObjectRef constant to stay in sync with format changes.
    /// </summary>
    private static int CountObjectRefs(byte[] binary, bool writeBinaryToConsole = true)
    {
        if (writeBinaryToConsole) WriteBinaryToConsole(binary);

        var count = 0;
        for (var i = 0; i < binary.Length; i++)
        {
            if (binary[i] == BinaryTypeCode.ObjectRef)
                count++;
        }
        return count;
    }

    private static void WriteBinaryToConsole(byte[] binary)
    {
            Console.WriteLine();
            Console.WriteLine(BitConverter.ToString(binary));
            Console.WriteLine();
    }

    /// <summary>
    /// Counts occurrences of a string in binary data (UTF8).
    /// </summary>
    private static int CountStringOccurrences(byte[] binary, string searchString)
    {
        var searchBytes = System.Text.Encoding.UTF8.GetBytes(searchString);
        var count = 0;
        for (var i = 0; i <= binary.Length - searchBytes.Length; i++)
        {
            var match = true;
            for (var j = 0; j < searchBytes.Length; j++)
            {
                if (binary[i + j] != searchBytes[j])
                {
                    match = false;
                    break;
                }
            }
            if (match) count++;
        }
        return count;
    }

    #endregion

    #region Scenario 1: Same Instance (Object Identity)

    /// <summary>
    /// SCENARIO 1: Same instance referenced multiple times.
    /// Tests all ReferenceHandling modes: None, OnlyId, All
    /// </summary>
    [TestMethod]
    [DataRow(true, true)]
    [DataRow(false, true)]
    [DataRow(true, false)]
    [DataRow(false, false)]
    public void SameInstance_SerializeAndDeserialize(bool useSgen, bool useMeta)
    {
        var modes = new[] 
        { 
            ReferenceHandlingMode.None, 
            ReferenceHandlingMode.OnlyId, 
            ReferenceHandlingMode.All 
        };

        foreach (var mode in modes)
        {
            // Arrange: SAME instance used multiple times
            var userPreferences = new UserPreferences_All_True();
            var sharedTag = new SharedTag_All_True { Id = 1, Name = "ImportantTag", Color = "#FF0000" };
            var sharedUser = new SharedUser_All_True { Id = 1, Preferences = userPreferences };

            var order = new TestOrder_Circ_Ref
            {
                Id = 1,
                OrderNumber = "ORD-001",
                PrimaryTag = sharedTag,
                Owner = sharedUser,
                Items =
                [
                    new TestOrderItem_Circ_Ref { Id = 1, ProductName = "Product-A", Tag = sharedTag, Assignee = sharedUser },
                    new TestOrderItem_Circ_Ref { Id = 2, ProductName = "Product-B", Tag = sharedTag, Assignee = new SharedUser_All_True { Id = 2, Preferences = userPreferences }},
                    new TestOrderItem_Circ_Ref { Id = 3, ProductName = "Product-C", Tag = sharedTag, Assignee = new SharedUser_All_True { Id = 3, Preferences = userPreferences } }
                ]
            };

            //order.Parent = order.Items[1];

            if (mode != ReferenceHandlingMode.None) order.Parent = order.Items[1];
            else order.Parent = userPreferences;

            order.Items[1].ParentOrder = order;

            var options = new AcBinarySerializerOptions
            {
                ReferenceHandling = mode,
                UseGeneratedCode = useSgen,
                UseMetadata = useMeta,
                MaxDepth = 10,
                // None mode has no ref tracking → the cycle (Items[1].ParentOrder = order) is unprotected.
                // Use Truncate so the recursion silently bottoms out with Null at the depth limit instead of throwing.
                MaxDepthBehavior = mode == ReferenceHandlingMode.None
                    ? MaxDepthBehavior.Truncate
                    : MaxDepthBehavior.Throw
            };

            Console.WriteLine($"\n========== ReferenceHandling: {options.ReferenceHandling}, UseSgen: {options.UseGeneratedCode}, UseMeta: {options.UseMetadata} ==========");

            // Act
            var binary = AcBinarySerializer.Serialize(order, options);
            if (mode == ReferenceHandlingMode.None) WriteBinaryToConsole(binary);
            var result = binary.BinaryTo<TestOrder_Circ_Ref>(); // Options from header

            var objectRefCount = CountObjectRefs(binary, false);
            Console.WriteLine($"Binary size: {binary.Length} bytes");
            Console.WriteLine($"ObjectRef count: {objectRefCount}");

            Assert.IsNotNull(result, $"[{mode}] Deserialized result is null");
            //Assert.IsNotNull(result.Parent);
            Assert.IsNotNull(result.Owner);

            // Assert based on mode
            switch (mode)
            {
                case ReferenceHandlingMode.None:
                    // Truncate semantic: cycle bottoms out with Null at MaxDepth=10 → serialize succeeds, deserialize
                    // produces a partial graph where deep cyclic references read as null. Data integrity at root +
                    // first few levels still holds (verified below after the switch). CountObjectRefs raw byte scan
                    // is unreliable in None mode — byte 65 (ObjectRef) == ASCII 'A', so "Product-A" produces false
                    // positives. Skip count assertion; rely on data integrity checks instead.
                    break;

                case ReferenceHandlingMode.OnlyId:
                    // sharedTag (Id=1) 4x → 3 ObjectRefs, sharedUser (Id=1) 2x → 1 ObjectRef = 4 total
                    Assert.IsTrue(objectRefCount >= 4, $"[{mode}] Expected at least 4 ObjectRefs, found {objectRefCount}");
                    // IId types should have reference identity
                    Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, $"[{mode}] Tag reference identity failed");
                    Assert.AreSame(result.Owner, result.Items[0].Assignee, $"[{mode}] User reference identity failed");
                    Assert.AreSame(result, result.Items[1].ParentOrder);
                    Assert.AreSame(result.Parent, result.Items[1]);
                    break;
                    
                case ReferenceHandlingMode.All:
                    // IId types + Non-IId (UserPreferences_All_True) should have ObjectRefs
                    Assert.IsTrue(objectRefCount >= 4, $"[{mode}] Expected at least 4 ObjectRefs, found {objectRefCount}");
                    Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, $"[{mode}] Tag reference identity failed");
                    Assert.AreSame(result.Owner, result.Items[0].Assignee, $"[{mode}] User reference identity failed");
                    
                    Assert.AreSame(result, result.Items[1].ParentOrder);
                    Assert.AreSame(result.Parent, result.Items[1]);

                    // Non-IId should also have reference identity in All mode
                    Assert.AreSame(result.Owner.Preferences, result.Items[0].Assignee.Preferences, $"[{mode}] UserPreferences_All_True reference identity failed - Non-IId should work in All mode!");
                    break;
            }

            // Data integrity - always check
            Assert.IsNotNull(result.PrimaryTag, $"[{mode}] PrimaryTag is null");
            Assert.AreEqual(1, result.PrimaryTag.Id, $"[{mode}] PrimaryTag.Id incorrect");
            Assert.AreEqual("ImportantTag", result.PrimaryTag.Name, $"[{mode}] PrimaryTag.Name incorrect");
            Assert.AreEqual(3, result.Items.Count, $"[{mode}] Items count incorrect");

            Console.WriteLine($"[{mode}] PASSED ✓");
        }
    }

    #endregion

    #region Scenario 2: Different Instances with Same IId (IId-Based Deduplication)

    /// <summary>
    /// SCENARIO 2: DIFFERENT instances with SAME IId.Id value.
    /// CRITICAL test - if IId-based deduplication works:
    /// - ObjectRef should be used in binary
    /// - Data should be complete after deserialize
    /// - References should be identical (AreSame)
    /// - Different TYPES with same int Id should NOT be confused!
    /// </summary>
    [TestMethod]
    public void DifferentInstances_SameIId_SerializeAndDeserialize()
    {
        // Arrange: DIFFERENT instances but SAME IId.Id
        // CRITICAL: Multiple DIFFERENT TYPES all have Id=1 - must not be confused!
        var sharedTag = new SharedTag_All_True { Id = 55, Name = "ImportantTag_55", Color = "#FF0000" };
        var order = new TestOrder_All_True
        {
            Id = 1,
            OrderNumber = "ORD-001",
            // All three types have Id=1 - tests (Type, Id) keying, not just Id
            PrimaryTag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
            Owner = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" },
            Category = new SharedCategory_All_True { Id = 1, Name = "Category_Id1", SortOrder = 10 },
            Items =
            [
                new TestOrderItem_All_True 
                { 
                    Id = 1, 
                    ProductName = "Product-A", 
                    Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
                    Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
                },
                new TestOrderItem_All_True 
                { 
                    Id = 2, 
                    ProductName = "Product-B", 
                    Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
                    Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
                },
                new TestOrderItem_All_True 
                { 
                    Id = 3, 
                    ProductName = "Product-C", 
                    Tag = new SharedTag_All_True { Id = 1, Name = "Tag_Id1", Color = "#FF0000" },
                    Assignee = new SharedUser_All_True { Id = 1, Username = "User_Id1", Email = "user1@test.com" }
                }
            ]
        };

        // Act
        var binary = order.ToBinary();
        var result = binary.BinaryTo<TestOrder_All_True>();

        // Assert 1: Check if ObjectRef is used (IId-based deduplication active)
        var objectRefCount = CountObjectRefs(binary);
        Console.WriteLine($"\nBinary size: {binary.Length} bytes (WithRef)");
        Console.WriteLine($"ObjectRef count: {objectRefCount}");
        
        // Assert 3: Reference identity - same TYPE with same Id should be same reference
        // Tags with Id=1 should all be same reference
        Assert.AreSame(result.PrimaryTag, result.Items[0].Tag, 
            "CRITICAL: Item[0].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
        Assert.AreSame(result.PrimaryTag, result.Items[1].Tag,
            "CRITICAL: Item[1].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
        Assert.AreSame(result.PrimaryTag, result.Items[2].Tag,
            "CRITICAL: Item[2].Tag should be same reference as PrimaryTag (same SharedTag_All_True.Id=1)");
        
        // Users with Id=1 should all be same reference
        Assert.AreSame(result.Owner, result.Items[0].Assignee,
            "CRITICAL: Item[0].Assignee should be same reference as Owner (same SharedUser.Id=1)");
        Assert.AreSame(result.Owner, result.Items[1].Assignee,
            "CRITICAL: Item[1].Assignee should be same reference as Owner (same SharedUser.Id=1)");
        Assert.AreSame(result.Owner, result.Items[2].Assignee,
            "CRITICAL: Item[2].Assignee should be same reference as Owner (same SharedUser.Id=1)");
        
        // Assert 4: Different TYPES with same Id should NOT be same reference!
        Assert.AreNotSame<object>(result.PrimaryTag, result.Owner,
            "CRITICAL BUG: Tag and User are same reference! Types with same int Id were confused!");
        Assert.AreNotSame<object>(result.PrimaryTag, result.Category,
            "CRITICAL BUG: Tag and Category are same reference! Types with same int Id were confused!");
        Assert.AreNotSame<object>(result.Owner, result.Category,
            "CRITICAL BUG: User and Category are same reference! Types with same int Id were confused!");
        // 4 Tags, 4 Users - each should have 3 ObjectRefs = 6 total minimum
        Assert.IsTrue(objectRefCount >= 6, 
            $"CRITICAL: Expected at least 6 ObjectRef entries (3 per type for Tag and User), found {objectRefCount}. " +
            "IId-based reference deduplication is NOT working!");

        // Assert 2: Data integrity - ALL data present and correct
        Assert.IsNotNull(result, "Deserialized result is null");
        
        // Tag data
        Assert.IsNotNull(result.PrimaryTag, "PrimaryTag is null - data lost!");
        Assert.AreEqual(1, result.PrimaryTag.Id, "PrimaryTag.Id incorrect");
        Assert.AreEqual("Tag_Id1", result.PrimaryTag.Name, "PrimaryTag.Name incorrect - might be confused with User!");
        Assert.AreEqual("#FF0000", result.PrimaryTag.Color, "PrimaryTag.Color incorrect");
        
        // User data - MUST NOT be confused with Tag (both have Id=1)
        Assert.IsNotNull(result.Owner, "Owner is null - data lost!");
        Assert.AreEqual(1, result.Owner.Id, "Owner.Id incorrect");
        Assert.AreEqual("User_Id1", result.Owner.Username, "Owner.Username incorrect - might be confused with Tag!");
        Assert.AreEqual("user1@test.com", result.Owner.Email, "Owner.Email incorrect");
        
        // Category data - MUST NOT be confused with Tag or User (all have Id=1)
        Assert.IsNotNull(result.Category, "Category is null - data lost!");
        Assert.AreEqual(1, result.Category.Id, "Category.Id incorrect");
        Assert.AreEqual("Category_Id1", result.Category.Name, "Category.Name incorrect - might be confused with Tag!");
        Assert.AreEqual(10, result.Category.SortOrder, "Category.SortOrder incorrect");
        
        Assert.IsNotNull(result.Items, "Items is null");
        Assert.AreEqual(3, result.Items.Count, "Items count incorrect");
        
        for (var i = 0; i < 3; i++)
        {
            // Tag in items
            Assert.IsNotNull(result.Items[i].Tag, $"Items[{i}].Tag is null - data lost!");
            Assert.AreEqual(1, result.Items[i].Tag!.Id, $"Items[{i}].Tag.Id incorrect");
            Assert.AreEqual("Tag_Id1", result.Items[i].Tag.Name, $"Items[{i}].Tag.Name incorrect - confused with User?");
            
            // User in items - MUST NOT be confused with Tag
            Assert.IsNotNull(result.Items[i].Assignee, $"Items[{i}].Assignee is null - data lost!");
            Assert.AreEqual(1, result.Items[i].Assignee!.Id, $"Items[{i}].Assignee.Id incorrect");
            Assert.AreEqual("User_Id1", result.Items[i].Assignee.Username, $"Items[{i}].Assignee.Username incorrect - confused with Tag?");
        }
    }

    /// <summary>
    /// Size comparison: Same IId should result in smaller binary + verify data integrity.
    /// </summary>
    [TestMethod]
    public void DifferentInstances_SameIId_SmallerBinaryWithDataIntegrity()
    {
        // Arrange: 10 different instances with SAME IId
        var orderWithSameIId = new TestOrder_All_True
        {
            Id = 1,
            OrderNumber = "SAME-IID",
            Items = Enumerable.Range(1, 10).Select(i => new TestOrderItem_All_True
            {
                Id = i,
                ProductName = $"Product-{i}",
                // All have SAME IId.Id = 1, but DIFFERENT instances
                Assignee = new SharedUser_All_True { Id = 1, Username = "shared_user_name", Email = "shared@test.com" }
            }).ToList()
        };

        // Arrange: 10 different instances with DIFFERENT IIds
        var orderWithDifferentIIds = new TestOrder_All_True
        {
            Id = 1,
            OrderNumber = "DIFF-IID",
            Items = Enumerable.Range(1, 10).Select(i => new TestOrderItem_All_True
            {
                Id = i,
                ProductName = $"Product-{i}",
                // All have DIFFERENT IId.Id
                Assignee = new SharedUser_All_True { Id = i * 100, Username = "unique_user_name", Email = "unique@test.com" }
            }).ToList()
        };

        // Act
        var sameIIdBinary = orderWithSameIId.ToBinary();
        var diffIIdBinary = orderWithDifferentIIds.ToBinary();
        var sameIIdResult = sameIIdBinary.BinaryTo<TestOrder_All_True>();
        var diffIIdResult = diffIIdBinary.BinaryTo<TestOrder_All_True>();

        // Assert 1: Size comparison
        Console.WriteLine($"Same IId binary size: {sameIIdBinary.Length} bytes");
        Console.WriteLine($"Different IId binary size: {diffIIdBinary.Length} bytes");
        Console.WriteLine($"Size difference: {diffIIdBinary.Length - sameIIdBinary.Length} bytes");
        
        Assert.IsTrue(sameIIdBinary.Length < diffIIdBinary.Length,
            $"Same IId ({sameIIdBinary.Length}b) should be smaller than different IIds ({diffIIdBinary.Length}b). " +
            "IId-based deduplication NOT working!");

        // Assert 2: Data integrity for sameIId result
        Assert.IsNotNull(sameIIdResult, "sameIIdResult is null");
        Assert.IsNotNull(sameIIdResult.Items, "sameIIdResult.Items is null");
        Assert.AreEqual(10, sameIIdResult.Items.Count, "sameIIdResult should have 10 items");
        
        for (var i = 0; i < 10; i++)
        {
            Assert.IsNotNull(sameIIdResult.Items[i].Assignee, 
                $"sameIIdResult.Items[{i}].Assignee is null - data lost!");
            Assert.AreEqual(1, sameIIdResult.Items[i].Assignee!.Id,
                $"sameIIdResult.Items[{i}].Assignee.Id should be 1");
            Assert.AreEqual("shared_user_name", sameIIdResult.Items[i].Assignee.Username,
                $"sameIIdResult.Items[{i}].Assignee.Username incorrect");
        }

        // Assert 3: Reference identity for sameIId
        var firstAssignee = sameIIdResult.Items[0].Assignee;
        for (var i = 1; i < 10; i++)
        {
            Assert.AreSame(firstAssignee, sameIIdResult.Items[i].Assignee,
                $"CRITICAL: Items[{i}].Assignee should be same reference as Items[0].Assignee (same IId.Id=1)");
        }
    }

    #endregion

    #region Guid-Based IId Tests

    /// <summary>
    /// Guid IId with same instance - validates ObjectRef + data integrity + reference identity.
    /// </summary>
    [TestMethod]
    public void GuidIId_SameInstance_SerializeAndDeserialize()
    {
        // Arrange: SAME instance
        var sharedGuid = Guid.NewGuid();
        var sharedItem = new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" };
        
        var order = new TestGuidOrder
        {
            Id = Guid.NewGuid(),
            Code = "GUID-001",
            Count = 3,
            Items = [sharedItem, sharedItem, sharedItem]
        };

        // Act
        var binary = order.ToBinary();
        var result = binary.BinaryTo<TestGuidOrder>();

        // Assert 1: ObjectRef should be present
        var objectRefCount = CountObjectRefs(binary);
        Console.WriteLine($"Binary size: {binary.Length} bytes");
        Console.WriteLine($"ObjectRef count: {objectRefCount}");
        
        Assert.IsTrue(objectRefCount >= 2, $"Expected at least 2 ObjectRefs, found {objectRefCount}");

        // Assert 2: Data integrity - all items present and correct
        Assert.IsNotNull(result, "Result is null");
        Assert.IsNotNull(result.Items, "Items is null");
        Assert.AreEqual(3, result.Items.Count, "Items count incorrect");
        
        for (var i = 0; i < 3; i++)
        {
            Assert.IsNotNull(result.Items[i], $"Items[{i}] is null");
            Assert.AreEqual(sharedGuid, result.Items[i].Id, $"Items[{i}].Id incorrect");
            Assert.AreEqual("SharedGuidItem", result.Items[i].Name, $"Items[{i}].Name incorrect");
        }

        // Assert 3: Reference identity
        Assert.AreSame(result.Items[0], result.Items[1], "Items[0] and Items[1] should be same reference");
        Assert.AreSame(result.Items[1], result.Items[2], "Items[1] and Items[2] should be same reference");
    }

    /// <summary>
    /// CRITICAL: Guid IId with different instances but same Id - tests IId-based deduplication.
    /// </summary>
    [TestMethod]
    public void GuidIId_DifferentInstances_SameId_SerializeAndDeserialize()
    {
        // Arrange: DIFFERENT instances but SAME Guid
        var sharedGuid = Guid.NewGuid();
        
        var order = new TestGuidOrder
        {
            Id = Guid.NewGuid(),
            Code = "GUID-001",
            Count = 3,
            Items =
            [
                new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" },
                new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" },
                new TestGuidItem { Id = sharedGuid, Name = "SharedGuidItem" }
            ]
        };

        // Act
        var binary = order.ToBinary();
        var result = binary.BinaryTo<TestGuidOrder>();

        // Assert 1: ObjectRef should be present if IId-based dedup works
        var objectRefCount = CountObjectRefs(binary);
        Console.WriteLine($"Binary size: {binary.Length} bytes");
        Console.WriteLine($"ObjectRef count: {objectRefCount}");
        
        Assert.IsTrue(objectRefCount >= 2, 
            $"CRITICAL: Expected at least 2 ObjectRefs for same Guid IId, found {objectRefCount}. " +
            "Guid-based IId deduplication NOT working!");

        // Assert 2: Data integrity - all items present and correct
        Assert.IsNotNull(result, "Result is null");
        Assert.IsNotNull(result.Items, "Items is null");
        Assert.AreEqual(3, result.Items.Count, "Items count incorrect");
        
        for (var i = 0; i < 3; i++)
        {
            Assert.IsNotNull(result.Items[i], $"Items[{i}] is null - data lost!");
            Assert.AreEqual(sharedGuid, result.Items[i].Id, $"Items[{i}].Id incorrect");
            Assert.AreEqual("SharedGuidItem", result.Items[i].Name, $"Items[{i}].Name incorrect");
        }

        // Assert 3: Reference identity - if IId-based, should be same reference
        Assert.AreSame(result.Items[0], result.Items[1], 
            "CRITICAL: Items[0] and Items[1] should be same reference (same Guid)");
        Assert.AreSame(result.Items[1], result.Items[2], 
            "CRITICAL: Items[1] and Items[2] should be same reference (same Guid)");
    }

    #endregion

    #region Data Integrity Tests

    /// <summary>
    /// Diagnostic test to verify IId detection works correctly.
    /// </summary>
    [TestMethod]
    public void IIdDetection_Diagnostic()
    {
        // Test GetIdInfo directly
        var sharedTagType = typeof(SharedTag_All_True);
        var idInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(sharedTagType);
        
        Console.WriteLine($"SharedTag_All_True GetIdInfo: IsId={idInfo.IsId}, IdType={idInfo.IdType?.Name}");
        Assert.IsTrue(idInfo.IsId, "SharedTag_All_True should be detected as IId<int>");
        Assert.AreEqual(typeof(int), idInfo.IdType, "SharedTag_All_True Id type should be int");
        
        // Test SharedUser
        var sharedUserType = typeof(SharedUser_All_True);
        var userIdInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(sharedUserType);
        Console.WriteLine($"SharedUser GetIdInfo: IsId={userIdInfo.IsId}, IdType={userIdInfo.IdType?.Name}");
        Assert.IsTrue(userIdInfo.IsId, "SharedUser should be detected as IId<int>");
        
        // Test TestGuidItem
        var guidItemType = typeof(TestGuidItem);
        var guidIdInfo = AyCode.Core.Helpers.JsonUtilities.GetIdInfo(guidItemType);
        Console.WriteLine($"TestGuidItem GetIdInfo: IsId={guidIdInfo.IsId}, IdType={guidIdInfo.IdType?.Name}");
        Assert.IsTrue(guidIdInfo.IsId, "TestGuidItem should be detected as IId<Guid>");
    }

    /// <summary>
    /// Verify data is correct regardless of reference handling.
    /// </summary>
    [TestMethod]
    public void SharedCategory_DataIntegrity()
    {
        var categories = new List<SharedCategory_All_True>
        {
            new() { Id = 1, Name = "Category1", SortOrder = 1, IsDefault = true },
            new() { Id = 2, Name = "Category2", SortOrder = 2, ParentCategoryId = 1 },
            new() { Id = 3, Name = "Category3", SortOrder = 3, ParentCategoryId = 1 }
        };

        var binary = categories.ToBinary();
        var result = binary.BinaryTo<List<SharedCategory_All_True>>();

        Assert.IsNotNull(result);
        Assert.AreEqual(3, result.Count);
        
        Assert.AreEqual(1, result[0].Id);
        Assert.AreEqual("Category1", result[0].Name);
        Assert.IsTrue(result[0].IsDefault);
        
        Assert.AreEqual(2, result[1].Id);
        Assert.AreEqual(1, result[1].ParentCategoryId);

        Assert.AreEqual(3, result[2].Id);
        Assert.AreEqual(1, result[2].ParentCategoryId);
    }

    #endregion

    #region SGen-emit writer/reader ref-handling asymmetry — regression target

    /// <summary>
    /// Target test for the SGen-emit writer/reader asymmetry hypothesis — covers BOTH
    /// collection-element AND dictionary-value ref-marker paths in a single graph.
    /// <para>
    /// Setup:
    /// <list type="bullet">
    ///   <item><c>TestRefAsymParent</c> [AcBinarySerializable(false)] — parent EnableRefHandlingFeature=false.</item>
    ///   <item><c>TestRefAsymChild</c> [AcBinarySerializable(true)] — child IId&lt;int&gt;, all features ON.</item>
    ///   <item>Same child instance referenced twice in the parent's <c>Children</c> list
    ///         AND twice as VALUES in the parent's <c>ChildrenMap</c> dictionary.</item>
    ///   <item>Runtime <c>ReferenceHandling=All</c> + <c>Interning=All</c> (via Default options).</item>
    ///   <item><c>MarkerDecimal</c> property AFTER the list — drift detection slot (decimal = 16 fixed bytes).</item>
    ///   <item><c>MarkerDecimal2</c> property AFTER the dictionary — second drift detection slot,
    ///         catches the symmetric dict-value emit asymmetry (EmitReadDictionary:482).</item>
    /// </list>
    /// </para>
    /// <para>
    /// Expected if the asymmetry-hypothesis holds: the writer (runtime via
    /// WriteObjectGenerated bridge) emits ObjectRefFirst+ObjectRef for the duplicates; the SGen
    /// reader-emit's zero-branch path (parent flag false guarding out the ref-aware switch)
    /// misreads the VarUInt cacheIdx as a property-marker byte → DECIMAL_DRIFT exception or
    /// value-mismatch on MarkerDecimal / MarkerDecimal2.
    /// </para>
    /// <para>
    /// Expected if the hypothesis is WRONG: the test passes — different fix direction needed.
    /// </para>
    /// </summary>
    [TestMethod]
    public void Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction()
    {
        var sharedChild = new TestRefAsymChild { Id = 1, Name = "Shared" };
        var parent = new TestRefAsymParent
        {
            Id = 100,
            Children = new List<TestRefAsymChild> { sharedChild, sharedChild },
            MarkerDecimal = 999.99m,
            ChildrenMap = new Dictionary<int, TestRefAsymChild>
            {
                { 10, sharedChild },
                { 20, sharedChild },
            },
            MarkerDecimal2 = 888.88m,
        };

        var options = AcBinarySerializerOptions.Default; // RefHandling=All, Interning=All
        options.UseGeneratedCode = true;

        var bytes = AcBinarySerializer.Serialize(parent, options);

        // Sanity check: did the writer actually emit an ObjectRef marker for the duplicates?
        var objectRefCount = CountObjectRefs(bytes, writeBinaryToConsole: false);
        Console.WriteLine($"Wire size: {bytes.Length}, ObjectRef occurrences: {objectRefCount}");

        var result = AcBinaryDeserializer.Deserialize<TestRefAsymParent>(bytes, options);

        Assert.IsNotNull(result, "Deserialize returned null — wire corruption");
        Assert.AreEqual(parent.Id, result.Id, "Parent.Id mismatch — possible drift before the list");

        // --- Collection-element path (EmitReadCollectionElement) ---
        Assert.IsNotNull(result.Children, "Children list was null after round-trip");
        Assert.AreEqual(2, result.Children.Count, "Children count mismatch");
        Assert.IsNotNull(result.Children[0]);
        Assert.IsNotNull(result.Children[1]);
        Assert.AreEqual(sharedChild.Id, result.Children[0].Id, "Children[0].Id mismatch");
        Assert.AreEqual(sharedChild.Name, result.Children[0].Name, "Children[0].Name mismatch");
        Assert.AreEqual(sharedChild.Id, result.Children[1].Id, "Children[1].Id mismatch — drift on the duplicate");
        Assert.AreEqual(sharedChild.Name, result.Children[1].Name, "Children[1].Name mismatch — drift on the duplicate");
        Assert.AreEqual(parent.MarkerDecimal, result.MarkerDecimal,
            "MarkerDecimal drift — wire-position desync after the Children list (smoking gun for collection-element SGen-emit asymmetry)");

        // --- Dictionary-value path (EmitReadDictionary) ---
        Assert.IsNotNull(result.ChildrenMap, "ChildrenMap was null after round-trip");
        Assert.AreEqual(2, result.ChildrenMap.Count, "ChildrenMap count mismatch");
        Assert.IsTrue(result.ChildrenMap.ContainsKey(10), "ChildrenMap missing key=10");
        Assert.IsTrue(result.ChildrenMap.ContainsKey(20), "ChildrenMap missing key=20");
        Assert.IsNotNull(result.ChildrenMap[10]);
        Assert.IsNotNull(result.ChildrenMap[20]);
        Assert.AreEqual(sharedChild.Id, result.ChildrenMap[10].Id, "ChildrenMap[10].Id mismatch");
        Assert.AreEqual(sharedChild.Name, result.ChildrenMap[10].Name, "ChildrenMap[10].Name mismatch");
        Assert.AreEqual(sharedChild.Id, result.ChildrenMap[20].Id, "ChildrenMap[20].Id mismatch — drift on the dict-value duplicate");
        Assert.AreEqual(sharedChild.Name, result.ChildrenMap[20].Name, "ChildrenMap[20].Name mismatch — drift on the dict-value duplicate");
        Assert.AreEqual(parent.MarkerDecimal2, result.MarkerDecimal2,
            "MarkerDecimal2 drift — wire-position desync after the ChildrenMap dictionary (smoking gun for dict-value SGen-emit asymmetry)");
    }

    #endregion
}
