// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0

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

namespace GFramework.SourceGenerators.Tests.Rule;

/// <summary>
///     验证 <see cref="ContextGetGenerator" /> 在显式特性、<c>GetAll</c> 推断与诊断场景下的生成契约。
/// </summary>
[TestFixture]
public class ContextGetGeneratorTests
{
    private const string InventoryPanelGeneratedFileName = "TestApp_InventoryPanel.ContextGet.g.cs";
    private const string BattlePanelGeneratedFileName = "TestApp_BattlePanel.ContextGet.g.cs";
    private const string GameplayHudGeneratedFileName = "TestApp_GameplayHud.ContextGet.g.cs";
    private const string StrategyHostGeneratedFileName = "TestApp_StrategyHost.ContextGet.g.cs";

    private const string ContextAwareAttributeClassSource = """
        using System;
        using System.Collections.Generic;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string ContextAwareAttributeClassExpected = """
        // <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>();
            }
        }

        """;

    private const string FullyQualifiedFieldAttributesSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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.Core.SourceGenerators.Abstractions.Rule.GetModel]
                private IInventoryModel _model = null!;
            }
        }
        """;

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

        using GFramework.Core.Extensions;

        namespace TestApp;

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

        """;

    private const string InferredGetAllClassSource = """
        using System;
        using System.Collections.Generic;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string InferredGetAllClassExpected = """
        // <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>();
            }
        }

        """;

    private const string ExplicitServiceGetAllClassSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string ExplicitServiceGetAllClassExpected = """
        // <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>();
            }
        }

        """;

    private const string GeneratedInjectionMethodAlreadyExistsMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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|}()
                {
                }
            }
        }
        """;

    private const string IgnoreConstFieldGetAllSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

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

        using GFramework.Core.Extensions;

        namespace TestApp;

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

        """;

    private const string ReadonlyInferredFieldGetAllMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

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

        using GFramework.Core.Extensions;

        namespace TestApp;

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

        """;

    private const string StaticInferredFieldGetAllMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string SkipNullableServiceLikeFieldSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string SkipNullableServiceLikeFieldExpected = """
        // <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>();
            }
        }

        """;

    private const string IContextAwareClassSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

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

        using GFramework.Core.Extensions;

        namespace TestApp;

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

        """;

    private const string NonContextAwareClassMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string GetModelsFieldNotIReadOnlyListMarkupSource = """
        using System;
        using System.Collections.Generic;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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();
            }
        }
        """;

    private const string ReadonlyExplicitGetModelFieldMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string StaticExplicitGetModelFieldMarkupSource = """
        using System;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

    private const string GetModelsAssignableSource = """
        using System;
        using System.Collections.Generic;
        using GFramework.Core.SourceGenerators.Abstractions.Rule;

        namespace GFramework.Core.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!;
            }
        }
        """;

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

        using GFramework.Core.Extensions;

        namespace TestApp;

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

        """;

    /// <summary>
    ///     验证 <c>[ContextAware]</c> 类上的显式字段特性会生成模型与服务绑定。
    /// </summary>
    [Test]
    public Task Generates_Bindings_For_ContextAwareAttribute_Class()
    {
        return VerifyGeneratedSourceAsync(
            ContextAwareAttributeClassSource,
            InventoryPanelGeneratedFileName,
            ContextAwareAttributeClassExpected);
    }

    /// <summary>
    ///     验证字段使用 fully-qualified 特性名时仍能生成绑定。
    /// </summary>
    [Test]
    public Task Generates_Bindings_For_Fully_Qualified_Field_Attributes()
    {
        return VerifyGeneratedSourceAsync(
            FullyQualifiedFieldAttributesSource,
            InventoryPanelGeneratedFileName,
            FullyQualifiedFieldAttributesExpected);
    }

    /// <summary>
    ///     验证 <c>GetAll</c> 会仅为可推断的 model、models、system 与 utility 字段生成绑定。
    /// </summary>
    [Test]
    public Task Generates_Inferred_Bindings_For_GetAll_Class()
    {
        return VerifyGeneratedSourceAsync(
            InferredGetAllClassSource,
            BattlePanelGeneratedFileName,
            InferredGetAllClassExpected);
    }

    /// <summary>
    ///     验证 <c>GetAll</c> 与显式 <c>[GetService]</c> 可以组合生成绑定。
    /// </summary>
    [Test]
    public Task Generates_Explicit_Service_Binding_For_GetAll_Class()
    {
        return VerifyGeneratedSourceAsync(
            ExplicitServiceGetAllClassSource,
            BattlePanelGeneratedFileName,
            ExplicitServiceGetAllClassExpected);
    }

    /// <summary>
    ///     验证目标类已声明注入方法时会报告冲突诊断。
    /// </summary>
    [Test]
    public Task Reports_Diagnostic_When_Generated_Injection_Method_Name_Already_Exists()
    {
        var source = MarkupTestSource.Parse(GeneratedInjectionMethodAlreadyExistsMarkupSource);

        return VerifyDiagnosticAsync(
            source,
            CreateSingleSpanDiagnostic(
                source,
                "GF_Common_Class_002",
                DiagnosticSeverity.Error,
                "InventoryPanel",
                "__InjectContextBindings_Generated"));
    }

    /// <summary>
    ///     验证 <c>GetAll</c> 会忽略不可推断的常量字段且不报告诊断。
    /// </summary>
    [Test]
    public Task Ignores_NonInferable_Const_Field_For_GetAll_Class_Without_Diagnostic()
    {
        return VerifyGeneratedSourceAsync(
            IgnoreConstFieldGetAllSource,
            BattlePanelGeneratedFileName,
            IgnoreConstFieldGetAllExpected);
    }

    /// <summary>
    ///     验证只读推断字段会被跳过并报告 warning，同时其他字段保持生成。
    /// </summary>
    [Test]
    public Task Warns_And_Skips_Readonly_Inferred_Field_For_GetAll_Class()
    {
        var source = MarkupTestSource.Parse(ReadonlyInferredFieldGetAllMarkupSource);

        return VerifyDiagnosticAndGeneratedSourceAsync(
            source,
            BattlePanelGeneratedFileName,
            SkipInvalidGetAllFieldExpected,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_008",
                DiagnosticSeverity.Warning,
                "_model"));
    }

    /// <summary>
    ///     验证静态推断字段会被跳过并报告 warning，同时其他字段保持生成。
    /// </summary>
    [Test]
    public Task Warns_And_Skips_Static_Inferred_Field_For_GetAll_Class()
    {
        var source = MarkupTestSource.Parse(StaticInferredFieldGetAllMarkupSource);

        return VerifyDiagnosticAndGeneratedSourceAsync(
            source,
            BattlePanelGeneratedFileName,
            SkipInvalidGetAllFieldExpected,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_007",
                DiagnosticSeverity.Warning,
                "_model"));
    }

    /// <summary>
    ///     验证 nullable 的服务样字段不会被 <c>GetAll</c> 误判为可注入字段。
    /// </summary>
    [Test]
    public Task Skips_Nullable_Service_Like_Field_For_ContextAware_GetAll_Class()
    {
        return VerifyGeneratedSourceAsync(
            SkipNullableServiceLikeFieldSource,
            GameplayHudGeneratedFileName,
            SkipNullableServiceLikeFieldExpected);
    }

    /// <summary>
    ///     验证实现 <c>IContextAware</c> 的类型无需 <c>[ContextAware]</c> 也能生成显式绑定。
    /// </summary>
    [Test]
    public Task Generates_Bindings_For_IContextAware_Class()
    {
        return VerifyGeneratedSourceAsync(
            IContextAwareClassSource,
            StrategyHostGeneratedFileName,
            IContextAwareClassExpected);
    }

    /// <summary>
    ///     验证缺少上下文感知契约的类型会报告错误诊断。
    /// </summary>
    [Test]
    public Task Reports_Diagnostic_When_Class_Is_Not_ContextAware()
    {
        var source = MarkupTestSource.Parse(NonContextAwareClassMarkupSource);

        return VerifyDiagnosticAsync(
            source,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_005",
                DiagnosticSeverity.Error,
                "InventoryPanel"));
    }

    /// <summary>
    ///     验证 <c>[GetModels]</c> 字段若不是可赋值自 <c>IReadOnlyList&lt;T&gt;</c> 会报告错误。
    /// </summary>
    [Test]
    public Task Reports_Diagnostic_When_GetModels_Field_Is_Not_IReadOnlyList()
    {
        var source = MarkupTestSource.Parse(GetModelsFieldNotIReadOnlyListMarkupSource);

        return VerifyDiagnosticAsync(
            source,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_004",
                DiagnosticSeverity.Error,
                "_models",
                "System.Collections.Generic.List<TestApp.IInventoryModel>",
                "GetModels"));
    }

    /// <summary>
    ///     验证显式 <c>[GetModel]</c> 作用于只读字段时会报告错误。
    /// </summary>
    [Test]
    public Task Reports_Diagnostic_For_Readonly_Explicit_GetModel_Field()
    {
        var source = MarkupTestSource.Parse(ReadonlyExplicitGetModelFieldMarkupSource);

        return VerifyDiagnosticAsync(
            source,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_003",
                DiagnosticSeverity.Error,
                "_model"));
    }

    /// <summary>
    ///     验证显式 <c>[GetModel]</c> 作用于静态字段时会报告错误。
    /// </summary>
    [Test]
    public Task Reports_Diagnostic_For_Static_Explicit_GetModel_Field()
    {
        var source = MarkupTestSource.Parse(StaticExplicitGetModelFieldMarkupSource);

        return VerifyDiagnosticAsync(
            source,
            CreateSingleSpanDiagnostic(
                source,
                "GF_ContextGet_002",
                DiagnosticSeverity.Error,
                "_model"));
    }

    /// <summary>
    ///     验证 <c>[GetModels]</c> 字段可以赋值到更宽的可枚举接口上。
    /// </summary>
    [Test]
    public Task Generates_Bindings_For_GetModels_Field_Assignable_From_IReadOnlyList()
    {
        return VerifyGeneratedSourceAsync(
            GetModelsAssignableSource,
            InventoryPanelGeneratedFileName,
            GetModelsAssignableExpected);
    }

    /// <summary>
    ///     运行单个生成源码断言，保持文件名与文本快照语义不变。
    /// </summary>
    /// <param name="source">测试输入源码。</param>
    /// <param name="generatedFileName">期望生成文件名。</param>
    /// <param name="expectedGeneratedSource">期望生成源码。</param>
    /// <returns>表示测试执行的异步任务。</returns>
    private static Task VerifyGeneratedSourceAsync(
        string source,
        string generatedFileName,
        string expectedGeneratedSource)
    {
        return GeneratorTest<ContextGetGenerator>.RunAsync(
            source,
            (generatedFileName, expectedGeneratedSource));
    }

    /// <summary>
    ///     运行仅关注诊断输出的生成器测试。
    /// </summary>
    /// <param name="source">包含 markup span 的测试源码。</param>
    /// <param name="expectedDiagnostic">期望诊断。</param>
    /// <returns>表示测试执行的异步任务。</returns>
    private static Task VerifyDiagnosticAsync(
        MarkupTestSource source,
        DiagnosticResult expectedDiagnostic)
    {
        var test = CreateGeneratorTest(source.Source);
        test.ExpectedDiagnostics.Add(expectedDiagnostic);
        return test.RunAsync();
    }

    /// <summary>
    ///     运行同时断言诊断与部分生成输出的生成器测试。
    /// </summary>
    /// <param name="source">包含 markup span 的测试源码。</param>
    /// <param name="generatedFileName">期望生成文件名。</param>
    /// <param name="expectedGeneratedSource">期望生成源码。</param>
    /// <param name="expectedDiagnostic">期望诊断。</param>
    /// <returns>表示测试执行的异步任务。</returns>
    private static Task VerifyDiagnosticAndGeneratedSourceAsync(
        MarkupTestSource source,
        string generatedFileName,
        string expectedGeneratedSource,
        DiagnosticResult expectedDiagnostic)
    {
        var test = CreateGeneratorTest(source.Source);
        test.TestState.GeneratedSources.Add(
            (typeof(ContextGetGenerator), generatedFileName, NormalizeLineEndings(expectedGeneratedSource)));
        test.ExpectedDiagnostics.Add(expectedDiagnostic);
        return test.RunAsync();
    }

    /// <summary>
    ///     为单一 markup span 场景构造诊断结果，统一保持定位键与参数组装方式。
    /// </summary>
    /// <param name="source">包含 markup span 的测试源码。</param>
    /// <param name="diagnosticId">诊断 ID。</param>
    /// <param name="severity">诊断严重级别。</param>
    /// <param name="arguments">诊断参数。</param>
    /// <returns>绑定到 markup key <c>0</c> 的期望诊断。</returns>
    private static DiagnosticResult CreateSingleSpanDiagnostic(
        MarkupTestSource source,
        string diagnosticId,
        DiagnosticSeverity severity,
        params string[] arguments)
    {
        return source.WithSpan(
                new DiagnosticResult(diagnosticId, severity),
                "0")
            .WithArguments(arguments);
    }

    /// <summary>
    ///     创建禁用 trace 诊断的通用源生成器测试实例，避免各场景重复样板配置。
    /// </summary>
    /// <param name="source">测试输入源码。</param>
    /// <returns>配置完成的生成器测试对象。</returns>
    private static CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier> CreateGeneratorTest(string source)
    {
        return new CSharpSourceGeneratorTest<ContextGetGenerator, DefaultVerifier>
        {
            TestState =
            {
                Sources = { source }
            },
            DisabledDiagnostics = { "GF_Common_Trace_001" }
        };
    }

    /// <summary>
    ///     将手工声明的期望生成源码归一化到当前平台换行符，避免不同宿主上的伪差异。
    /// </summary>
    /// <param name="content">原始期望源码。</param>
    /// <returns>已按当前平台换行符归一化的源码文本。</returns>
    private static string NormalizeLineEndings(string content)
    {
        return content
            .Replace("\r\n", "\n", StringComparison.Ordinal)
            .Replace("\r", "\n", StringComparison.Ordinal)
            .Replace("\n", Environment.NewLine, StringComparison.Ordinal);
    }
}
