using GFramework.SourceGenerators.Rule;
using GFramework.SourceGenerators.Tests.Core;

namespace GFramework.SourceGenerators.Tests.Rule;

[TestFixture]
public class ContextGetGeneratorTests
{
    [Test]
    public async Task Generates_Bindings_For_ContextAwareAttribute_Class()
    {
        var source = """
                     using System;
                     using System.Collections.Generic;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class ContextAwareAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetModelAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetServicesAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                             public static IReadOnlyList<T> GetServices<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
                         public interface IInventoryStrategy { }

                         [ContextAware]
                         public partial class InventoryPanel
                         {
                             [GetModel]
                             private IInventoryModel _model = null!;

                             [GetServices]
                             private IReadOnlyList<IInventoryStrategy> _strategies = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class InventoryPanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _model = this.GetModel<global::TestApp.IInventoryModel>();
                                        _strategies = this.GetServices<global::TestApp.IInventoryStrategy>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class ContextAwareAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetModelAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                         [ContextAware]
                         public partial class InventoryPanel
                         {
                             [global::GFramework.SourceGenerators.Abstractions.Rule.GetModel]
                             private IInventoryModel _model = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class InventoryPanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _model = this.GetModel<global::TestApp.IInventoryModel>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Generates_Inferred_Bindings_For_GetAll_Class()
    {
        var source = """
                     using System;
                     using System.Collections.Generic;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class GetAllAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Architectures
                     {
                         public interface IArchitectureContext { }
                     }

                     namespace GFramework.Core.Rule
                     {
                         public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                             public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
                             public static T GetSystem<T>(this object contextAware) => default!;
                             public static T GetUtility<T>(this object contextAware) => default!;
                         }
                     }

                     namespace Godot
                     {
                         public class Node { }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
                         public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }
                         public interface IUiUtility : GFramework.Core.Abstractions.Utility.IUtility { }
                         public interface IStrategy { }

                         [GetAll]
                         public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
                         {
                             private IInventoryModel _model = null!;
                             private IReadOnlyList<IInventoryModel> _models = null!;
                             private ICombatSystem _system = null!;
                             private IUiUtility _utility = null!;
                             private IStrategy _service = null!;
                             private IReadOnlyList<IStrategy> _services = null!;
                             private Godot.Node _node = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class BattlePanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _model = this.GetModel<global::TestApp.IInventoryModel>();
                                        _models = this.GetModels<global::TestApp.IInventoryModel>();
                                        _system = this.GetSystem<global::TestApp.ICombatSystem>();
                                        _utility = this.GetUtility<global::TestApp.IUiUtility>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_BattlePanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Generates_Explicit_Service_Binding_For_GetAll_Class()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class GetAllAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetServiceAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Rule
                     {
                         public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                             public static T GetService<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
                         public interface IStrategy { }

                         [GetAll]
                         public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
                         {
                             private IInventoryModel _model = null!;

                             [GetService]
                             private IStrategy _service = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class BattlePanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _model = this.GetModel<global::TestApp.IInventoryModel>();
                                        _service = this.GetService<global::TestApp.IStrategy>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_BattlePanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class ContextAwareAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetModelAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                         [ContextAware]
                         public partial class InventoryPanel
                         {
                             [GetModel]
                             private IInventoryModel _model = null!;

                             private void {|#0:__InjectContextBindings_Generated|}()
                             {
                             }
                         }
                     }
                     """;

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" },
            TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck
        };

        test.ExpectedDiagnostics.Add(new DiagnosticResult("GF_Common_Class_002", DiagnosticSeverity.Error)
            .WithLocation(0)
            .WithArguments("InventoryPanel", "__InjectContextBindings_Generated"));

        await test.RunAsync();
    }

    [Test]
    public async Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class GetAllAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Rule
                     {
                         public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                         [GetAll]
                         public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
                         {
                             private const double LogicStep = 0.2;
                             private IInventoryModel _model = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class BattlePanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _model = this.GetModel<global::TestApp.IInventoryModel>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_BattlePanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                                                public sealed class GetAllAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Rule
                                            {
                                                public interface IContextAware { }
                                            }

                                            namespace GFramework.Core.Rule
                                            {
                                                public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static T GetModel<T>(this object contextAware) => default!;
                                                    public static T GetSystem<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
                                                public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }

                                                [GetAll]
                                                public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
                                                {
                                                    private readonly IInventoryModel {|#0:_model|} = null!;
                                                    private ICombatSystem _system = null!;
                                                }
                                            }
                                            """);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class BattlePanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _system = this.GetSystem<global::TestApp.ICombatSystem>();
                                    }
                                }

                                """;

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source },
                GeneratedSources =
                {
                    (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
                }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_008", DiagnosticSeverity.Warning),
                "0")
            .WithArguments("_model"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                                                public sealed class GetAllAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Rule
                                            {
                                                public interface IContextAware { }
                                            }

                                            namespace GFramework.Core.Rule
                                            {
                                                public abstract class ContextAwareBase : GFramework.Core.Abstractions.Rule.IContextAware { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static T GetModel<T>(this object contextAware) => default!;
                                                    public static T GetSystem<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }
                                                public interface ICombatSystem : GFramework.Core.Abstractions.Systems.ISystem { }

                                                [GetAll]
                                                public partial class BattlePanel : GFramework.Core.Rule.ContextAwareBase
                                                {
                                                    private static IInventoryModel {|#0:_model|} = null!;
                                                    private ICombatSystem _system = null!;
                                                }
                                            }
                                            """);

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class BattlePanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _system = this.GetSystem<global::TestApp.ICombatSystem>();
                                    }
                                }

                                """;

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source },
                GeneratedSources =
                {
                    (typeof(ContextGetGenerator), "TestApp_BattlePanel.ContextGet.g.cs", expected)
                }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_007", DiagnosticSeverity.Warning),
                "0")
            .WithArguments("_model"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class ContextAwareAttribute : Attribute { }

                         [AttributeUsage(AttributeTargets.Class, Inherited = false)]
                         public sealed class GetAllAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetModel<T>(this object contextAware) => default!;
                             public static T GetSystem<T>(this object contextAware) => default!;
                         }
                     }

                     namespace Godot
                     {
                         public class Control { }
                     }

                     namespace TestApp
                     {
                         public interface IGridModel : GFramework.Core.Abstractions.Model.IModel { }
                         public interface IRunLoopSystem : GFramework.Core.Abstractions.Systems.ISystem { }
                         public interface IUiPageBehavior { }

                         [ContextAware]
                         [GetAll]
                         public partial class GameplayHud : Godot.Control
                         {
                             private IGridModel _gridModel = null!;
                             private IUiPageBehavior? _page;
                             private IRunLoopSystem _runLoopSystem = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class GameplayHud
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _gridModel = this.GetModel<global::TestApp.IGridModel>();
                                        _runLoopSystem = this.GetSystem<global::TestApp.IRunLoopSystem>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_GameplayHud.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Generates_Bindings_For_IContextAware_Class()
    {
        var source = """
                     using System;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetServiceAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static T GetService<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IStrategy { }

                         public partial class StrategyHost : GFramework.Core.Abstractions.Rule.IContextAware
                         {
                             [GetService]
                             private IStrategy _strategy = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class StrategyHost
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _strategy = this.GetService<global::TestApp.IStrategy>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_StrategyHost.ContextGet.g.cs", expected));
        Assert.Pass();
    }

    [Test]
    public async Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                                                public sealed class GetModelAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static T GetModel<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                                                public partial class InventoryPanel
                                                {
                                                    [GetModel]
                                                    private IInventoryModel {|#0:_model|} = null!;
                                                }
                                            }
                                            """);

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_005", DiagnosticSeverity.Error),
                "0")
            .WithArguments("InventoryPanel"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using System.Collections.Generic;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                                                public sealed class GetModelsAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Rule
                                            {
                                                public interface IContextAware { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                                                public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
                                                {
                                                    [GetModels]
                                                    private List<IInventoryModel> {|#0:_models|} = new();
                                                }
                                            }
                                            """);

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_004", DiagnosticSeverity.Error),
                "0")
            .WithArguments("_models", "System.Collections.Generic.List<TestApp.IInventoryModel>", "GetModels"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                                                public sealed class GetModelAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Rule
                                            {
                                                public interface IContextAware { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static T GetModel<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                                                public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
                                                {
                                                    [GetModel]
                                                    private readonly IInventoryModel {|#0:_model|} = null!;
                                                }
                                            }
                                            """);

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_003", DiagnosticSeverity.Error),
                "0")
            .WithArguments("_model"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
    {
        var source = MarkupTestSource.Parse("""
                                            using System;
                                            using GFramework.SourceGenerators.Abstractions.Rule;

                                            namespace GFramework.SourceGenerators.Abstractions.Rule
                                            {
                                                [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                                                public sealed class GetModelAttribute : Attribute { }
                                            }

                                            namespace GFramework.Core.Abstractions.Rule
                                            {
                                                public interface IContextAware { }
                                            }

                                            namespace GFramework.Core.Abstractions.Model
                                            {
                                                public interface IModel { }
                                            }

                                            namespace GFramework.Core.Abstractions.Systems
                                            {
                                                public interface ISystem { }
                                            }

                                            namespace GFramework.Core.Abstractions.Utility
                                            {
                                                public interface IUtility { }
                                            }

                                            namespace GFramework.Core.Extensions
                                            {
                                                public static class ContextAwareServiceExtensions
                                                {
                                                    public static T GetModel<T>(this object contextAware) => default!;
                                                }
                                            }

                                            namespace TestApp
                                            {
                                                public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                                                public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
                                                {
                                                    [GetModel]
                                                    private static IInventoryModel {|#0:_model|} = null!;
                                                }
                                            }
                                            """);

        var test = new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source.Source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };

        test.ExpectedDiagnostics.Add(source.WithSpan(
                new DiagnosticResult("GF_ContextGet_002", DiagnosticSeverity.Error),
                "0")
            .WithArguments("_model"));

        await test.RunAsync();
        Assert.Pass();
    }

    [Test]
    public async Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
    {
        var source = """
                     using System;
                     using System.Collections.Generic;
                     using GFramework.SourceGenerators.Abstractions.Rule;

                     namespace GFramework.SourceGenerators.Abstractions.Rule
                     {
                         [AttributeUsage(AttributeTargets.Field, Inherited = false)]
                         public sealed class GetModelsAttribute : Attribute { }
                     }

                     namespace GFramework.Core.Abstractions.Rule
                     {
                         public interface IContextAware { }
                     }

                     namespace GFramework.Core.Abstractions.Model
                     {
                         public interface IModel { }
                     }

                     namespace GFramework.Core.Abstractions.Systems
                     {
                         public interface ISystem { }
                     }

                     namespace GFramework.Core.Abstractions.Utility
                     {
                         public interface IUtility { }
                     }

                     namespace GFramework.Core.Extensions
                     {
                         public static class ContextAwareServiceExtensions
                         {
                             public static IReadOnlyList<T> GetModels<T>(this object contextAware) => default!;
                         }
                     }

                     namespace TestApp
                     {
                         public interface IInventoryModel : GFramework.Core.Abstractions.Model.IModel { }

                         public partial class InventoryPanel : GFramework.Core.Abstractions.Rule.IContextAware
                         {
                             [GetModels]
                             private IEnumerable<IInventoryModel> _models = null!;
                         }
                     }
                     """;

        const string expected = """
                                // <auto-generated />
                                #nullable enable

                                using GFramework.Core.Extensions;

                                namespace TestApp;

                                partial class InventoryPanel
                                {
                                    private void __InjectContextBindings_Generated()
                                    {
                                        _models = this.GetModels<global::TestApp.IInventoryModel>();
                                    }
                                }

                                """;

        await GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            ("TestApp_InventoryPanel.ContextGet.g.cs", expected));
        Assert.Pass();
    }
}