// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.TypeSpec.Generator.ClientModel.Providers;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.Statements;
using Microsoft.TypeSpec.Generator.Tests.Common;
using NUnit.Framework;
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;

namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProviders
{
    public class ClientProviderTests
    {
        [TestCase("Foo", "Foo", ExpectedResult = true)]
        [TestCase("Foo", "Bar", ExpectedResult = false)]
        [TestCase("Foo", "_Foo", ExpectedResult = false)]
        [TestCase("_Foo", "Foo", ExpectedResult = false)]
        [TestCase("Foo", "Bar.Foo", ExpectedResult = true)]
        [TestCase("Bar.Foo", "Foo", ExpectedResult = true)]
        [TestCase("Foo", "Bar._Foo", ExpectedResult = false)]
        [TestCase("Bar._Foo", "Foo", ExpectedResult = false)]
        [TestCase("Foo", "/Foo", ExpectedResult = false)]
        [TestCase("/Foo", "Foo", ExpectedResult = false)]
        [TestCase(".Foo", ".Foo", ExpectedResult = true)]
        [TestCase("Foo", ".Foo", ExpectedResult = true)]
        [TestCase(".Foo", "Foo", ExpectedResult = true)]
        public bool ValidateIsLastNamespaceSegmentTheSame(string left, string right)
        {
            return ClientProvider.IsLastNamespaceSegmentTheSame(left, right);
        }

        private const string SubClientsCategory = "WithSubClients";
        private const string KeyAuthCategory = "WithKeyAuth";
        private const string OAuth2Category = "WithOAuth2";
        private const string OAuth2CategoryOtherCredType = "WithOAuth2_OtherCredType";
        private const string OnlyUnsupportedAuthCategory = "WithOnlyUnsupportedAuth";
        private const string TestClientName = "TestClient";
        private static readonly InputClient _testClient = InputFactory.Client(TestClientName);
        private static readonly InputClient _animalClient = InputFactory.Client("animal", doc: "AnimalClient description", parent: _testClient, initializedBy: InputClientInitializedBy.Parent);
        private static readonly InputClient _dogClient = InputFactory.Client("dog", doc: "DogClient description", parent: _animalClient, initializedBy: InputClientInitializedBy.Parent);
        private static readonly InputClient _huskyClient = InputFactory.Client("husky", doc: "HuskyClient description", parent: _dogClient, initializedBy: InputClientInitializedBy.Parent);
        private static readonly InputModelType _spreadModel = InputFactory.Model(
            "spreadModel",
            usage: InputModelTypeUsage.Spread,
            properties:
            [
                InputFactory.Property("p1", InputPrimitiveType.String, isRequired: true),
            ]);

        private bool _containsSubClients;
        private bool _hasKeyAuth;
        private bool _hasOAuth2;
        private bool _hasOAuth2WithOtherCredType;
        private bool _hasSupportedAuth;
        private bool _hasOnlyUnsupportedAuth;

        [SetUp]
        public void SetUp()
        {
            // Reset the singleton instance before each test to prevent state leaking
            var singletonField = typeof(ClientOptionsProvider).GetField("_singletonInstance", BindingFlags.Static | BindingFlags.NonPublic);
            singletonField?.SetValue(null, null);

            var categories = TestContext.CurrentContext.Test?.Properties["Category"];
            _containsSubClients = categories?.Contains(SubClientsCategory) ?? false;
            _hasKeyAuth = categories?.Contains(KeyAuthCategory) ?? false;
            _hasOAuth2 = categories?.Contains(OAuth2Category) ?? false;
            _hasOAuth2WithOtherCredType = categories?.Contains(OAuth2CategoryOtherCredType) ?? false;
            _hasSupportedAuth = _hasKeyAuth || _hasOAuth2 || _hasOAuth2WithOtherCredType;
            _hasOnlyUnsupportedAuth = categories?.Contains(OnlyUnsupportedAuthCategory) ?? false;

            Func<IReadOnlyList<InputClient>>? clients = _containsSubClients ?
                () => [_testClient] :
                null;
            InputApiKeyAuth? apiKeyAuth = _hasKeyAuth ? new InputApiKeyAuth("mock", null) : null;
            InputOAuth2Auth? oauth2Auth = (_hasOAuth2 || _hasOAuth2WithOtherCredType)
                ? new InputOAuth2Auth([new InputOAuth2Flow(["mock"], null, null, null)])
                : null;
            Func<InputAuth>? auth = (_hasSupportedAuth || _hasOnlyUnsupportedAuth)
                ? () => new InputAuth(apiKeyAuth, oauth2Auth)
                : null;

            MockHelpers.LoadMockGenerator(
                clients: clients,
                clientPipelineApi: _hasOAuth2WithOtherCredType ? TestClientPipelineApi.Instance : null,
                auth: auth);
        }

        [Test]
        public void TestNullRootClientWithChildren()
        {
            var plugin = MockHelpers.LoadMockGenerator(
                clients: () => [_testClient],
                createClientCore: FilterOutClient
                );

            ClientProvider? FilterOutClient(InputClient client)
            {
                if (client == _testClient)
                {
                    return null;
                }
                return new ClientProvider(client);
            }

            var clients = plugin.Object.OutputLibrary.TypeProviders.Where(p => p is ClientProvider).ToList();
            Assert.AreEqual(3, clients.Count);
        }

        [Test]
        public void TestBuildProperties()
        {
            var client = InputFactory.Client(TestClientName);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // validate the properties
            var properties = clientProvider.Properties;
            Assert.IsTrue(properties.Count > 0);
            // there should be a pipeline property
            Assert.AreEqual(1, properties.Count);

            var pipelineProperty = properties.First();
            Assert.AreEqual(typeof(ClientPipeline), pipelineProperty.Type.FrameworkType);
            Assert.AreEqual("Pipeline", pipelineProperty.Name);
            Assert.AreEqual(MethodSignatureModifiers.Public, pipelineProperty.Modifiers);
        }

        [TestCaseSource(nameof(BuildFieldsTestCases))]
        public void TestBuildFields(List<InputParameter> inputParameters, List<ExpectedFieldProvider> expectedFields)
        {
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            AssertHasFields(clientProvider, expectedFields);
        }

        [TestCaseSource(nameof(BuildAuthFieldsTestCases), Category = KeyAuthCategory)]
        [TestCaseSource(nameof(BuildAuthFieldsTestCases), Category = OAuth2Category)]
        [TestCaseSource(nameof(BuildAuthFieldsTestCases), Category = OAuth2CategoryOtherCredType)]
        [TestCaseSource(nameof(BuildAuthFieldsTestCases), Category = $"{KeyAuthCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(BuildAuthFieldsTestCases), Category = OnlyUnsupportedAuthCategory)]
        public void TestBuildAuthFields_WithAuth(List<InputParameter> inputParameters)
        {
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            if (_hasKeyAuth)
            {
                // key auth should have the AuthorizationHeader const field; _keyCredential is no longer stored
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Const, new CSharpType(typeof(string)), "AuthorizationHeader"),
                });
                // _keyCredential field is no longer on the client (auth handled via AuthenticationPolicy parameter)
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_keyCredential"));
            }
            if (_hasOAuth2)
            {
                // oauth2 auth should have the _flows field; _tokenProvider is no longer stored
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, new CSharpType(typeof(Dictionary<string, object>[])), "_flows"),
                });
                // _tokenProvider field is no longer on the client (auth handled via AuthenticationPolicy parameter)
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_tokenProvider"));
            }

            if (_hasOAuth2WithOtherCredType)
            {
                // if another cred type other than the SCM type is used, then the client should default to the following fields: _scopes (no _tokenCredential)
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, new CSharpType(typeof(string[])), "AuthorizationScopes"),
                });
                // _tokenCredential field is no longer on the client (auth handled via AuthenticationPolicy parameter)
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_tokenCredential"));
            }

            if (_hasOnlyUnsupportedAuth)
            {
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Contains("credential", StringComparison.OrdinalIgnoreCase)));
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Contains("auth", StringComparison.OrdinalIgnoreCase)));
            }
        }

        [TestCaseSource(nameof(BuildOAuth2FlowsFieldTestCases))]
        public void TestBuildOAuth2FlowsField(IEnumerable<InputOAuth2Flow> inputFlows)
        {
            var oauth2Auth = new InputOAuth2Auth([.. inputFlows]);
            Func<InputAuth>? inputAuth = () => new InputAuth(null, oauth2Auth);
            MockHelpers.LoadMockGenerator(auth: inputAuth);

            var client = InputFactory.Client(TestClientName);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // oauth2 auth should have the _flows field; _tokenProvider is no longer stored on the client
            AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
            {
                new(FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, new CSharpType(typeof(Dictionary<string, object>[])), "_flows"),
            });
            Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_tokenProvider"), "_tokenProvider should not be present - auth handled via AuthenticationPolicy parameter");

            // validate the field initialization
            var testName = TestContext.CurrentContext.Test.Name;
            Match match = Regex.Match(testName, @"\(([^)]*)\)");
            string? caseName = null;
            if (!match.Success)
            {
                Assert.Fail("Unable to parse test case name.");
            }
            caseName = match.Groups[1].Value;

            var expected = Helpers.GetExpectedFromFile($"{caseName}");
            ValueExpression? initValue = clientProvider.Fields.FirstOrDefault(f => f.Name == "_flows")?.InitializationValue;
            Assert.IsNotNull(initValue);

            var actual = initValue?.ToDisplayString();
            Assert.AreEqual(expected, actual);
        }

        [TestCaseSource(nameof(BuildAuthFieldsTestCases))]
        public void TestBuildAuthFields_NoAuth(List<InputParameter> inputParameters)
        {
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // fields here should not have anything related with auth
            bool authFieldFound = false;
            foreach (var field in clientProvider.Fields)
            {
                if (field.Name.EndsWith("Credential") || field.Name.Contains("Authorization"))
                {
                    authFieldFound = true;
                }
            }

            Assert.IsFalse(authFieldFound);
        }

        // validates the fields are built correctly when a client has sub-clients
        [TestCaseSource(nameof(SubClientFieldsTestCases), Category = SubClientsCategory)]
        public void TestBuildFields_WithSubClients(InputClient client, List<ExpectedFieldProvider> expectedFields)
        {
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            AssertHasFields(clientProvider, expectedFields);
        }

        // validates the credential fields do not exist within sub-clients
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = SubClientsCategory)]
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = OnlyUnsupportedAuthCategory)]
        public void TestBuildAuthFields_WithSubClients_NoAuth(InputClient client)
        {
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // fields here should not have anything related with auth
            bool authFieldFound = false;
            foreach (var field in clientProvider.Fields)
            {
                if (field.Name.EndsWith("Credential") || field.Name.Contains("Authorization"))
                {
                    authFieldFound = true;
                }
            }

            Assert.IsFalse(authFieldFound);
        }

        // validates the credential fields are built correctly when a client has sub-clients
        [TestCaseSource(nameof(WithSubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{KeyAuthCategory}")]
        [TestCaseSource(nameof(WithSubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(WithSubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{OAuth2CategoryOtherCredType}")]
        [TestCaseSource(nameof(WithSubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{KeyAuthCategory},{OAuth2Category}")]
        public void TestBuildAuthFields_WithSubClients_WithAuth(InputClient client)
        {
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            if (_hasKeyAuth)
            {
                // key auth should have AuthorizationHeader const field; _keyCredential is no longer stored
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Const, new CSharpType(typeof(string)), "AuthorizationHeader"),
                });
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_keyCredential"));
            }
            if (_hasOAuth2)
            {
                // oauth2 auth should have _flows field; _tokenProvider is no longer stored
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, new CSharpType(typeof(Dictionary<string, object>[])), "_flows"),
                });
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_tokenProvider"));
            }
            if (_hasOAuth2WithOtherCredType)
            {
                AssertHasFields(clientProvider, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.Static | FieldModifiers.ReadOnly, new CSharpType(typeof(string[])), "AuthorizationScopes"),
                });
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name == "_tokenCredential"));
            }
        }

        // validates the credential fields do not exist within sub-clients
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{KeyAuthCategory}")]
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{OAuth2CategoryOtherCredType}")]
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = $"{SubClientsCategory},{KeyAuthCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(SubClientAuthFieldsTestCases), Category = OnlyUnsupportedAuthCategory)]
        public void TestBuildAuthFields_SubClients_WithAuth(InputClient client)
        {
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            if (_hasKeyAuth)
            {
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Equals("_keyCredential")));
            }
            if (_hasOAuth2)
            {
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Equals("_flows")));
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Equals("_tokenCredential")));
            }
            if (_hasOAuth2WithOtherCredType)
            {
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Equals("_tokenCredential")));
                Assert.IsFalse(clientProvider.Fields.Any(f => f.Name.Equals("AuthorizationScopes")));
            }
        }

        [TestCaseSource(nameof(BuildConstructorsTestCases))]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = KeyAuthCategory)]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = OAuth2Category)]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = $"{KeyAuthCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = OnlyUnsupportedAuthCategory)]
        public void TestBuildConstructors_PrimaryConstructor(List<InputParameter> inputParameters)
        {
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            // The implementation constructors are now INTERNAL (taking AuthenticationPolicy? as first param)
            var implementationConstructors = constructors.Where(
                c => c.Signature?.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToArray();

            // There is always exactly 1 internal implementation constructor, regardless of auth type count.
            // All public credential constructors are pass-throughs to this single implementation constructor.
            int expectedImplCtorCount = 1;
            Assert.AreEqual(expectedImplCtorCount, implementationConstructors.Length);

            for (int i = 0; i < implementationConstructors.Length; i++)
            {
                ValidatePrimaryConstructor(implementationConstructors[i], inputParameters, i);
            }
        }

        [TestCaseSource(nameof(BuildConstructorsTestCases))]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = KeyAuthCategory)]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = OAuth2Category)]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = OAuth2CategoryOtherCredType)]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = $"{KeyAuthCategory},{OAuth2Category}")]
        [TestCaseSource(nameof(BuildConstructorsTestCases), Category = OnlyUnsupportedAuthCategory)]
        public void TestBuildConstructors_SecondaryConstructor(List<InputParameter> inputParameters)
        {
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            var implementationConstructors = constructors.Where(
                c => c.Signature?.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToArray();
            var secondaryPublicConstructors = constructors.Where(
                c => c.Signature?.Initializer != null &&
                     c.Signature?.Modifiers == MethodSignatureModifiers.Public &&
                     !IsSettingsConstructor(c) &&
                     !IsPrimaryPassThroughConstructor(c)).ToArray();
            var primaryPassThroughConstructors = constructors.Where(IsPrimaryPassThroughConstructor).ToArray();

            // Check if endpoint has a default value
            var endpointParam = inputParameters.FirstOrDefault(p => p is InputEndpointParameter ep && ep.IsEndpoint);
            bool hasDefaultEndpoint = endpointParam?.DefaultValue != null;

            // Calculate expected secondary constructor count:
            // - For only unsupported auth: 0
            // - For each auth type when endpoint has default value and auth is present: 2 (simple + with options)
            // - For each auth type when endpoint is required or no auth: 1
            int expectedSecondaryCtorCount;
            if (_hasOnlyUnsupportedAuth)
            {
                expectedSecondaryCtorCount = 0;
            }
            else
            {
                int authCount = (_hasKeyAuth ? 1 : 0) + (_hasOAuth2 || _hasOAuth2WithOtherCredType ? 1 : 0);
                if (authCount == 0)
                {
                    // No auth case - just 1 secondary constructor
                    expectedSecondaryCtorCount = 1;
                }
                else if (hasDefaultEndpoint)
                {
                    // When endpoint has default value and auth is present, we have 2 constructors per auth type:
                    // - Client(credential)
                    // - Client(credential, options)
                    expectedSecondaryCtorCount = authCount * 2;
                }
                else
                {
                    // When endpoint is required, we have 1 constructor per auth type
                    expectedSecondaryCtorCount = authCount;
                }
            }

            Assert.AreEqual(expectedSecondaryCtorCount, secondaryPublicConstructors.Length);
            foreach (var secondaryPublicConstructor in secondaryPublicConstructors)
            {
                ValidateSecondaryConstructor(primaryPassThroughConstructors, secondaryPublicConstructor, inputParameters);
            }
        }

        [TestCase]
        public void TestBuildConstructors_ForSubClient_NoAuth()
        {
            var clientProvider = new ClientProvider(_animalClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            Assert.AreEqual(2, constructors.Count);
            var internalConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(internalConstructor);
            // in the no auth case, the ctor no longer has the credentail parameter therefore here we expect 2 parameters.
            var ctorParams = internalConstructor?.Signature?.Parameters;
            Assert.AreEqual(2, ctorParams?.Count);

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        [TestCase(Category = KeyAuthCategory)]
        [TestCase(Category = OAuth2Category)]
        public void TestBuildConstructors_ForSubClient_KeyAuthOrOAuth2Auth()
        {
            var clientProvider = new ClientProvider(_animalClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            Assert.AreEqual(2, constructors.Count);
            var internalConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(internalConstructor);
            // when there is only one approach of auth, we still have 2 parameters in the ctor because we should not have auth parameter in sub-client
            var ctorParams = internalConstructor?.Signature?.Parameters;
            Assert.AreEqual(2, ctorParams?.Count);

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        [TestCase(Category = $"{KeyAuthCategory},{OAuth2Category}")]
        public void TestBuildConstructors_ForSubClient_BothAuth()
        {
            var clientProvider = new ClientProvider(_animalClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            Assert.AreEqual(2, constructors.Count);
            var internalConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(internalConstructor);
            // when we have both auths, we still have 2 parameters in the ctor, because we should not have auth parameters in sub-client
            var ctorParams = internalConstructor?.Signature?.Parameters;
            Assert.AreEqual(2, ctorParams?.Count);

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        /// <summary>
        /// Tests that when a client has a default endpoint and auth, we generate a simplified constructor
        /// that accepts auth + options (using the default endpoint).
        /// This is the pattern: Client(tokenProvider, options) with default endpoint.
        /// </summary>
        [TestCase(Category = KeyAuthCategory)]
        [TestCase(Category = OAuth2Category)]
        public void TestBuildConstructors_SimplifiedWithOptions_WhenDefaultEndpoint()
        {
            var inputParameters = new List<InputParameter>
            {
                InputFactory.EndpointParameter(
                    KnownParameters.Endpoint.Name,
                    InputPrimitiveType.String,
                    defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                    scope: InputParameterScope.Client,
                    isEndpoint: true)
            };
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            // Get secondary constructors (not primary pass-through, not settings constructor)
            var secondaryPublicConstructors = constructors.Where(
                c => c.Signature?.Initializer != null &&
                     c.Signature?.Modifiers == MethodSignatureModifiers.Public &&
                     !IsSettingsConstructor(c) &&
                     !IsPrimaryPassThroughConstructor(c)).ToList();

            // We should have 2 secondary constructors per auth type:
            // 1. Client(credential) - simple with just auth
            // 2. Client(credential, options) - simplified with auth + options
            Assert.AreEqual(2, secondaryPublicConstructors.Count);

            // Verify the settings constructor also exists
            var settingsConstructor = constructors.FirstOrDefault(IsSettingsConstructor);
            Assert.IsNotNull(settingsConstructor, "Expected a settings constructor");

            // Verify the simple constructor exists (just auth)
            var simpleConstructor = secondaryPublicConstructors.FirstOrDefault(c => c.Signature.Parameters.Count == 1);
            Assert.IsNotNull(simpleConstructor, "Expected a simple constructor with just auth parameter");
            var simpleCtorParamName = simpleConstructor!.Signature.Parameters[0].Name;
            Assert.IsTrue(simpleCtorParamName == "credential" || simpleCtorParamName == "tokenProvider",
                $"Expected auth parameter, got {simpleCtorParamName}");

            // Verify the simplified constructor with options exists (auth + options)
            var simplifiedWithOptionsConstructor = secondaryPublicConstructors.FirstOrDefault(c => c.Signature.Parameters.Count == 2);
            Assert.IsNotNull(simplifiedWithOptionsConstructor, "Expected a simplified constructor with auth + options parameters");

            var firstParamName = simplifiedWithOptionsConstructor!.Signature.Parameters[0].Name;
            Assert.IsTrue(firstParamName == "credential" || firstParamName == "tokenProvider",
                $"Expected first param to be auth parameter, got {firstParamName}");

            var secondParamName = simplifiedWithOptionsConstructor.Signature.Parameters[1].Name;
            Assert.AreEqual("options", secondParamName, "Expected second param to be 'options'");

            // Verify the initializer correctly passes the options parameter
            var initializerArgs = simplifiedWithOptionsConstructor.Signature.Initializer?.Arguments;
            Assert.IsNotNull(initializerArgs);
            Assert.AreEqual(3, initializerArgs!.Count, "Initializer should have 3 arguments: endpoint, auth, options");

            // The third argument should be the options parameter reference (not a new instance)
            // When the options parameter is passed through, it will be a ParameterReferenceExpression
            var optionsArg = initializerArgs[2];
            // Verify it's not a NewInstanceExpression (which would indicate a default value is being used)
            Assert.IsFalse(optionsArg is NewInstanceExpression, "Options argument should be the parameter itself, not a new instance");
        }

        [TestCase(Category = KeyAuthCategory)]
        [TestCase(Category = OAuth2Category)]
        public void TestBuildConstructors_SettingsConstructor()
        {
            var inputParameters = new List<InputParameter>
            {
                InputFactory.EndpointParameter(
                    KnownParameters.Endpoint.Name,
                    InputPrimitiveType.String,
                    defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                    scope: InputParameterScope.Client,
                    isEndpoint: true)
            };
            var client = InputFactory.Client(TestClientName, parameters: [.. inputParameters]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;
            var settingsConstructor = constructors.FirstOrDefault(IsSettingsConstructor);
            Assert.IsNotNull(settingsConstructor, "Expected a settings constructor");

            // Validate it's public
            Assert.AreEqual(MethodSignatureModifiers.Public, settingsConstructor!.Signature.Modifiers);

            // Validate it has exactly 1 parameter: settings
            Assert.AreEqual(1, settingsConstructor.Signature.Parameters.Count);
            Assert.AreEqual("settings", settingsConstructor.Signature.Parameters[0].Name);

            // Validate it's a pass-through (body is empty)
            Assert.AreEqual(MethodBodyStatement.Empty, settingsConstructor.BodyStatements);

            // Validate it has a this(...) initializer
            var initializer = settingsConstructor.Signature.Initializer;
            Assert.IsNotNull(initializer);
            Assert.IsFalse(initializer!.IsBase, "Settings constructor should use this() initializer, not base()");
        }

        [Test]
        public void TestBuildConstructors_NoSettingsConstructor_WhenNoEndpoint()
        {
            // No endpoint parameter — settings constructor should not be generated
            var client = InputFactory.Client(TestClientName);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;
            var settingsConstructor = constructors.FirstOrDefault(IsSettingsConstructor);
            Assert.IsNull(settingsConstructor, "Settings constructor should not be generated when there is no endpoint");
        }

        [Test]
        public void TestBuildConstructors_DeduplicatesParametersBySerializedName()
        {
            // Scenario: Client has a parameter with name "indexName" and serializedName "indexName"
            // An operation has a parameter with name "name" but serializedName "indexName" (renamed via @encodedName)
            // These should be deduplicated by SerializedName to avoid duplicate fields
            var clientParameter = InputFactory.PathParameter(
                "indexName",
                InputPrimitiveType.String,
                serializedName: "indexName",
                isRequired: true,
                scope: InputParameterScope.Client);

            var operationParameterWithRenamedName = InputFactory.PathParameter(
                "name",
                InputPrimitiveType.String,
                serializedName: "indexName",
                isRequired: true,
                scope: InputParameterScope.Client);

            var endpointParameter = InputFactory.EndpointParameter(
                KnownParameters.Endpoint.Name,
                InputPrimitiveType.String,
                defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                scope: InputParameterScope.Client,
                isEndpoint: true);

            var operation = InputFactory.Operation(
                "TestOperation",
                parameters: [operationParameterWithRenamedName]);

            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("TestMethod", operation)],
                parameters: [endpointParameter, clientParameter]);

            var clientProvider = new ClientProvider(client);
            Assert.IsNotNull(clientProvider);

            // Verify the field with the same SerializedName only appears once
            var fields = clientProvider.Fields;
            var indexNameFields = fields.Where(f => f.Name == "_indexName" || f.Name == "_name").ToList();

            // Should only have ONE field for the "indexName" serialized name, not two
            Assert.AreEqual(1, indexNameFields.Count,
                $"Expected 1 field for serializedName 'indexName', but found {indexNameFields.Count}: {string.Join(", ", indexNameFields.Select(f => f.Name))}");

            // The field should be "_indexName" (from the client parameters, which comes first)
            Assert.AreEqual("_indexName", indexNameFields[0].Name,
                "Expected the client parameter field '_indexName' to be used, not the operation parameter field '_name'");
        }

        [Test]
        public void TestBuildConstructors_DeduplicatesConstructorParametersBySerializedName()
        {
            // Scenario: Create required client-scoped parameters that will become constructor parameters.
            // We use required non-path/query/header parameters that are client-scoped - these become fields
            // and their corresponding parameters in the constructor.
            var clientParameter = InputFactory.QueryParameter(
                "indexName",
                InputPrimitiveType.String,
                serializedName: "indexName",
                isRequired: true,
                scope: InputParameterScope.Client);

            var operationParameterWithRenamedName = InputFactory.QueryParameter(
                "name",
                InputPrimitiveType.String,
                serializedName: "indexName",
                isRequired: true,
                scope: InputParameterScope.Client);

            var endpointParameter = InputFactory.EndpointParameter(
                KnownParameters.Endpoint.Name,
                InputPrimitiveType.String,
                defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                scope: InputParameterScope.Client,
                isEndpoint: true);

            var operation = InputFactory.Operation(
                "TestOperation",
                parameters: [operationParameterWithRenamedName]);

            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("TestMethod", operation)],
                parameters: [endpointParameter, clientParameter]);

            var clientProvider = new ClientProvider(client);
            Assert.IsNotNull(clientProvider);

            // Verify the underlying parameter deduplication occurred
            var allClientParameters = clientProvider.GetType()
                .GetMethod("GetAllClientParameters", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
                ?.Invoke(clientProvider, null) as IReadOnlyList<InputParameter>;

            Assert.IsNotNull(allClientParameters, "Could not access GetAllClientParameters method");

            var indexNameParams = allClientParameters!.Where(p =>
                p.Name == "indexName" || p.Name == "name").ToList();

            // Should only have 1 parameter after deduplication by name
            Assert.AreEqual(1, indexNameParams.Count,
                $"Expected 1 parameter after deduplication, but found {indexNameParams.Count}: {string.Join(", ", indexNameParams.Select(p => p.Name))}");

            // Should be the client parameter (first one wins)
            Assert.AreEqual("indexName", indexNameParams[0].Name,
                "Expected the client parameter 'indexName' to be preserved after deduplication");
        }

        // Tests for InitializedBy flag behavior

        [Test]
        public void TestBuildConstructors_ForSubClient_InitializedByIndividually_HasPublicConstructors()
        {
            var endpointParameter = InputFactory.EndpointParameter(
                KnownParameters.Endpoint.Name,
                InputPrimitiveType.String,
                defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                scope: InputParameterScope.Client,
                isEndpoint: true);
            var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParameter]);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [endpointParameter],
                initializedBy: InputClientInitializedBy.Individually);

            MockHelpers.LoadMockGenerator(
                auth: () => new(new InputApiKeyAuth("mock", null), null),
                clients: () => [parentClient]);

            var clientProvider = new ClientProvider(subClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            // Should have mocking constructor + public constructors
            var publicConstructors = constructors.Where(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Public).ToList();
            Assert.IsTrue(publicConstructors.Count > 0, "SubClient with InitializedBy.Individually should have public constructors");

            // Primary public constructor should have auth credential param and chain to internal via this(...)
            var primaryConstructor = publicConstructors.FirstOrDefault(
                c => c.Signature?.Parameters.Any(p => p.Name == "options") == true && c.Signature?.Initializer != null);
            Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor with initializer");

            // Should have a credential parameter (from the mock api key auth)
            Assert.IsTrue(primaryConstructor!.Signature.Parameters.Any(p => p.Name == "credential"),
                "SubClient with InitializedBy.Individually should have credential parameter in public constructor");

            // Should have internal AuthenticationPolicy constructor
            var internalConstructors = constructors.Where(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToList();
            Assert.IsTrue(internalConstructors.Count > 0, "SubClient with InitializedBy.Individually should have internal constructor");
            var authPolicyConstructor = internalConstructors.FirstOrDefault(
                c => c.Signature?.Parameters.Any(p => p.Type.Name == nameof(AuthenticationPolicy)) == true);
            Assert.IsNotNull(authPolicyConstructor, "SubClient with InitializedBy.Individually should have internal AuthenticationPolicy constructor");

            // Should have a Settings constructor
            Assert.IsNotNull(clientProvider.ClientSettings, "SubClient with InitializedBy.Individually should have ClientSettings");
            var settingsConstructor = publicConstructors.FirstOrDefault(
                c => c.Signature?.Parameters.Count == 1 && c.Signature.Parameters[0].Name == "settings");
            Assert.IsNotNull(settingsConstructor, "SubClient with InitializedBy.Individually should have Settings constructor");

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        [Test]
        public void TestBuildConstructors_ForSubClient_InitializedByParentOnly_HasOnlyInternalConstructor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                initializedBy: InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                auth: () => new(new InputApiKeyAuth("mock", null), null),
                clients: () => [parentClient]);

            var clientProvider = new ClientProvider(subClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            // Should have mocking constructor + internal constructor only
            var publicConstructors = constructors.Where(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Public).ToList();
            Assert.AreEqual(0, publicConstructors.Count, "SubClient with InitializedBy.Parent should not have public constructors");

            var internalConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(internalConstructor, "SubClient with InitializedBy.Parent should have internal constructor");

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        [Test]
        public void TestBuildConstructors_ForSubClient_InitializedByBoth_HasBothConstructors()
        {
            var endpointParameter = InputFactory.EndpointParameter(
                KnownParameters.Endpoint.Name,
                InputPrimitiveType.String,
                defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                scope: InputParameterScope.Client,
                isEndpoint: true);
            var parentClient = InputFactory.Client("ParentClient", parameters: [endpointParameter]);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [endpointParameter],
                initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                auth: () => new(new InputApiKeyAuth("mock", null), null),
                clients: () => [parentClient]);

            var clientProvider = new ClientProvider(subClient);

            Assert.IsNotNull(clientProvider);

            var constructors = clientProvider.Constructors;

            // Should have public constructors
            var publicConstructors = constructors.Where(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Public).ToList();
            Assert.IsTrue(publicConstructors.Count > 0, "SubClient with InitializedBy.Individually | Parent should have public constructors");

            // Primary public constructor should have auth credential param and chain to internal via this(...)
            var primaryConstructor = publicConstructors.FirstOrDefault(
                c => c.Signature?.Parameters.Any(p => p.Name == "options") == true && c.Signature?.Initializer != null);
            Assert.IsNotNull(primaryConstructor, "SubClient with InitializedBy.Individually should have primary public constructor with initializer");

            // Should also have internal constructors (sub-client internal + AuthenticationPolicy internal)
            var internalConstructors = constructors.Where(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Internal).ToList();
            Assert.IsTrue(internalConstructors.Count > 0, "SubClient with InitializedBy.Individually | Parent should have internal constructor");

            // Should have a Settings constructor
            Assert.IsNotNull(clientProvider.ClientSettings, "SubClient with InitializedBy.Individually | Parent should have ClientSettings");
            var settingsConstructor = publicConstructors.FirstOrDefault(
                c => c.Signature?.Parameters.Count == 1 && c.Signature.Parameters[0].Name == "settings");
            Assert.IsNotNull(settingsConstructor, "SubClient with InitializedBy.Individually | Parent should have Settings constructor");

            var mockingConstructor = constructors.FirstOrDefault(
                c => c.Signature?.Modifiers == MethodSignatureModifiers.Protected);
            Assert.IsNotNull(mockingConstructor);
        }

        [Test]
        public void TestBuildMethods_ForParent_InitializedByIndividually_NoSubClientAccessor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                initializedBy: InputClientInitializedBy.Individually);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should NOT have a factory method for the subclient
            var factoryMethod = parentProvider.Methods.FirstOrDefault(
                m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient");
            Assert.IsNull(factoryMethod, "Parent should not have factory method for subclient with InitializedBy.Individually (without Parent)");

            // The parent should NOT have the caching field for the subclient
            var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient");
            Assert.IsNull(cachingField, "Parent should not have caching field for subclient with InitializedBy.Individually (without Parent)");
        }

        [Test]
        public void TestBuildMethods_ForParent_InitializedByParent_HasSubClientAccessor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                initializedBy: InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should have a factory method for the subclient
            var factoryMethod = parentProvider.Methods.FirstOrDefault(
                m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient");
            Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with InitializedBy.Parent");

            // The parent should have the caching field for the subclient
            var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient");
            Assert.IsNotNull(cachingField, "Parent should have caching field for subclient with InitializedBy.Parent");
        }

        [Test]
        public void TestBuildMethods_ForParent_InitializedByBoth_HasSubClientAccessor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should have a factory method for the subclient
            var factoryMethod = parentProvider.Methods.FirstOrDefault(
                m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient");
            Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with InitializedBy.Individually | Parent");

            // The parent should have the caching field for the subclient
            var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient");
            Assert.IsNotNull(cachingField, "Parent should have caching field for subclient with InitializedBy.Individually | Parent");
        }

        [Test]
        public void TestBuildMethods_ForParent_InitializedByBoth_WithSubClientParams_HasParameterizedAccessor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [subClientParam],
                initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should have a factory method for the subclient
            var factoryMethod = parentProvider.Methods.FirstOrDefault(
                m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient");
            Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with parameters");

            // The accessor method should include the subclient's extra parameters
            Assert.IsNotNull(factoryMethod!.Signature, "Factory method should have a signature");
            Assert.AreEqual(1, factoryMethod.Signature!.Parameters.Count,
                "Accessor method should include subclient parameters not present on parent");
            Assert.AreEqual("resourceId", factoryMethod.Signature.Parameters[0].Name,
                "Accessor method parameter should be the subclient's extra parameter");
        }

        [Test]
        public void TestBuildFields_ForParent_InitializedByBoth_WithSubClientParams_NoCachingField()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [subClientParam],
                initializedBy: InputClientInitializedBy.Individually | InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should NOT have a caching field for the subclient when the accessor requires parameters,
            // since caching is not appropriate when different parameter values produce different client instances.
            var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient");
            Assert.IsNull(cachingField, "Parent should not have caching field for subclient that has subclient-specific parameters in its accessor");
        }

        [Test]
        public void TestBuildMethods_ForParent_InitializedByParentOnly_WithSubClientParams_HasParameterizedAccessor()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [subClientParam],
                initializedBy: InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should have a factory method for the subclient
            var factoryMethod = parentProvider.Methods.FirstOrDefault(
                m => m.Signature?.Name == "GetSubClient" || m.Signature?.Name == "GetSubClientClient");
            Assert.IsNotNull(factoryMethod, "Parent should have factory method for subclient with parameters");

            // The accessor method should include the subclient's extra parameters
            Assert.IsNotNull(factoryMethod!.Signature, "Factory method should have a signature");
            Assert.AreEqual(1, factoryMethod.Signature!.Parameters.Count,
                "Accessor method should include subclient parameters not present on parent");
            Assert.AreEqual("resourceId", factoryMethod.Signature.Parameters[0].Name,
                "Accessor method parameter should be the subclient's extra parameter");
        }

        [Test]
        public void TestBuildFields_ForParent_InitializedByParentOnly_WithSubClientParams_NoCachingField()
        {
            var parentClient = InputFactory.Client("ParentClient");
            var subClientParam = InputFactory.PathParameter("resourceId", InputPrimitiveType.String, scope: InputParameterScope.Client);
            var subClient = InputFactory.Client(
                "SubClient",
                parent: parentClient,
                parameters: [subClientParam],
                initializedBy: InputClientInitializedBy.Parent);

            MockHelpers.LoadMockGenerator(
                clients: () => [parentClient]);

            var parentProvider = new ClientProvider(parentClient);

            Assert.IsNotNull(parentProvider);

            // The parent should NOT have a caching field for the subclient when the accessor requires parameters,
            // since caching is not appropriate when different parameter values produce different client instances.
            var cachingField = parentProvider.Fields.FirstOrDefault(f => f.Name == "_cachedSubClient");
            Assert.IsNull(cachingField, "Parent should not have caching field for subclient that has subclient-specific parameters in its accessor");
        }

        private void ValidatePrimaryConstructor(
            ConstructorProvider primaryPublicConstructor,
            List<InputParameter> inputParameters,
            int ctorIndex,
            [CallerMemberName] string method = "",
            [CallerFilePath] string filePath = "")
        {
            var primaryCtorParams = primaryPublicConstructor?.Signature?.Parameters;
            // The internal implementation constructor always has 3 parameters: authenticationPolicy, endpoint, options
            var expectedPrimaryCtorParamCount = 3;

            Assert.AreEqual(expectedPrimaryCtorParamCount, primaryCtorParams?.Count);

            // the first should be authenticationPolicy (AuthenticationPolicy?)
            var authPolicyParam = primaryCtorParams?[0];
            Assert.AreEqual("authenticationPolicy", authPolicyParam?.Name);
            Assert.IsTrue(authPolicyParam?.Type.Equals(new CSharpType(typeof(AuthenticationPolicy), isNullable: true)));

            // the second should be endpoint
            var endpointParam = primaryCtorParams?[1];
            Assert.AreEqual(KnownParameters.Endpoint.Name, endpointParam?.Name);

            if (endpointParam?.DefaultValue != null)
            {
                var inputEndpointParam = inputParameters
                    .FirstOrDefault(p => p is InputEndpointParameter endpointParameter && endpointParameter.IsEndpoint);
                var parsedValue = inputEndpointParam?.DefaultValue?.Value;
                Assert.AreEqual(Literal(parsedValue), endpointParam?.InitializationValue);
            }

            // the last parameter should be the options
            var optionsParam = primaryCtorParams?[^1];
            Assert.AreEqual("options", optionsParam?.Name);

            // validate the body of the primary ctor
            var caseName = TestContext.CurrentContext.Test.Properties.Get("caseName");
            var expected = Helpers.GetExpectedFromFile($"{caseName},{_hasKeyAuth},{_hasOAuth2},{ctorIndex}", method, filePath);
            var primaryCtorBody = primaryPublicConstructor?.BodyStatements;
            Assert.IsNotNull(primaryCtorBody);
            Assert.AreEqual(expected, primaryCtorBody?.ToDisplayString());
        }

        private void ValidateSecondaryConstructor(
            IReadOnlyList<ConstructorProvider> primaryConstructors,
            ConstructorProvider secondaryPublicConstructor,
            List<InputParameter> inputParameters)
        {
            var ctorParams = secondaryPublicConstructor.Signature?.Parameters;

            // secondary ctor should consist of all required parameters + auth parameter (when present)
            // Additionally, when endpoint has default value and auth is present, there may be
            // a constructor with auth + options
            var requiredParams = inputParameters.Where(p => p.IsRequired).ToList();
            var authParameterCount = _hasSupportedAuth ? 1 : 0;

            // Check if this is the new simplified constructor with options
            var hasOptionsParam = ctorParams?.Any(p => p.Name == "options") ?? false;
            var expectedParamCount = requiredParams.Count + authParameterCount + (hasOptionsParam ? 1 : 0);
            Assert.AreEqual(expectedParamCount, ctorParams?.Count);

            var endpointParam = ctorParams?.FirstOrDefault(p => p.Name == KnownParameters.Endpoint.Name);

            if (requiredParams.Count == 0)
            {
                // auth should be the only parameter if endpoint is optional when there is auth
                if (_hasSupportedAuth)
                {
                    var expectedName = ctorParams?[0].Type?.Equals(ClientPipelineProvider.Instance.TokenCredentialType!) == true
                        ? "tokenProvider"
                        : "credential";
                    Assert.AreEqual(expectedName, ctorParams?[0].Name);

                    // If there's an options param, it should be last
                    if (hasOptionsParam)
                    {
                        Assert.AreEqual("options", ctorParams?[^1].Name);
                    }
                }
                else
                {
                    // when there is no auth, the ctor should not have parameters
                    Assert.AreEqual(0, ctorParams?.Count);
                }
            }
            else
            {
                // otherwise, it should only consist of the auth parameter
                Assert.AreEqual(KnownParameters.Endpoint.Name, ctorParams?[0].Name);
                if (_hasSupportedAuth)
                {
                    var expectedName = ctorParams?[1].Type?.Equals(ClientPipelineProvider.Instance.TokenCredentialType!) == true
                        ? "tokenProvider"
                        : "credential";
                    Assert.AreEqual(expectedName, ctorParams?[1].Name);
                }
            }

            Assert.AreEqual(MethodBodyStatement.Empty, secondaryPublicConstructor?.BodyStatements);

            // validate the initializer
            var initializer = secondaryPublicConstructor?.Signature?.Initializer;
            Assert.NotNull(initializer);
            Assert.IsTrue(primaryConstructors.Any(pc => pc.Signature.Parameters.Count == initializer?.Arguments.Count));
        }

        private static bool IsSettingsConstructor(ConstructorProvider c) =>
            c.Signature?.Initializer != null &&
            c.Signature?.Modifiers == MethodSignatureModifiers.Public &&
            c.Signature.Parameters.Any(p => p.Name == "settings");

        /// <summary>
        /// Identifies the public primary pass-through constructor (calls the internal implementation constructor).
        /// It is the public constructor that has both an endpoint parameter AND an options parameter.
        /// Secondary constructors never have both endpoint and options (they have a subset).
        /// </summary>
        private static bool IsPrimaryPassThroughConstructor(ConstructorProvider c) =>
            c.Signature?.Initializer != null &&
            c.Signature?.Modifiers == MethodSignatureModifiers.Public &&
            !IsSettingsConstructor(c) &&
            c.Signature.Parameters.Any(p => p.Name == KnownParameters.Endpoint.Name) &&
            c.Signature.Parameters.Any(p => p.Name == "options");

        [TestCaseSource(nameof(EndpointParamInitializationValueTestCases))]
        public void EndpointInitializationValue(InputParameter endpointParameter, ValueExpression? expectedValue)
        {
            var client = InputFactory.Client(TestClientName, parameters: [endpointParameter]);
            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);
            // find the endpoint parameter from the primary constructor
            var primaryConstructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature?.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            var endpoint = primaryConstructor?.Signature?.Parameters?.FirstOrDefault(p => p.Name == KnownParameters.Endpoint.Name);

            Assert.IsNotNull(endpoint);
            Assert.AreEqual(expectedValue?.GetType(), endpoint?.InitializationValue?.GetType());
            if (expectedValue != null)
            {
                Assert.IsTrue(endpoint?.InitializationValue is NewInstanceExpression);
            }
        }

        [TestCase(true)]
        [TestCase(false)]
        public void TestGetClientOptions(bool isSubClient)
        {
            InputClient? parentClient = null;
            if (isSubClient)
            {
                parentClient = InputFactory.Client("parent");
            }

            var client = InputFactory.Client(TestClientName, parent: parentClient, initializedBy: isSubClient ? InputClientInitializedBy.Parent : InputClientInitializedBy.Individually);
            var clientProvider = new ClientProvider(client);
            Assert.IsNotNull(clientProvider);

            if (isSubClient)
            {
                Assert.IsNull(clientProvider.ClientOptions);
            }
            else
            {
                Assert.IsNotNull(clientProvider.ClientOptions);
            }
        }

        [TestCaseSource(nameof(SubClientFactoryMethodTestCases), Category = SubClientsCategory)]
        public void TestSubClientAccessorFactoryMethods(InputClient client, bool hasSubClients)
        {
            var clientProvider = new ClientProvider(client);
            Assert.IsNotNull(clientProvider);

            var methods = clientProvider.Methods;
            List<MethodProvider> subClientAccessorFactoryMethods = [];
            foreach (var method in methods)
            {
                Assert.IsInstanceOf<ScmMethodProvider>(method);
                var methodSignature = method.Signature;
                if (methodSignature != null &&
                    methodSignature.Name.StartsWith("Get") &&
                    methodSignature.Modifiers.HasFlag(MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual))
                {
                    subClientAccessorFactoryMethods.Add(method);
                }
            }

            if (hasSubClients)
            {
                Assert.AreEqual(1, subClientAccessorFactoryMethods.Count);
                var factoryMethod = subClientAccessorFactoryMethods[0];
                Assert.AreEqual(0, factoryMethod.Signature?.Parameters.Count);

                // method body should not be empty
                Assert.AreNotEqual(MethodBodyStatement.Empty, factoryMethod.BodyStatements);
            }
            else
            {
                Assert.AreEqual(0, subClientAccessorFactoryMethods.Count);
            }
        }

        [Test]
        public void ValidateQueryParamDiff()
        {
            MockHelpers.LoadMockGenerator();

            //protocol and convenience methods should have a different type for enum query parameters
            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(GetEnumQueryParamClient());
            Assert.IsNotNull(clientProvider);
            var methods = clientProvider!.Methods;
            // all methods should be ScmMethodProvider instances
            Assert.IsTrue(methods.All(m => m is ScmMethodProvider));
            //4 methods, sync / async + protocol / convenience
            Assert.AreEqual(4, methods.Count);
            //two methods need to have the query parameter as an enum
            Assert.AreEqual(2, methods.Where(m => m.Signature.Parameters.Any(p => p.Name == "queryParam" && p.Type.Name == "InputEnum")).Count());
            //two methods need to have the query parameter as an string
            Assert.AreEqual(2, methods.Where(m => m.Signature.Parameters.Any(p => p.Name == "queryParam" && p.Type.IsFrameworkType && p.Type.FrameworkType == typeof(string))).Count());
        }

        // Validates that when generateConvenienceMethod is false (i.e. @convenientAPI(false)),
        [Test]
        public void ValidateEnumQueryParamWithoutConvenienceMethod()
        {
            MockHelpers.LoadMockGenerator();

            var enumType = InputFactory.StringEnum(
                "InputEnum",
                [("value1", "value1"), ("value2", "value2")],
                usage: InputModelTypeUsage.Input,
                isExtensible: true);
            var inputClient = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod(
                        "test",
                        InputFactory.Operation(
                            "Operation",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "queryParam",
                                    enumType,
                                    isRequired: true)
                            ],
                            generateConvenienceMethod: false),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "queryParam",
                                enumType,
                                isRequired: true,
                                location: InputRequestLocation.Query)
                        ])
                ]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
            Assert.IsNotNull(clientProvider);
            var methods = clientProvider!.Methods;

            // only protocol methods (sync + async), no convenience methods
            Assert.AreEqual(2, methods.Count);
            Assert.IsTrue(methods.All(m => m is ScmMethodProvider));

            Assert.AreEqual(2, methods.Where(m => m.Signature.Parameters.Any(
                p => p.Name == "queryParam" && p.Type.IsFrameworkType && p.Type.FrameworkType == typeof(string))).Count());

            Assert.AreEqual(0, methods.Where(m => m.Signature.Parameters.Any(
                p => p.Name == "queryParam" && p.Type.Name == "InputEnum")).Count());

            var enumProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateEnum(enumType);
            Assert.IsNotNull(enumProvider);
        }

        [TestCase(true)]
        [TestCase(false)]
        public void ValidateQueryParamWriterDiff(bool isAsync)
        {
            MockHelpers.LoadMockGenerator(
                createClientCore: (client) => new ValidateQueryParamDiffClientProvider(client, isAsync));

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(GetEnumQueryParamClient());
            Assert.IsNotNull(clientProvider);

            TypeProviderWriter writer = new(clientProvider!);
            var codeFile = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(isAsync.ToString()), codeFile.Content);
        }

        [TestCaseSource(nameof(TestNonBodyRequestParametersInBodyTestCases))]
        public void TestNonBodyRequestParametersInBodyWriterDiff(InputServiceMethod inputServiceMethod)
        {
            var inputClient = InputFactory.Client("testClient", methods: [inputServiceMethod]);
            MockHelpers.LoadMockGenerator(
                createClientCore: (client) => new TestNonBodyRequestParametersInBodyDiffClientProvider(inputClient));

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(GetEnumQueryParamClient());
            Assert.IsNotNull(clientProvider);

            TypeProviderWriter writer = new(clientProvider!);
            var codeFile = writer.Write();
            var caseName = TestContext.CurrentContext.Test.Properties.Get("caseName");
            Assert.AreEqual(Helpers.GetExpectedFromFile($"{caseName}"), codeFile.Content);
        }

        // This test validates that no public constructors are generated when the client has only unsupported auth
        [Test]
        public void ValidateConstructorsWhenUnsupportedAuth()
        {
            MockHelpers.LoadMockGenerator(
                createClientCore: (client) => new UnsupportedAuthClientProvider(client),
                auth: () => new InputAuth(null, null));

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(InputFactory.Client(TestClientName));
            Assert.IsNotNull(clientProvider);

            TypeProviderWriter writer = new(clientProvider!);
            var codeFile = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), codeFile.Content);
        }

        [Test]
        public void ValidateMethodSignatureUsesIEnumerable()
        {
            MockHelpers.LoadMockGenerator();
            List<InputMethodParameter> methodParameters = [InputFactory.MethodParameter("arrayParam", InputFactory.Array(InputPrimitiveType.String), isRequired: true, location: InputRequestLocation.Body)];
            List<InputParameter> parameters = [InputFactory.BodyParameter("arrayParam", InputFactory.Array(InputPrimitiveType.String))];
            var inputClient = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod(
                        "Foo",
                        InputFactory.Operation(
                            "Foo",
                            parameters: parameters),
                        parameters: methodParameters)
                ]);
            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);
            Assert.IsNotNull(clientProvider);
            var convenienceMethod = clientProvider!.Methods.FirstOrDefault(
                m => m.Signature.Name == "Foo" &&
                     !m.Signature.Parameters.Any(p => p.Type.Equals(typeof(RequestOptions))));
            Assert.IsNotNull(convenienceMethod);
            Assert.AreEqual(new CSharpType(typeof(IEnumerable<string>)), convenienceMethod!.Signature.Parameters[0].Type);
        }

        [TestCaseSource(nameof(ValidateClientWithSpreadTestCases))]
        public void ValidateClientWithSpread(InputClient inputClient)
        {
            var clientProvider = new ClientProvider(inputClient);
            var methods = clientProvider.Methods;

            Assert.AreEqual(4, methods.Count);

            var protocolMethods = methods.Where(m => m.Signature.Parameters.Any(p => p.Type.Equals(typeof(BinaryContent)))).ToList();
            Assert.AreEqual(2, protocolMethods.Count);
            Assert.AreEqual(2, protocolMethods[0].Signature.Parameters.Count);
            Assert.AreEqual(2, protocolMethods[1].Signature.Parameters.Count);

            Assert.AreEqual(new CSharpType(typeof(BinaryContent)), protocolMethods[0].Signature.Parameters[0].Type);
            Assert.AreEqual(new CSharpType(typeof(RequestOptions)), protocolMethods[0].Signature.Parameters[1].Type);
            Assert.AreEqual(new CSharpType(typeof(BinaryContent)), protocolMethods[1].Signature.Parameters[0].Type);
            Assert.AreEqual(new CSharpType(typeof(RequestOptions)), protocolMethods[1].Signature.Parameters[1].Type);

            var convenienceMethods = methods.Where(m => m.Signature.Parameters.Any(p => p.Type.Equals(typeof(string)))).ToList();
            Assert.AreEqual(2, convenienceMethods.Count);
            Assert.AreEqual(2, convenienceMethods[0].Signature.Parameters.Count);

            Assert.AreEqual(new CSharpType(typeof(string)), convenienceMethods[0].Signature.Parameters[0].Type);
            Assert.AreEqual("p1", convenienceMethods[0].Signature.Parameters[0].Name);
        }

        [TestCaseSource(nameof(RequestOptionsParameterInSignatureTestCases))]
        public void TestRequestOptionsParameterInSignature(InputServiceMethod inputServiceMethod, bool shouldBeOptional, bool hasOptionalParameter)
        {
            var client = InputFactory.Client(TestClientName, methods: [inputServiceMethod]);
            var clientProvider = new ClientProvider(client);
            var protocolMethods = clientProvider.Methods.Where(m => m.Signature.Parameters.Any(p => p.Type.Name == "RequestOptions")).ToList();
            var syncMethod = protocolMethods.FirstOrDefault(m => !m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(syncMethod);

            var requestOptionsParameterInSyncMethod = syncMethod!.Signature.Parameters.FirstOrDefault(p => p.Type.Name == "RequestOptions");
            Assert.IsNotNull(requestOptionsParameterInSyncMethod);
            Assert.AreEqual(shouldBeOptional, requestOptionsParameterInSyncMethod!.DefaultValue != null);

            var asyncMethod = protocolMethods.FirstOrDefault(m => m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(asyncMethod);

            var requestOptionsParameterInAsyncMethod = asyncMethod!.Signature.Parameters.FirstOrDefault(p => p.Type.Name == "RequestOptions");
            Assert.IsNotNull(requestOptionsParameterInAsyncMethod);
            Assert.AreEqual(shouldBeOptional, requestOptionsParameterInAsyncMethod!.DefaultValue != null);

            // request options should always be last parameter
            Assert.AreEqual("RequestOptions", syncMethod.Signature.Parameters[^1].Type.Name);
            Assert.AreEqual("RequestOptions", asyncMethod.Signature.Parameters[^1].Type.Name);

            if (shouldBeOptional)
            {
                Assert.IsNotNull(requestOptionsParameterInSyncMethod.DefaultValue);
                Assert.IsNotNull(requestOptionsParameterInAsyncMethod.DefaultValue);
            }

            if (shouldBeOptional && hasOptionalParameter)
            {
                var optionalParameter = syncMethod.Signature.Parameters[^2];
                // The optional parameter should be required in protocol method
                Assert.IsNull(optionalParameter.DefaultValue);
                // It should also be nullable for value types
                if (optionalParameter.Type.IsValueType)
                {
                    Assert.IsTrue(optionalParameter.Type.IsNullable);
                }
            }
        }

        [Test]
        public void TestApiVersionOfClient()
        {
            List<string> apiVersions = ["1.0", "2.0"];
            var enumValues = apiVersions.Select(a => (a, a));
            var inputEnum = InputFactory.StringEnum(
                "ServiceVersion", enumValues, usage: InputModelTypeUsage.ApiVersionEnum);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => apiVersions,
                inputEnums: () => [inputEnum]);
            var client = InputFactory.Client(TestClientName,
                methods: [
                    InputFactory.BasicServiceMethod(
                        "test",
                        InputFactory.Operation(
                            "OperationWithApiVersion",
                            parameters: [InputFactory.QueryParameter("apiVersion", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Client, isApiVersion: true)]))
                ]);
            var clientProvider = new ClientProvider(client);
            Assert.IsNotNull(clientProvider);

            /* verify that the client has apiVersion field */
            Assert.IsNotNull(clientProvider.Fields.FirstOrDefault(f => f.Name.Equals("_apiVersion")));

            /* verify that there is no apiVersion parameter in constructor. */
            var apiVersionParameter = clientProvider.Constructors.Select(c => c.Signature.Parameters.FirstOrDefault(p => p.Name.Equals("apiVersion"))).FirstOrDefault();
            Assert.IsNull(apiVersionParameter);

            /* verify the apiVersion assignment in constructor body */
            var primaryConstructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature?.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(primaryConstructor);
            var bodyStatements = primaryConstructor?.BodyStatements as MethodBodyStatements;
            Assert.IsNotNull(bodyStatements);
            Assert.IsTrue(bodyStatements!.Statements.Any(s => s.ToDisplayString().IndexOf("_apiVersion = options.Version;\n") != -1));

            var method = clientProvider.Methods.FirstOrDefault(m => m.Signature.Name.Equals("OperationWithApiVersion"));
            Assert.IsNotNull(method);
            /* verify that the method does not have apiVersion parameter */
            Assert.IsNull(method?.Signature.Parameters.FirstOrDefault(p => p.Name.Equals("apiVersion")));
        }

        [TestCaseSource(nameof(ValidateApiVersionPathParameterTestCases))]
        public void TestApiVersionPathParameterOfClient(InputClient inputClient)
        {
            List<string> apiVersions = ["value1", "value2"];
            var enumValues = apiVersions.Select(a => (a, a));
            var inputEnum = InputFactory.StringEnum("ServiceVersion", enumValues, usage: InputModelTypeUsage.ApiVersionEnum);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => apiVersions,
                inputEnums: () => [inputEnum]);

            var clientProvider = new ClientProvider(inputClient);
            Assert.IsNotNull(clientProvider);

            /* verify that the client has apiVersion field */
            var apiVersionField = clientProvider.Fields.FirstOrDefault(f => f.Name.Equals("_apiVersion"));
            Assert.IsNotNull(apiVersionField);
            Assert.AreEqual(new CSharpType(typeof(string)), apiVersionField?.Type);

            /* verify that there is no apiVersion parameter in constructor. */
            var apiVersionParameter = clientProvider.Constructors.Select(c => c.Signature.Parameters.FirstOrDefault(p => p.Name.Equals("apiVersion"))).FirstOrDefault();
            Assert.IsNull(apiVersionParameter);

            /* verify the apiVersion assignment in constructor body */
            var primaryConstructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature?.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);
            Assert.IsNotNull(primaryConstructor);
            var bodyStatements = primaryConstructor?.BodyStatements as MethodBodyStatements;
            Assert.IsNotNull(bodyStatements);
            Assert.IsTrue(bodyStatements!.Statements.Any(s => s.ToDisplayString().IndexOf("_apiVersion = options.Version;\n") != -1));

            var method = clientProvider.Methods.FirstOrDefault(m => m.Signature.Name.Equals("TestOperation"));
            Assert.IsNotNull(method);
            /* verify that the method does not have apiVersion parameter */
            Assert.IsNull(method?.Signature.Parameters.FirstOrDefault(p => p.Name.Equals("apiVersion")));
        }

        [Test]
        public void ApiVersionFieldIsStoredOnRootClient()
        {
            var rootClient = InputFactory.Client(
                "RootClient");
            var subClient = InputFactory.Client(
                "SubClient",
                parent: rootClient,
                initializedBy: InputClientInitializedBy.Parent,
                parameters:
                [
                    InputFactory.PathParameter("apiVersion", InputPrimitiveType.String, isRequired: true, scope: InputParameterScope.Client, isApiVersion: true),
                    InputFactory.PathParameter("someOtherParameter", InputPrimitiveType.Url, isRequired: true, scope: InputParameterScope.Client)
                ]);

            MockHelpers.LoadMockGenerator(clients: () => [rootClient]);

            var rootClientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(rootClient);
            Assert.IsNotNull(rootClientProvider);
            Assert.IsTrue(rootClientProvider!.Fields.Any(f => f.Name.Equals("_apiVersion")));
            // Other subclient parameters are not hoisted
            Assert.IsFalse(rootClientProvider.Fields.Any(f => f.Name.Equals("_someOtherParameter")));
        }

        [TestCase]
        public void ClientProviderIsAddedToLibrary()
        {
            var mockGenerator = MockHelpers.LoadMockGenerator(
                clients: () => [InputFactory.Client("test", clientNamespace: "test", doc: "test")]);

            Assert.AreEqual(1, mockGenerator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().Count());
        }

        [TestCase]
        public void NullClientProviderIsNotAddedToLibrary()
        {
            var mockGenerator = MockHelpers.LoadMockGenerator(
                clients: () => [InputFactory.Client("test", clientNamespace: "test", doc: "test")],
                createClientCore: (client) => null);

            Assert.IsEmpty(mockGenerator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>());
        }

        [TestCase]
        public void ClientProviderSummaryIsPopulated()
        {
            var mockGenerator = MockHelpers.LoadMockGenerator(
                clients: () => [InputFactory.Client("test", clientNamespace: "test", doc: "client description")]);

            var client = mockGenerator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().SingleOrDefault();
            Assert.IsNotNull(client);

            Assert.AreEqual("/// <summary> client description. </summary>\n", client!.XmlDocs.Summary!.ToDisplayString());
        }

        [Test]
        public void ClientProviderSummaryIsPopulatedWithDefaultDocs()
        {
            var mockGenerator = MockHelpers.LoadMockGenerator(
                clients: () => [new InputClient("testClient", @namespace: "test", string.Empty, null, null, false, [], [], null, null, null)]);

            var client = mockGenerator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().SingleOrDefault();
            Assert.IsNotNull(client);

            Assert.AreEqual("/// <summary> The TestClient. </summary>\n", client!.XmlDocs.Summary!.ToDisplayString());
        }

        [TestCase(true)]
        [TestCase(false)]
        public void AccessibilityOfMethodMatchesInputOperation(bool isPublic)
        {
            MockHelpers.LoadMockGenerator();
            var access = isPublic ? "public" : "internal";
            var inputClient = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod(
                        "Foo",
                        InputFactory.Operation(
                            "Foo",
                            access: access),
                        access: access)
                ]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);

            Assert.IsNotNull(clientProvider);
            var convenienceMethod = clientProvider!.Methods.FirstOrDefault(
                m => m.Signature.Name == "Foo" &&
                     !m.Signature.Parameters.Any(p => p.Type.Equals(typeof(RequestOptions))));
            Assert.IsNotNull(convenienceMethod);

            var protocolMethod = clientProvider.Methods.FirstOrDefault(
                m => m.Signature.Name == "Foo" &&
                     m.Signature.Parameters.Any(p => p.Type.Equals(typeof(RequestOptions))));
            Assert.IsNotNull(protocolMethod);

            if (isPublic)
            {
                Assert.IsTrue(convenienceMethod!.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
                Assert.IsTrue(protocolMethod!.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
            }
            else
            {
                Assert.IsFalse(convenienceMethod!.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
                Assert.IsFalse(protocolMethod!.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
            }
        }

        [Test]
        public void XmlDocsAreWritten()
        {
            MockHelpers.LoadMockGenerator(includeXmlDocs: true);
            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod(
                        "Foo",
                        InputFactory.Operation(
                            name: "Foo",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "queryParam",
                                    InputPrimitiveType.String,
                                    isRequired: true,
                                    scope: InputParameterScope.Client)
                            ]),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "queryParam",
                                InputPrimitiveType.String,
                                isRequired: true,
                                location: InputRequestLocation.Query,
                                scope: InputParameterScope.Client)
                        ])
                ]);
            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            var writer = new TypeProviderWriter(clientProvider!);
            var file = writer.Write();

            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void EndpointFieldAssignedFromUriParameter()
        {
            MockHelpers.LoadMockGenerator();
            var client = InputFactory.Client(
                TestClientName,
                parameters: [InputFactory.EndpointParameter(
                    "endpoint",
                    InputPrimitiveType.Url,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    isEndpoint: true)]);
            var clientProvider = new ClientProvider(client);
            var constructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);

            StringAssert.Contains("_endpoint = endpoint;", constructor?.BodyStatements?.ToDisplayString());
        }

        [TestCase("{endpoint}", "endpoint")]
        [TestCase("https://{hostName}", "hostName")]
        public void EndpointFieldAssignedFromStringParameter(string serverTemplate, string parameterName)
        {
            MockHelpers.LoadMockGenerator();
            var client = InputFactory.Client(
                TestClientName,
                parameters: [InputFactory.EndpointParameter(
                    parameterName,
                    InputPrimitiveType.String,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    serverUrlTemplate: serverTemplate,
                    isEndpoint: true)]);
            var clientProvider = new ClientProvider(client);
            var constructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);

            StringAssert.Contains($"_endpoint = new global::System.Uri($\"{serverTemplate}\");", constructor?.BodyStatements?.ToDisplayString());
        }

        [TestCase("{endpoint}", "endpoint")]
        [TestCase("http://{hostName}", "hostName")]
        [TestCase("https://{hostName}", "hostName")]
        public void EndpointAppliedInCreateMethodRequest(string serverTemplate, string parameterName)
        {
            MockHelpers.LoadMockGenerator();
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Foo", InputFactory.Operation("bar", uri: $"{serverTemplate}/foo"))],
                parameters: [InputFactory.EndpointParameter(
                    parameterName,
                    InputPrimitiveType.String,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    serverUrlTemplate: serverTemplate,
                    isEndpoint: true)]);
            var clientProvider = new ClientProvider(client);
            var createMethod = clientProvider.RestClient.Methods.FirstOrDefault();
            StringAssert.Contains($"uri.Reset(_endpoint);", createMethod?.BodyStatements?.ToDisplayString());
        }

        [Test]
        public void ListMethodsAreRenamedToGet()
        {
            MockHelpers.LoadMockGenerator();

            var inputOperation = InputFactory.Operation(
                "ListCats");

            var inputServiceMethod = InputFactory.BasicServiceMethod("ListCats", inputOperation);

            var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]);

            var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);

            foreach (var method in client!.Methods)
            {
                Assert.IsTrue(method.Signature.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase));
            }

            foreach (var method in client.RestClient.Methods)
            {
                Assert.IsTrue(method.Signature.Name.Equals("CreateGetCatsRequest", StringComparison.OrdinalIgnoreCase));
            }
        }

        [Test]
        public void ListMethodIsRenamedToGetAll()
        {
            MockHelpers.LoadMockGenerator();

            var inputOperation = InputFactory.Operation(
                "List");

            var inputServiceMethod = InputFactory.BasicServiceMethod("List", inputOperation);

            var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]);

            var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);

            foreach (var method in client!.Methods)
            {
                Assert.IsTrue(method.Signature.Name.StartsWith("GetAll", StringComparison.OrdinalIgnoreCase));
            }

            foreach (var method in client.RestClient.Methods)
            {
                Assert.IsTrue(method.Signature.Name.Equals("CreateGetAllRequest", StringComparison.OrdinalIgnoreCase));
            }
        }

        [Test]
        public void MethodStartingWithListIsNotRenamedIfNotFullWord()
        {
            MockHelpers.LoadMockGenerator();

            var inputOperation = InputFactory.Operation(
                "Listen");

            var inputServiceMethod = InputFactory.BasicServiceMethod("Listen", inputOperation);

            var inputClient = InputFactory.Client("TestClient", methods: [inputServiceMethod]);

            var client = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient);

            foreach (var method in client!.Methods)
            {
                Assert.IsTrue(method.Signature.Name.StartsWith("Listen", StringComparison.OrdinalIgnoreCase));
            }

            foreach (var method in client.RestClient.Methods)
            {
                Assert.IsTrue(method.Signature.Name.Equals("CreateListenRequest", StringComparison.OrdinalIgnoreCase));
            }
        }

        private static InputClient GetEnumQueryParamClient()
        {
            return InputFactory.Client(
                        TestClientName,
                        methods:
                        [
                            InputFactory.BasicServiceMethod(
                        "test",
                        InputFactory.Operation(
                            "Operation",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "queryParam",
                                    InputFactory.StringEnum(
                                        "InputEnum",
                                        [
                                            ("value1", "value1"),
                                            ("value2", "value2")
                                        ],
                                        usage: InputModelTypeUsage.Input,
                                        isExtensible: true),
                                    isRequired: true)
                            ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                            "queryParam",
                            InputFactory.StringEnum(
                                "InputEnum",
                                [
                                    ("value1", "value1"),
                                    ("value2", "value2")
                                ],
                                usage: InputModelTypeUsage.Input,
                                isExtensible: true),
                            isRequired: true,
                            location: InputRequestLocation.Query)
                    ])
                        ]);
        }

        private class ValidateQueryParamDiffClientProvider : ClientProvider
        {
            private readonly bool _isAsync;

            public ValidateQueryParamDiffClientProvider(InputClient client, bool isAsync = false)
                : base(client)
            {
                _isAsync = isAsync;
            }

            protected override ScmMethodProvider[] BuildMethods()
            {
                var method = base.BuildMethods().First(m => m.Signature.Parameters.Any(p =>
                    p is { Name: "queryParam", Type.Name: "InputEnum" } &&
                    ((_isAsync && m.Signature.Name.EndsWith("Async")) || (!_isAsync && !m.Signature.Name.EndsWith("Async")))));
                method.Update(xmlDocProvider: new XmlDocProvider()); // null out the docs
                return [method];
            }

            protected override FieldProvider[] BuildFields() => [];
            protected override ConstructorProvider[] BuildConstructors() => [];
            protected override PropertyProvider[] BuildProperties() => [];
        }

        // This custom client provider is used to validate operations where non-body request parameters are declared within a request body model.
        private class TestNonBodyRequestParametersInBodyDiffClientProvider : ClientProvider
        {
            public TestNonBodyRequestParametersInBodyDiffClientProvider(InputClient client) : base(client) { }

            protected override ScmMethodProvider[] BuildMethods()
            {
                var method = base.BuildMethods().First(m => m.Signature.Parameters.Any(p =>
                    p is { Name: "body" } && m.Signature.Name.EndsWith("Async")));
                method.Update(xmlDocProvider: new XmlDocProvider());
                return [method];
            }

            protected override FieldProvider[] BuildFields() => [];
            protected override ConstructorProvider[] BuildConstructors() => [];
            protected override PropertyProvider[] BuildProperties() => [];
        }

        private class UnsupportedAuthClientProvider : ClientProvider
        {
            public UnsupportedAuthClientProvider(InputClient client)
                : base(client) { }

            protected override ScmMethodProvider[] BuildMethods() => [];

            protected override FieldProvider[] BuildFields() => [];
            protected override PropertyProvider[] BuildProperties() => [];
        }

        public static IEnumerable<TestCaseData> BuildAuthFieldsTestCases
        {
            get
            {
                yield return new TestCaseData(new List<InputParameter>
                {
                    InputFactory.EndpointParameter(
                        "optionalParam",
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client),

                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client,
                        isEndpoint: true)
                });
                yield return new TestCaseData(new List<InputParameter>
                {
                    // have to explicitly set isRequired because we now call CreateParameter in buildFields
                    InputFactory.EndpointParameter(
                        "optionalNullableParam",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("someValue"),
                        scope: InputParameterScope.Client,
                        isRequired: false),
                    InputFactory.EndpointParameter(
                        "requiredParam2",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("someValue"),
                        scope: InputParameterScope.Client,
                        isRequired: true),
                    InputFactory.EndpointParameter(
                        "requiredParam3",
                        InputPrimitiveType.Int64,
                        defaultValue: InputFactory.Constant.Int64(2),
                        scope: InputParameterScope.Client,
                        isRequired: true),
                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        defaultValue: null,
                        scope: InputParameterScope.Client,
                        isEndpoint: true)
                });
            }
        }

        public static IEnumerable<TestCaseData> BuildOAuth2FlowsFieldTestCases
        {
            get
            {
                // all flow properties present
                yield return new TestCaseData(new List<InputOAuth2Flow>
                {
                    new(
                        ["mockScope1", "mockScope2"],
                        "mockAuthUrl",
                        "mockTokenUrl",
                        "mockRefreshUrl"),
                }).SetArgDisplayNames(["AllFlowProperties"]);

                // multiple flows
                yield return new TestCaseData(new List<InputOAuth2Flow>
                {
                    new(
                        ["mockScope1", "mockScope2"],
                        "mockAuthUrl",
                        null,
                        null),
                    new(
                        ["mockScope3"],
                        "mockAuthUrl",
                        null,
                        null),
                     new(
                        [],
                        null,
                        null,
                        "mockRefreshUrl"),
                }).SetArgDisplayNames(["MultipleFlows"]);

                // no flow
                yield return new TestCaseData(new List<InputOAuth2Flow>()).SetArgDisplayNames(["NoFlows"]);

                // no scopes
                yield return new TestCaseData(new List<InputOAuth2Flow>
                {
                    new(
                        [],
                        "mockAuthUrl",
                        null,
                        null),
                }).SetArgDisplayNames(["NoScopes"]);
            }
        }

        public static IEnumerable<TestCaseData> BuildFieldsTestCases
        {
            get
            {
                yield return new TestCaseData(new List<InputParameter>
                {
                    InputFactory.PathParameter(
                        "optionalParam",
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client),
                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client,
                        isEndpoint: true)
                },
                new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint"),
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(string), true), "_optionalParam")
                }
                );
                yield return new TestCaseData(new List<InputParameter>
                {
                    // have to explicitly set isRequired because we now call CreateParameter in buildFields
                    InputFactory.PathParameter(
                        "optionalNullableParam",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("someValue"),
                        scope: InputParameterScope.Client,
                        isRequired: false),
                    InputFactory.PathParameter(
                        "requiredParam2",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("someValue"),
                        scope: InputParameterScope.Client,
                        isRequired: true),
                    InputFactory.PathParameter(
                        "requiredParam3",
                        InputPrimitiveType.Int64,
                        defaultValue: InputFactory.Constant.Int64(2),
                        scope: InputParameterScope.Client,
                        isRequired: true),
                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        defaultValue: null,
                        scope: InputParameterScope.Client,
                        isEndpoint: true)
                },
                new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint"),
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(string), true), "_optionalNullableParam"),
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(string), false), "_requiredParam2"),
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(long), false), "_requiredParam3")
                });
            }
        }

        public static IEnumerable<TestCaseData> WithSubClientAuthFieldsTestCases
        {
            get
            {
                yield return new TestCaseData(InputFactory.Client(TestClientName));
            }
        }

        public static IEnumerable<TestCaseData> SubClientAuthFieldsTestCases
        {
            get
            {
                yield return new TestCaseData(_animalClient);
                yield return new TestCaseData(_dogClient);
                yield return new TestCaseData(_huskyClient);
            }
        }

        public static IEnumerable<TestCaseData> SubClientFieldsTestCases
        {
            get
            {
                yield return new TestCaseData(_testClient, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint"),
                    new(FieldModifiers.Private, new ExpectedCSharpType("Animal", "Sample", false), "_cachedAnimal"),
                });
                yield return new TestCaseData(_animalClient, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint"),
                    new(FieldModifiers.Private, new ExpectedCSharpType("Dog", "Sample", false), "_cachedDog"),
                });
                yield return new TestCaseData(_dogClient, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint"),
                    new(FieldModifiers.Private, new ExpectedCSharpType("Husky", "Sample", false), "_cachedHusky"),
                });
                yield return new TestCaseData(_huskyClient, new List<ExpectedFieldProvider>
                {
                    new(FieldModifiers.Private | FieldModifiers.ReadOnly, new CSharpType(typeof(Uri)), "_endpoint")
                });
            }
        }

        public static IEnumerable<TestCaseData> ValidateClientWithSpreadTestCases
        {
            get
            {
                yield return new TestCaseData(InputFactory.Client(
                    TestClientName,
                    methods:
                    [
                        InputFactory.BasicServiceMethod("test",
                            InputFactory.Operation(
                            "CreateMessage",
                            parameters:
                            [
                                InputFactory.BodyParameter(
                                    "spread",
                                    _spreadModel,
                                    isRequired: true,
                                    scope: InputParameterScope.Spread),
                            ]),
                            parameters:  [InputFactory.MethodParameter("p1", InputPrimitiveType.String, scope: InputParameterScope.Spread, isRequired: true)])
                    ]));
            }
        }

        public static IEnumerable<TestCaseData> SubClientFactoryMethodTestCases
        {
            get
            {
                yield return new TestCaseData(_testClient, true);
                yield return new TestCaseData(_animalClient, true);
                yield return new TestCaseData(_dogClient, true);
                yield return new TestCaseData(_huskyClient, false);
            }
        }

        public static IEnumerable<TestCaseData> BuildConstructorsTestCases
        {
            get
            {
                yield return new TestCaseData(new List<InputParameter>
                {
                    InputFactory.PathParameter(
                        "optionalParam",
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client),
                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("someValue"),
                        scope: InputParameterScope.Client,
                        isEndpoint: true)
                }).SetProperty("caseName", "WithDefault");
                // scenario where endpoint is required
                yield return new TestCaseData(new List<InputParameter>
                {
                    InputFactory.EndpointParameter(
                        KnownParameters.Endpoint.Name,
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client,
                        isRequired: true,
                        isEndpoint: true),
                    InputFactory.PathParameter(
                        "optionalParam",
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client)
                }).SetProperty("caseName", "WithRequired");
            }
        }

        public static IEnumerable<TestCaseData> TestNonBodyRequestParametersInBodyTestCases
        {
            get
            {
                // header parameter in body
                yield return new TestCaseData(InputFactory.BasicServiceMethod(
                    "TestServiceMethod",
                    InputFactory.Operation(
                        "TestOperation",
                        parameters:
                        [
                            InputFactory.BodyParameter(
                                "body",
                                InputFactory.Model(
                                    "ModelWithHeader",
                                    properties:
                                    [
                                        InputFactory.Property("foo", InputPrimitiveType.String, isRequired: true, isHttpMetadata: true),
                                        InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                    ]),
                                isRequired: true),
                            InputFactory.HeaderParameter("foo", InputPrimitiveType.String, isRequired: true),
                        ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                            "body",
                            InputFactory.Model(
                                "ModelWithHeader",
                                properties:
                                [
                                    InputFactory.Property("foo", InputPrimitiveType.String, isRequired: true, isHttpMetadata: true),
                                    InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                ]),
                            location: InputRequestLocation.Body, isRequired: true)]
                    )).SetProperty("caseName", "WithHeaderInRequestBody");

                // query parameter in body
                yield return new TestCaseData(InputFactory.BasicServiceMethod(
                    "TestServiceMethod",
                    InputFactory.Operation(
                        "TestOperation",
                        parameters:
                        [
                            InputFactory.BodyParameter(
                                "body",
                                InputFactory.Model(
                                    "ModelWithQuery",
                                    properties:
                                    [
                                        InputFactory.Property("foo", InputPrimitiveType.String, isRequired: true, isHttpMetadata: true),
                                        InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                    ]),
                                isRequired: true),
                            InputFactory.QueryParameter("foo", InputPrimitiveType.String, isRequired: true),
                        ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                            "body",
                            InputFactory.Model(
                                "ModelWithQuery",
                                properties:
                                [
                                    InputFactory.Property("foo", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                    InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                ]),
                            location: InputRequestLocation.Body, isRequired: true)]
                    )).SetProperty("caseName", "WithQueryInRequestBody");

                // path parameter in body
                yield return new TestCaseData(InputFactory.BasicServiceMethod(
                    "TestServiceMethod",
                    InputFactory.Operation(
                        "TestOperation",
                        parameters:
                        [
                            InputFactory.BodyParameter(
                                "body",
                                InputFactory.Model(
                                    "ModelWithPathParam",
                                    properties:
                                    [
                                        InputFactory.Property("foo", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                        InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                    ]),
                                isRequired: true),
                            InputFactory.HeaderParameter("foo", InputPrimitiveType.String, isRequired : true),
                        ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                            "body",
                            InputFactory.Model(
                                "ModelWithPathParam",
                                properties:
                                [
                                    InputFactory.Property("foo", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                    InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                ]),
                            location: InputRequestLocation.Body, isRequired: true)]
                    )).SetProperty("caseName", "WithPathInRequestBody");

                // mixed parameters in body
                yield return new TestCaseData(InputFactory.BasicServiceMethod(
                    "TestServiceMethod",
                    InputFactory.Operation(
                        "TestOperation",
                        parameters:
                        [
                            InputFactory.BodyParameter(
                                "body",
                                InputFactory.Model(
                                    "ModelWithMixedParams",
                                    properties:
                                    [
                                        InputFactory.Property("cat", InputPrimitiveType.String, isRequired: true, isHttpMetadata: true),
                                        InputFactory.Property("dog", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                        InputFactory.Property("bird", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                        InputFactory.Property("bar", InputPrimitiveType.Int32, isRequired: true)
                                    ]),
                                isRequired: true),
                            InputFactory.PathParameter("cat", InputPrimitiveType.String, isRequired: true),
                            InputFactory.QueryParameter("dog", InputPrimitiveType.String, isRequired: true),
                            InputFactory.HeaderParameter("bird", InputPrimitiveType.String, isRequired: true),
                        ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                            "body",
                            InputFactory.Model(
                                "ModelWithPathParam",
                                properties:
                                [
                                    InputFactory.Property("cat", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                    InputFactory.Property("dog", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                    InputFactory.Property("bird", InputPrimitiveType.String, isRequired : true, isHttpMetadata : true),
                                ]),
                            location: InputRequestLocation.Body, isRequired: true)]
                    )).SetProperty("caseName", "WithMixedParametersInRequestBody");
            }
        }

        public static IEnumerable<TestCaseData> RequestOptionsParameterInSignatureTestCases
        {
            get
            {
                // Protocol & convenience methods will have the same parameters, so RequestOptions should be required.
                yield return new TestCaseData(
                InputFactory.BasicServiceMethod(
                    "TestServiceMethod",
                    InputFactory.Operation(
                        "TestOperation",
                        parameters:
                        [
                            InputFactory.QueryParameter(
                                    "p1",
                                    InputPrimitiveType.String,
                                    isRequired: true),
                                InputFactory.QueryParameter(
                                    "p2",
                                    InputPrimitiveType.Int64,
                                    isRequired: true),
                        ]),
                    parameters:
                    [
                        InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.String,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                            InputFactory.MethodParameter(
                                "p2",
                                InputPrimitiveType.Int64,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                    ]), false, false);

                // Protocol & convenience methods will have the same parameters.
                // One of the parameter is optional, so it should be made required in the protocol method.
                yield return new TestCaseData(
                     InputFactory.BasicServiceMethod(
                        "TestServiceMethod2",
                        InputFactory.Operation(
                            "TestOperation2",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "p1",
                                    InputPrimitiveType.String,
                                    isRequired: false),
                                InputFactory.QueryParameter(
                                    "p2",
                                    InputPrimitiveType.Int64,
                                    isRequired: true),
                            ]),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.String,
                                location: InputRequestLocation.Query,
                                isRequired: false),
                            InputFactory.MethodParameter(
                                "p2",
                                InputPrimitiveType.Int64,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                        ]), false, true);

                // Protocol & convenience methods will have the same parameters.
                // One of the parameter is optional value type, so it should be made nullable required in the protocol method, and RequestOptions can be optional.
                yield return new TestCaseData(
                     InputFactory.BasicServiceMethod(
                        "TestServiceMethod3",
                        InputFactory.Operation(
                            "TestOperation3",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "p1",
                                    InputPrimitiveType.Int32,
                                    isRequired: false),
                                InputFactory.QueryParameter(
                                    "p2",
                                    InputPrimitiveType.Int64,
                                    isRequired: true),
                            ]),
                         parameters:
                        [
                            InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.Int32,
                                location: InputRequestLocation.Query,
                                isRequired: false),
                            InputFactory.MethodParameter(
                                "p2",
                                InputPrimitiveType.Int64,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                        ]), false, true);

                // convenience method only has a body param, but it is optional, so RequestOptions should be optional in protocol method.
                yield return new TestCaseData(
                     InputFactory.BasicServiceMethod(
                        "TestServiceMethod",
                        InputFactory.Operation(
                            "TestOperation",
                             parameters:
                             [
                                 InputFactory.BodyParameter(
                                     "p1",
                                     InputPrimitiveType.String),
                             ]),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.String,
                                location: InputRequestLocation.Body),
                        ]), true, true);

                // Protocol & convenience methods will have different parameters since there is a model body param, so RequestOptions should be optional.
                yield return new TestCaseData(
                    InputFactory.BasicServiceMethod(
                        "TestServiceMethod",
                        InputFactory.Operation(
                            "TestOperation",
                           parameters:
                           [
                                InputFactory.QueryParameter(
                                    "p1",
                                    InputPrimitiveType.String,
                                    isRequired: true),
                                InputFactory.BodyParameter(
                                    "p2",
                                    InputFactory.Model("SampleModel"),
                                    isRequired: true),
                           ]),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.String,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                            InputFactory.MethodParameter(
                                "p2",
                                InputFactory.Model("SampleModel"),
                                location: InputRequestLocation.Body,
                                isRequired: true),
                        ]), true, false);

                // Protocol & convenience methods will have different parameters but since the body parameter is optional,
                // the body parameter of the protocol method will be made required, and the request options should remain optional.
                yield return new TestCaseData(
                    InputFactory.BasicServiceMethod(
                        "TestServiceMethod",
                        InputFactory.Operation(
                            "TestOperation",
                            parameters:
                            [
                                InputFactory.QueryParameter(
                                    "p1",
                                    InputPrimitiveType.String,
                                    isRequired: true),
                                InputFactory.BodyParameter(
                                    "p2",
                                    InputFactory.Model("SampleModel"),
                                    isRequired: false),
                            ]),
                        parameters:
                        [
                            InputFactory.MethodParameter(
                                "p1",
                                InputPrimitiveType.String,
                                location: InputRequestLocation.Query,
                                isRequired: true),
                            InputFactory.MethodParameter(
                                "p2",
                                InputFactory.Model("SampleModel"),
                                location: InputRequestLocation.Body,
                                isRequired: false),
                        ]), true, true);


                // Convenience method has no parameters, RequestOptions should be required in protocol method.
                yield return new TestCaseData(
                     InputFactory.BasicServiceMethod(
                        "TestServiceMethod",
                        InputFactory.Operation(
                            "TestOperation",
                            responses: [InputFactory.OperationResponse([201], InputFactory.Model("testModel"))],
                            parameters: [])),
                     false, false);
            }
        }

        private static IEnumerable<TestCaseData> EndpointParamInitializationValueTestCases
        {
            get
            {
                // string primitive type
                yield return new TestCaseData(
                    InputFactory.EndpointParameter(
                        "param",
                        InputPrimitiveType.String,
                        scope: InputParameterScope.Client,
                        isEndpoint: true,
                        defaultValue: InputFactory.Constant.String("mockValue")),
                    New.Instance(KnownParameters.Endpoint.Type, Literal("mockvalue")));
            }
        }

        private static IEnumerable<TestCaseData> ValidateApiVersionPathParameterTestCases
        {
            get
            {
                InputEndpointParameter endpointParameter = InputFactory.EndpointParameter(
                    "endpoint",
                    InputPrimitiveType.String,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    isEndpoint: true,
                    isApiVersion: false);

                InputParameter stringApiVersionParameter = InputFactory.PathParameter(
                    "apiVersion",
                    InputPrimitiveType.String,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    isApiVersion: true);

                InputParameter enumApiVersionParameter = InputFactory.PathParameter(
                    "apiVersion",
                    InputFactory.StringEnum(
                        "InputEnum",
                        [
                            ("value1", "value1"),
                            ("value2", "value2")
                        ],
                        usage: InputModelTypeUsage.Input,
                        isExtensible: true),
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    isApiVersion: true);

                yield return new TestCaseData(
                    InputFactory.Client(
                        "TestClient",
                        methods:
                        [
                            InputFactory.BasicServiceMethod(
                                "test",
                                InputFactory.Operation("TestOperation", uri: "{endpoint}/{apiVersion}"))
                        ],
                        parameters: [endpointParameter, stringApiVersionParameter]));

                yield return new TestCaseData(
                    InputFactory.Client(
                        "TestClient",
                         methods:
                         [
                            InputFactory.BasicServiceMethod(
                                "test",
                                InputFactory.Operation(
                                    "TestOperation",
                                    uri: "{endpoint}/{apiVersion}"))
                         ],
                         parameters: [endpointParameter, enumApiVersionParameter]));
            }
        }

        private record TestClientPipelineApi : ClientPipelineProvider
        {
            private static ClientPipelineApi? _instance;
            internal new static ClientPipelineApi Instance => _instance ??= new TestClientPipelineApi(Empty);

            public TestClientPipelineApi(ValueExpression original) : base(original)
            {
            }

            public override CSharpType TokenCredentialType => typeof(FakeTokenCredential);

            public override ClientPipelineApi FromExpression(ValueExpression expression)
                => new TestClientPipelineApi(expression);

            public override ValueExpression TokenAuthorizationPolicy(ValueExpression credential, ValueExpression scopes)
                => Original.Invoke("GetFakeTokenAuthorizationPolicy", [credential, scopes]);

            public override ClientPipelineApi ToExpression() => this;
        }

        internal class FakeTokenCredential { }

        public record ExpectedCSharpType
        {
            public string Name { get; }

            public string Namespace { get; }

            public bool IsFrameworkType { get; }

            public Type FrameworkType => _frameworkType ?? throw new InvalidOperationException();

            public bool IsNullable { get; }

            private readonly Type? _frameworkType;

            public ExpectedCSharpType(Type frameworkType, bool isNullable)
            {
                _frameworkType = frameworkType;
                IsFrameworkType = true;
                IsNullable = isNullable;
                Name = frameworkType.Name;
                Namespace = frameworkType.Namespace!;
            }

            public ExpectedCSharpType(string name, string ns, bool isNullable)
            {
                IsFrameworkType = false;
                IsNullable = isNullable;
                Name = name;
                Namespace = ns;
            }

            public static implicit operator ExpectedCSharpType(CSharpType type)
            {
                if (type.IsFrameworkType)
                {
                    return new(type.FrameworkType, type.IsNullable);
                }
                else
                {
                    return new(type.Name, type.Namespace, type.IsNullable);
                }
            }
        }

        public record ExpectedFieldProvider(FieldModifiers Modifiers, ExpectedCSharpType Type, string Name);

        private static void AssertCSharpTypeAreEqual(ExpectedCSharpType expected, CSharpType type)
        {
            if (expected.IsFrameworkType)
            {
                Assert.IsTrue(type.IsFrameworkType);
                Assert.AreEqual(expected.FrameworkType, type.FrameworkType);
            }
            else
            {
                Assert.IsFalse(type.IsFrameworkType);
                Assert.AreEqual(expected.Name, type.Name);
                Assert.AreEqual(expected.Namespace, type.Namespace);
            }
            Assert.AreEqual(expected.IsNullable, type.IsNullable);
        }

        private static void AssertFieldAreEqual(ExpectedFieldProvider expected, FieldProvider field)
        {
            Assert.AreEqual(expected.Name, field.Name);
            AssertCSharpTypeAreEqual(expected.Type, field.Type);
            Assert.AreEqual(expected.Modifiers, field.Modifiers);
        }

        private static void AssertHasFields(TypeProvider provider, IReadOnlyList<ExpectedFieldProvider> expectedFields)
        {
            var fields = provider.Fields;

            // validate the length of the result
            Assert.GreaterOrEqual(fields.Count, expectedFields.Count);

            // validate each of them
            var fieldDict = fields.ToDictionary(f => f.Name);
            for (int i = 0; i < expectedFields.Count; i++)
            {
                var expected = expectedFields[i];

                Assert.IsTrue(fieldDict.TryGetValue(expected.Name, out var actual), $"Field {expected.Name} not present");
                AssertFieldAreEqual(expected, actual!);
            }
        }

        [Test]
        public async Task BackCompatibility_ProtocolMethodParamOrderChanged()
        {
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var param2 = InputFactory.QueryParameter("param2", InputPrimitiveType.Int32, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: true);

            var operation = InputFactory.Operation(
                "TestMethod",
                parameters: [param1, param3, param2]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("TestMethod", operation, parameters: methodParameters);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var methods = clientProvider!.Methods;
            var protocolMethods = methods
                .Where(m => m.Signature.Name.StartsWith("TestMethod") && m is ScmMethodProvider { Kind: ScmMethodKind.Protocol })
                .ToList();
            Assert.AreEqual(2, protocolMethods.Count);

            // Validate both sync and async methods have the reordered parameters matching the previous contract
            foreach (var testMethod in protocolMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                // Should have 4 parameters: param1, param2, param3, options
                Assert.AreEqual(4, parameters.Count);
                Assert.AreEqual("content", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("options", parameters[3].Name);
            }

            // validate the convenience method bodies are updated
            var convenienceMethods = methods
                .Where(m => m.Signature.Name.StartsWith("TestMethod") && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience })
                .ToList();
            Assert.AreEqual(2, convenienceMethods.Count);

            var syncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => !m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(syncConvenienceMethod);

            var body = syncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            var result = body!.ToDisplayString();
            Assert.AreEqual(
                "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
                "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
                "return this.TestMethod(content, param2, param3, cancellationToken.ToRequestOptions());\n",
                result);

            var asyncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(asyncConvenienceMethod);

            body = asyncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            result = body!.ToDisplayString();
            Assert.AreEqual(
               "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
               "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
               "return await this.TestMethodAsync(content, param2, param3, cancellationToken.ToRequestOptions()).ConfigureAwait(false);\n",
               result);
        }

        [Test]
        public async Task BackCompatibility_ConvenienceMethodParamOrderChanged()
        {
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var param2 = InputFactory.QueryParameter("param2", InputPrimitiveType.Int32, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: true);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param3, param2],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var methods = clientProvider!.Methods;
            var convenienceMethods = methods
                .Where(m => m.Signature.Name.StartsWith("GetData") && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience })
                .ToList();
            Assert.AreEqual(2, convenienceMethods.Count);

            // Validate both sync and async methods have the reordered parameters matching the previous contract
            foreach (var testMethod in convenienceMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                Assert.AreEqual(4, parameters.Count);
                Assert.AreEqual("param1", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("cancellationToken", parameters[3].Name);
            }

            var syncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => !m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(syncConvenienceMethod);

            var body = syncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            var result = body!.ToDisplayString();
            Assert.AreEqual(
                "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
                "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
                "global::System.ClientModel.ClientResult result = this.GetData(param3, param2, content, cancellationToken.ToRequestOptions());\n" +
                "return global::System.ClientModel.ClientResult.FromValue(result.GetRawResponse().Content.ToObjectFromJson<string>(), result.GetRawResponse());\n",
                result);

            var asyncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(asyncConvenienceMethod);

            body = asyncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            result = body!.ToDisplayString();
            Assert.AreEqual(
                "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
                "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
                "global::System.ClientModel.ClientResult result = await this.GetDataAsync(param3, param2, content, cancellationToken.ToRequestOptions()).ConfigureAwait(false);\n" +
                "return global::System.ClientModel.ClientResult.FromValue(result.GetRawResponse().Content.ToObjectFromJson<string>(), result.GetRawResponse());\n",
               result);
        }

        [Test]
        public async Task BackCompatibility_BothMethodsParamOrderChanged()
        {
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var param2 = InputFactory.QueryParameter("param2", InputPrimitiveType.Int32, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: true);

            var operation = InputFactory.Operation(
                "UpdateResource",
                parameters: [param1, param3, param2],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("UpdateResource", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var methods = clientProvider.Methods;
            var protocolMethods = methods
                .Where(m => m.Signature.Name.StartsWith("UpdateResource") && m is ScmMethodProvider { Kind: ScmMethodKind.Protocol })
                .ToList();
            Assert.AreEqual(2, protocolMethods.Count);

            // Validate both sync and async methods have the reordered parameters matching the previous contract
            foreach (var testMethod in protocolMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                Assert.AreEqual(4, parameters.Count);
                Assert.AreEqual("content", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("options", parameters[3].Name);
            }

            // validate the convenience method bodies are updated
            var convenienceMethods = methods
                .Where(m => m.Signature.Name.StartsWith("UpdateResource") && m is ScmMethodProvider { Kind: ScmMethodKind.Convenience })
                .ToList();
            Assert.AreEqual(2, convenienceMethods.Count);

            // Validate both sync and async methods have the reordered parameters matching the previous contract
            foreach (var testMethod in convenienceMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                Assert.AreEqual(4, parameters.Count);
                Assert.AreEqual("param1", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("cancellationToken", parameters[3].Name);
            }

            var syncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => !m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(syncConvenienceMethod);

            var body = syncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            var result = body!.ToDisplayString();
            Assert.AreEqual(
                "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
                "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
                "global::System.ClientModel.ClientResult result = this.UpdateResource(content, param2, param3, cancellationToken.ToRequestOptions());\n" +
                "return global::System.ClientModel.ClientResult.FromValue(result.GetRawResponse().Content.ToObjectFromJson<string>(), result.GetRawResponse());\n",
                result);

            var asyncConvenienceMethod = convenienceMethods
                .FirstOrDefault(m => m.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Async));
            Assert.IsNotNull(asyncConvenienceMethod);

            body = asyncConvenienceMethod!.BodyStatements;
            Assert.IsNotNull(body);

            result = body!.ToDisplayString();
            Assert.AreEqual(
               "global::Sample.Argument.AssertNotNullOrEmpty(param1, nameof(param1));\n\n" +
               "using global::System.ClientModel.BinaryContent content = global::System.ClientModel.BinaryContent.Create(global::System.BinaryData.FromString(param1));\n" +
               "global::System.ClientModel.ClientResult result = await this.UpdateResourceAsync(content, param2, param3, cancellationToken.ToRequestOptions()).ConfigureAwait(false);\n" +
               "return global::System.ClientModel.ClientResult.FromValue(result.GetRawResponse().Content.ToObjectFromJson<string>(), result.GetRawResponse());\n",
               result);
        }

        [Test]
        public async Task BackCompatibility_ExactMatchWithCompatibleOverload()
        {
            // Set up operation parameters - exact match has string param2, overload has int param2
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var param2String = InputFactory.QueryParameter("param2", InputPrimitiveType.String, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: true);

            var operation = InputFactory.Operation(
                "ProcessData",
                parameters: [param1, param2String, param3]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.String, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("ProcessData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var methods = clientProvider.Methods;
            var processDataMethods = methods
                .Where(m => m.Signature.Name.StartsWith("ProcessData") && m is ScmMethodProvider)
                .ToList();

            // Should have 4 methods total: 2 protocol (sync/async) and 2 convenience (sync/async)
            Assert.AreEqual(4, processDataMethods.Count);

            foreach (var testMethod in processDataMethods)
            {
                Assert.IsTrue(testMethod is ScmMethodProvider scmMethod && scmMethod.EnclosingType is ClientProvider);
            }

            var protocolMethods = processDataMethods
                .Where(m => m is ScmMethodProvider { Kind: ScmMethodKind.Protocol })
                .ToList();
            Assert.AreEqual(2, protocolMethods.Count);

            // Validate that the parameters remained unchanged (exact match found, so no reordering)
            foreach (var testMethod in protocolMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                Assert.AreEqual(4, parameters.Count);
                // Should remain in the original order: content, param2 (string), param3, options
                Assert.AreEqual("content", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual(typeof(string), parameters[1].Type.FrameworkType);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("options", parameters[3].Name);
            }

            // Validate convenience methods also remained unchanged
            var convenienceMethods = processDataMethods
                .Where(m => m is ScmMethodProvider { Kind: ScmMethodKind.Convenience })
                .ToList();
            Assert.AreEqual(2, convenienceMethods.Count);

            foreach (var testMethod in convenienceMethods)
            {
                var parameters = testMethod.Signature.Parameters;
                Assert.AreEqual(4, parameters.Count);
                // Should remain in the original order: param1, param2 (string), param3, cancellationToken
                Assert.AreEqual("param1", parameters[0].Name);
                Assert.AreEqual("param2", parameters[1].Name);
                Assert.AreEqual(typeof(string), parameters[1].Type.FrameworkType);
                Assert.AreEqual("param3", parameters[2].Name);
                Assert.AreEqual("cancellationToken", parameters[3].Name);
            }
        }

        [Test]
        public async Task BackCompatibility_DuplicateMethodSignatureDoesNotThrow()
        {
            var bodyParam1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var bodyParam2 = InputFactory.BodyParameter("param1", InputPrimitiveType.Int32, isRequired: true);

            var operation1 = InputFactory.Operation(
                "TestMethod",
                parameters: [bodyParam1]);
            var operation2 = InputFactory.Operation(
                "TestMethod",
                parameters: [bodyParam2]);

            List<InputMethodParameter> methodParameters1 =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
            ];
            List<InputMethodParameter> methodParameters2 =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Body, isRequired: true),
            ];

            var method1 = InputFactory.BasicServiceMethod("TestMethod", operation1, parameters: methodParameters1);
            var method2 = InputFactory.BasicServiceMethod("TestMethod", operation2, parameters: methodParameters2);
            var client = InputFactory.Client(TestClientName, methods: [method1, method2]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            // This should not throw even when there are duplicate method signatures
            Assert.DoesNotThrow(() => clientProvider!.ProcessTypeForBackCompatibility());
        }

        // Last contract has GetData(int param1, string param2, CancellationToken) (and async).
        // The current TypeSpec adds a new optional non-body (header) parameter `param3`.
        // Expected: a hidden back-compat overload matching the previous signature is added that
        // delegates to the new method, passing default for the new parameter.
        [Test]
        public async Task BackCompatibility_NewOptionalNonBodyParameterAdded()
        {
            var param1 = InputFactory.QueryParameter("param1", InputPrimitiveType.Int32, isRequired: true);
            var param2 = InputFactory.BodyParameter("param2", InputPrimitiveType.String, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2, param3],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // The current TypeSpec adds two new optional non-body parameters relative to the last contract.
        // Expected: a single back-compat overload matching the previous signature is added that
        // delegates to the new method, passing default for both new parameters.
        [Test]
        public async Task BackCompatibility_MultipleNewOptionalNonBodyParametersAdded()
        {
            var param1 = InputFactory.QueryParameter("param1", InputPrimitiveType.Int32, isRequired: true);
            var param2 = InputFactory.BodyParameter("param2", InputPrimitiveType.String, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: false);
            var param4 = InputFactory.QueryParameter("param4", InputPrimitiveType.String, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2, param3, param4],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: false),
                InputFactory.MethodParameter("param4", InputPrimitiveType.String, location: InputRequestLocation.Query, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // Last contract has GetData(int param1, ModelType body, CancellationToken) (and async).
        // The current TypeSpec adds a new optional non-body (header) parameter `param3`.
        // Expected: a hidden back-compat overload matching the previous signature is added even when
        // the method has a model-typed request body.
        [Test]
        public async Task BackCompatibility_NewOptionalNonBodyParameterAddedWithModelBody()
        {
            var bodyModel = InputFactory.Model(
                "SampleModel",
                properties: [InputFactory.Property("name", InputPrimitiveType.String, isRequired: true)]);

            var param1 = InputFactory.QueryParameter("param1", InputPrimitiveType.Int32, isRequired: true);
            var param2 = InputFactory.BodyParameter("body", bodyModel, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2, param3],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("body", bodyModel, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // When the new parameter is a body parameter, the back-compat overload should NOT be added.
        // See https://github.com/Azure/azure-sdk-for-net/blob/main/doc/DataPlaneCodeGeneration/ServiceDrivenEvolution.md#a-method-gets-a-new-optional-parameter
        [Test]
        public async Task BackCompatibility_NewOptionalBodyParameterDoesNotAddBackCompatOverload()
        {
            // Last contract had a GetData(int param2, CancellationToken) method (no body parameter).
            // Current method adds an optional body parameter `param1`.
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: false);
            var param2 = InputFactory.QueryParameter("param2", InputPrimitiveType.Int32, isRequired: true);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: false),
                InputFactory.MethodParameter("param2", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // When the new parameter is required (not optional), the back-compat overload should NOT be added,
        // even if it is non-body. Adding such an overload would require us to invent a value for a required
        // parameter, which would be unsafe.
        [Test]
        public async Task BackCompatibility_NewRequiredParameterDoesNotAddBackCompatOverload()
        {
            var param1 = InputFactory.BodyParameter("param1", InputPrimitiveType.String, isRequired: true);
            var param2 = InputFactory.QueryParameter("param2", InputPrimitiveType.Int32, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: true);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2, param3],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: true),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // Last contract has GetData(string itemId, int filter, CancellationToken) (and async) where
        // itemId is a path parameter, filter is a required query, and a required header is present.
        // The current TypeSpec adds a new optional query parameter `sort`.
        // Expected: a hidden back-compat overload matching the previous signature is added even when
        // the operation mixes path, query, and header parameters.
        [Test]
        public async Task BackCompatibility_NewOptionalNonBodyParameterAddedWithPathAndHeaderParameters()
        {
            var itemId = InputFactory.PathParameter("itemId", InputPrimitiveType.String, isRequired: true);
            var filter = InputFactory.QueryParameter("filter", InputPrimitiveType.Int32, isRequired: true);
            var region = InputFactory.HeaderParameter("region", InputPrimitiveType.String, isRequired: true);
            var sort = InputFactory.QueryParameter("sort", InputPrimitiveType.String, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                path: "/items/{itemId}",
                parameters: [itemId, filter, region, sort],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("itemId", InputPrimitiveType.String, location: InputRequestLocation.Path, isRequired: true),
                InputFactory.MethodParameter("filter", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("region", InputPrimitiveType.String, location: InputRequestLocation.Header, isRequired: true),
                InputFactory.MethodParameter("sort", InputPrimitiveType.String, location: InputRequestLocation.Query, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        // Verifies that a back-compat overload added by ProcessTypeForBackCompatibility is removed
        // when the user has explicitly suppressed it via [CodeGenSuppress] in custom code. Without
        // filtering in ProcessTypeForBackCompatibility, the suppressed overload would be re-introduced
        // after the FilterAllCustomizedMembers pass, leaking into the generated client.
        [Test]
        public async Task BackCompatibility_BackCompatOverloadSuppressedByCustomCode()
        {
            // Last contract had a GetData(int param1, string param2, CancellationToken) method.
            // Current method adds an optional non-body parameter `param3`, which would normally
            // cause a back-compat overload matching the previous signature to be added.
            var param1 = InputFactory.QueryParameter("param1", InputPrimitiveType.Int32, isRequired: true);
            var param2 = InputFactory.BodyParameter("param2", InputPrimitiveType.String, isRequired: true);
            var param3 = InputFactory.HeaderParameter("param3", InputPrimitiveType.Boolean, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, param2, param3],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("param2", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("param3", InputPrimitiveType.Boolean, location: InputRequestLocation.Header, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"),
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"));

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);
            Assert.IsNotNull(clientProvider.CustomCodeView);

            clientProvider.ProcessTypeForBackCompatibility();

            // The current spec method has param3, the back-compat overload would NOT have param3.
            // With the fix, the suppressed back-compat overload should not appear in the methods.
            var getDataOverloads = clientProvider.Methods
                .Where(m => m.Signature.Name == "GetData" && !m.Signature.Parameters.Any(p => p.Name == "param3"))
                .ToList();
            var getDataAsyncOverloads = clientProvider.Methods
                .Where(m => m.Signature.Name == "GetDataAsync" && !m.Signature.Parameters.Any(p => p.Name == "param3"))
                .ToList();

            Assert.AreEqual(0, getDataOverloads.Count, "Back-compat overload of GetData should be suppressed by [CodeGenSuppress] in custom code.");
            Assert.AreEqual(0, getDataAsyncOverloads.Count, "Back-compat overload of GetDataAsync should be suppressed by [CodeGenSuppress] in custom code.");

            // The current methods (with param3) must still be present.
            Assert.IsTrue(clientProvider.Methods.Any(m => m.Signature.Name == "GetData" && m.Signature.Parameters.Any(p => p.Name == "param3")));
            Assert.IsTrue(clientProvider.Methods.Any(m => m.Signature.Name == "GetDataAsync" && m.Signature.Parameters.Any(p => p.Name == "param3")));
        }

        // Last contract has only protocol methods: GetData(int param1, BinaryContent content, RequestOptions options = null).
        // The current TypeSpec adds a new optional non-body query parameter "$select" whose raw input name
        // starts with a reserved character.
        // Expected: a hidden back-compat protocol overload matching the previous signature is added. The
        // delegating call body must use the C# variable name ("select") for the named-argument label, not
        // the raw "$select" name (which would produce invalid C#).
        [Test]
        public async Task BackCompatibility_NewOptionalParameterWithReservedName()
        {
            var param1 = InputFactory.QueryParameter("param1", InputPrimitiveType.Int32, isRequired: true);
            var content = InputFactory.BodyParameter("content", InputPrimitiveType.String, isRequired: true);
            var selectParam = InputFactory.QueryParameter("$select", InputPrimitiveType.String, isRequired: false);

            var operation = InputFactory.Operation(
                "GetData",
                parameters: [param1, content, selectParam],
                responses: [InputFactory.OperationResponse([200], bodytype: InputPrimitiveType.String)]);

            List<InputMethodParameter> methodParameters =
            [
                InputFactory.MethodParameter("param1", InputPrimitiveType.Int32, location: InputRequestLocation.Query, isRequired: true),
                InputFactory.MethodParameter("content", InputPrimitiveType.String, location: InputRequestLocation.Body, isRequired: true),
                InputFactory.MethodParameter("$select", InputPrimitiveType.String, location: InputRequestLocation.Query, isRequired: false),
            ];

            var method = InputFactory.BasicServiceMethod("GetData", operation, parameters: [.. methodParameters]);
            var client = InputFactory.Client(TestClientName, methods: [method]);

            var generator = await MockHelpers.LoadMockGeneratorAsync(
                clients: () => [client],
                lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

            var clientProvider = generator.Object.OutputLibrary.TypeProviders.OfType<ClientProvider>().FirstOrDefault();
            Assert.IsNotNull(clientProvider);
            Assert.IsNotNull(clientProvider!.LastContractView);

            clientProvider!.ProcessTypeForBackCompatibility();

            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider!, name => name == "GetData" || name == "GetDataAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void ServerTemplateWithBasePathOnly_DoesNotDuplicateBasePath()
        {
            // This tests a scenario where the server template includes a base path:
            // - serverUrlTemplate = "{endpoint}/contentsafety"
            // - operation.Uri = "{endpoint}/contentsafety" (same as server template, no additional segments)
            // - operation.Path = "/text:analyze" (actual operation path)
            // Expected: Should append both the base path from server template and the operation path

            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{endpoint}/contentsafety";
            var operation = InputFactory.Operation(
                name: "AnalyzeText",
                uri: serverTemplate,
                path: "/text:analyze",
                httpMethod: "POST");

            var serviceMethod = InputFactory.BasicServiceMethod("AnalyzeText", operation);
            var client = InputFactory.Client(
                "ContentSafety",
                methods: [serviceMethod],
                parameters: [InputFactory.EndpointParameter("endpoint", InputPrimitiveType.String, serverUrlTemplate: serverTemplate)]);
            var clientProvider = new ClientProvider(client);
            var createRequestMethod = clientProvider.RestClient.Methods.FirstOrDefault(m => m.Signature.Name == "CreateAnalyzeTextRequest");
            Assert.IsNotNull(createRequestMethod);

            var methodBody = createRequestMethod!.BodyStatements!.ToArray();
            var bodyText = methodBody.Select(s => s.ToDisplayString()).ToArray();
            var fullText = string.Join("\n", bodyText);

            var contentsafetyCount = System.Text.RegularExpressions.Regex.Matches(fullText, "contentsafety", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Count;

            var analyzeTextCount = System.Text.RegularExpressions.Regex.Matches(fullText, "text:analyze", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Count;

            Assert.AreEqual(1, contentsafetyCount, "Should append /contentsafety path segment from Uri (after endpoint)");
            Assert.AreEqual(1, analyzeTextCount, "Should append the operation path /text:analyze exactly once");
        }

        [Test]
        public void ServerTemplateWithPathParameter_OnlyAppendsSegmentsAfterEndpoint()
        {
            // This tests scenarios like:
            // - serverUrlTemplate = "{endpoint}/{apiVersion}"
            // - operation.Uri = "{endpoint}/{apiVersion}" (path parameter in Uri)
            // - operation.Path = "/users/{userId}" (actual operation path with its own parameter)
            // Expected: Should append apiVersion parameter and then the operation path

            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{endpoint}/{apiVersion}";
            var apiVersionParam = InputFactory.PathParameter("apiVersion", InputPrimitiveType.String, serverUrlTemplate: serverTemplate);
            var userIdParam = InputFactory.PathParameter("userId", InputPrimitiveType.String);

            var operation = InputFactory.Operation(
                name: "GetUser",
                uri: serverTemplate,
                path: "/users/{userId}",
                httpMethod: "GET",
                parameters: [apiVersionParam, userIdParam]);

            var serviceMethod = InputFactory.BasicServiceMethod("GetUser", operation);
            var client = InputFactory.Client(
                "UserService",
                methods: [serviceMethod],
                parameters: [InputFactory.EndpointParameter("endpoint", InputPrimitiveType.String, serverUrlTemplate: serverTemplate)]);
            var clientProvider = new ClientProvider(client);
            var createRequestMethod = clientProvider.RestClient.Methods.FirstOrDefault(m => m.Signature.Name == "CreateGetUserRequest");
            Assert.IsNotNull(createRequestMethod);

            var methodBody = createRequestMethod!.BodyStatements!.ToArray();
            var bodyText = methodBody.Select(s => s.ToDisplayString()).ToArray();
            var fullText = string.Join("\n", bodyText);

            Assert.IsTrue(fullText.Contains("AppendPath(") && fullText.Contains("apiVersion"),
                "Should append apiVersion path parameter from Uri");

            Assert.IsTrue(fullText.Contains("/users/"),
                "Should append the operation path /users/");
            Assert.IsTrue(fullText.Contains("userId"),
                "Should append userId path parameter from operation path");
        }

        [Test]
        public void ServerTemplateWithMultipleSegments_HandlesCorrectly()
        {
            // This tests scenarios like:
            // - serverUrlTemplate = "{endpoint}/v1/services"
            // - operation.Uri = "{endpoint}/v1/services" (multiple static segments after endpoint)
            // - operation.Path = "/operations/{operationId}"
            // Expected: Should append /v1/services and then the operation path

            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{endpoint}/v1/services";
            var operationIdParam = InputFactory.PathParameter("operationId", InputPrimitiveType.String);

            var operation = InputFactory.Operation(
                name: "GetOperation",
                uri: serverTemplate,
                path: "/operations/{operationId}",
                httpMethod: "GET",
                parameters: [operationIdParam]);

            var serviceMethod = InputFactory.BasicServiceMethod("GetOperation", operation);
            var client = InputFactory.Client(
                "OperationService",
                methods: [serviceMethod],
                parameters: [InputFactory.EndpointParameter("endpoint", InputPrimitiveType.String, serverUrlTemplate: serverTemplate)]);
            var clientProvider = new ClientProvider(client);
            var createRequestMethod = clientProvider.RestClient.Methods.FirstOrDefault(m => m.Signature.Name == "CreateGetOperationRequest");
            Assert.IsNotNull(createRequestMethod);

            var methodBody = createRequestMethod!.BodyStatements!.ToArray();
            var bodyText = methodBody.Select(s => s.ToDisplayString()).ToArray();
            var fullText = string.Join("\n", bodyText);

            // Should append /v1/services from Uri
            Assert.IsTrue(fullText.Contains("/v1/services"),
                "Should append server template path segments /v1/services");

            Assert.IsTrue(fullText.Contains("/operations/"),
                "Should append the operation path /operations/");
            Assert.IsTrue(fullText.Contains("operationId"),
                "Should append operationId path parameter");
        }

        [Test]
        public void ServerTemplateEqualsEndpoint_OnlyAppendsOperationPath()
        {
            // This tests the simplest scenario:
            // - serverUrlTemplate = "{endpoint}"
            // - operation.Uri = "{endpoint}" (same as server template)
            // - operation.Path = "/items"
            // Expected: Should only append the operation path

            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{endpoint}";
            var operation = InputFactory.Operation(
                name: "GetItems",
                uri: serverTemplate,
                path: "/items",
                httpMethod: "GET");

            var serviceMethod = InputFactory.BasicServiceMethod("GetItems", operation);
            var client = InputFactory.Client(
                "ItemService",
                methods: [serviceMethod],
                parameters: [InputFactory.EndpointParameter("endpoint", InputPrimitiveType.String, serverUrlTemplate: serverTemplate)]);
            var clientProvider = new ClientProvider(client);
            var createRequestMethod = clientProvider.RestClient.Methods.FirstOrDefault(m => m.Signature.Name.Contains("GetItems"));
            Assert.IsNotNull(createRequestMethod);

            var methodBody = createRequestMethod!.BodyStatements!.ToArray();
            var bodyText = methodBody.Select(s => s.ToDisplayString()).ToArray();
            var fullText = string.Join("\n", bodyText);

            var appendPathCount = System.Text.RegularExpressions.Regex.Matches(fullText, "AppendPath\\(").Count;

            Assert.GreaterOrEqual(appendPathCount, 1,
                "Should have at least one AppendPath call for the operation path");
            Assert.IsTrue(fullText.Contains("/items"),
                "Should append the operation path /items");
        }

        [Test]
        public void MultiServiceClient_GeneratesExpectedClient()
        {
            // Setup multiservice client with multiple API version enums
            List<string> serviceAVersions = ["1.0", "2.0"];
            List<string> serviceBVersions = ["3.0", "4.0"];

            var serviceAEnumValues = serviceAVersions.Select(a => (a, a));
            var serviceBEnumValues = serviceBVersions.Select(a => (a, a));

            var serviceAEnum = InputFactory.StringEnum(
                "ServiceVersionA",
                serviceAEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceA");
            var serviceBEnum = InputFactory.StringEnum(
                "ServiceVersionB",
                serviceBEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceB");

            InputParameter apiVersionParameter = InputFactory.PathParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            InputParameter subscriptionIdParameter = InputFactory.PathParameter(
                "subscriptionId",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var client = InputFactory.Client(TestClientName, parameters: [subscriptionIdParameter, apiVersionParameter], isMultiServiceClient: true);
            var serviceAClient = InputFactory.Client("ServiceA", clientNamespace: "Sample.ServiceA", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]);
            var serviceBClient = InputFactory.Client("ServiceB", clientNamespace: "Sample.ServiceB", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceAVersions, .. serviceBVersions],
                clients: () => [client, serviceAClient, serviceBClient],
                inputEnums: () => [serviceAEnum, serviceBEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);

            Assert.IsNotNull(clientProvider);

            var writer = new TypeProviderWriter(clientProvider!);
            var file = writer.Write();

            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void MultiServiceClient_WithThreeServices_GeneratesExpectedClient()
        {
            // Setup multiservice client with three different services (KeyVault, Storage, Compute)
            List<string> keyVaultVersions = ["7.4", "7.5"];
            List<string> storageVersions = ["2023-01-01", "2024-01-01"];
            List<string> computeVersions = ["2023-07-01", "2024-03-01", "2024-07-01"];

            var keyVaultEnumValues = keyVaultVersions.Select(a => (a, a));
            var storageEnumValues = storageVersions.Select(a => (a, a));
            var computeEnumValues = computeVersions.Select(a => (a, a));

            var keyVaultEnum = InputFactory.StringEnum(
                "KeyVaultVersion",
                keyVaultEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.KeyVault");
            var storageEnum = InputFactory.StringEnum(
                "StorageVersion",
                storageEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.Storage");
            var computeEnum = InputFactory.StringEnum(
                "ComputeVersion",
                computeEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.Compute");

            InputParameter apiVersionParameter = InputFactory.PathParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            InputParameter subscriptionIdParameter = InputFactory.PathParameter(
                "subscriptionId",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var client = InputFactory.Client(TestClientName, parameters: [subscriptionIdParameter, apiVersionParameter], isMultiServiceClient: true);
            var keyVaultClient = InputFactory.Client("KeyVault", clientNamespace: "Sample.KeyVault", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]);
            var storageClient = InputFactory.Client("Storage", clientNamespace: "Sample.Storage", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]);
            var computeClient = InputFactory.Client("Compute", clientNamespace: "Sample.Compute", parent: client, initializedBy: InputClientInitializedBy.Parent, parameters: [apiVersionParameter, subscriptionIdParameter]);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. keyVaultVersions, .. storageVersions, .. computeVersions],
                clients: () => [client, keyVaultClient, storageClient, computeClient],
                inputEnums: () => [keyVaultEnum, storageEnum, computeEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);

            Assert.IsNotNull(clientProvider);

            var writer = new TypeProviderWriter(clientProvider!);
            var file = writer.Write();

            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void MultiServiceCombinedClient_GeneratesExpectedClient()
        {
            // Setup multiservice client with multiple API version enums and operations
            List<string> serviceAVersions = ["1.0", "2.0"];
            List<string> serviceBVersions = ["3.0", "4.0"];

            var serviceAEnumValues = serviceAVersions.Select(a => (a, a));
            var serviceBEnumValues = serviceBVersions.Select(a => (a, a));

            var serviceAEnum = InputFactory.StringEnum(
                "ServiceVersionA",
                serviceAEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceA");
            var serviceBEnum = InputFactory.StringEnum(
                "ServiceVersionB",
                serviceBEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceB");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            // Create operations with namespace set to each service
            var serviceAOperation = InputFactory.Operation(
                "ServiceAOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceA");

            var serviceBOperation = InputFactory.Operation(
                "ServiceBOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceB");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("ServiceAMethod", serviceAOperation),
                    InputFactory.BasicServiceMethod("ServiceBMethod", serviceBOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceAVersions, .. serviceBVersions],
                clients: () => [client],
                inputEnums: () => [serviceAEnum, serviceBEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            var writer = new TypeProviderWriter(clientProvider!);
            var file = writer.Write();

            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void MultiServiceCombinedClient_WithThreeServices_GeneratesExpectedClient()
        {
            // Setup multiservice combined client with three different services (KeyVault, Storage, Compute)
            List<string> keyVaultVersions = ["7.4", "7.5"];
            List<string> storageVersions = ["2023-01-01", "2024-01-01"];
            List<string> computeVersions = ["2023-07-01", "2024-03-01", "2024-07-01"];

            var keyVaultEnumValues = keyVaultVersions.Select(a => (a, a));
            var storageEnumValues = storageVersions.Select(a => (a, a));
            var computeEnumValues = computeVersions.Select(a => (a, a));

            var keyVaultEnum = InputFactory.StringEnum(
                "KeyVaultVersion",
                keyVaultEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.KeyVault");
            var storageEnum = InputFactory.StringEnum(
                "StorageVersion",
                storageEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.Storage");
            var computeEnum = InputFactory.StringEnum(
                "ComputeVersion",
                computeEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.Compute");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            // Create operations with namespace set to each service
            var keyVaultOperation = InputFactory.Operation(
                "KeyVaultOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.KeyVault");

            var storageOperation = InputFactory.Operation(
                "StorageOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.Storage");

            var computeOperation = InputFactory.Operation(
                "ComputeOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.Compute");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("KeyVaultMethod", keyVaultOperation),
                    InputFactory.BasicServiceMethod("StorageMethod", storageOperation),
                    InputFactory.BasicServiceMethod("ComputeMethod", computeOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. keyVaultVersions, .. storageVersions, .. computeVersions],
                clients: () => [client],
                inputEnums: () => [keyVaultEnum, storageEnum, computeEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            var writer = new TypeProviderWriter(clientProvider!);
            var file = writer.Write();

            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void GetApiVersionFieldForService_SingleService_ReturnsField()
        {
            // Setup single service client with one API version
            List<string> serviceVersions = ["1.0", "2.0"];

            var serviceEnumValues = serviceVersions.Select(a => (a, a));
            var serviceEnum = InputFactory.StringEnum(
                "ServiceVersion",
                serviceEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            var operation = InputFactory.Operation(
                "TestOperation",
                parameters: [apiVersionParameter],
                ns: "Sample");

            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("TestMethod", operation)],
                parameters: [apiVersionParameter]);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceVersions],
                clients: () => [client],
                inputEnums: () => [serviceEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            // Trigger the lazy initialization of Fields which populates _apiVersionFields
            _ = clientProvider!.Fields;

            // Should return the single API version field regardless of namespace
            var field = clientProvider!.GetApiVersionFieldForService("Sample");
            Assert.IsNotNull(field);
            Assert.AreEqual("_apiVersion", field!.Name);

            // Should also return the field when namespace is null
            var fieldWithNull = clientProvider.GetApiVersionFieldForService(null);
            Assert.IsNotNull(fieldWithNull);
            Assert.AreEqual("_apiVersion", fieldWithNull!.Name);
        }

        [Test]
        public void GetApiVersionFieldForService_MultiService_ReturnsMatchingField()
        {
            // Setup multiservice client with multiple API version enums
            List<string> serviceAVersions = ["1.0", "2.0"];
            List<string> serviceBVersions = ["3.0", "4.0"];

            var serviceAEnumValues = serviceAVersions.Select(a => (a, a));
            var serviceBEnumValues = serviceBVersions.Select(a => (a, a));

            var serviceAEnum = InputFactory.StringEnum(
                "ServiceVersionA",
                serviceAEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceA");
            var serviceBEnum = InputFactory.StringEnum(
                "ServiceVersionB",
                serviceBEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceB");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            // Create operations with namespace set to each service
            var serviceAOperation = InputFactory.Operation(
                "ServiceAOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceA");

            var serviceBOperation = InputFactory.Operation(
                "ServiceBOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceB");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("ServiceAMethod", serviceAOperation),
                    InputFactory.BasicServiceMethod("ServiceBMethod", serviceBOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceAVersions, .. serviceBVersions],
                clients: () => [client],
                inputEnums: () => [serviceAEnum, serviceBEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            // Trigger the lazy initialization of Fields which populates _apiVersionFields
            _ = clientProvider!.Fields;

            // Should return the matching field for ServiceA
            var fieldA = clientProvider!.GetApiVersionFieldForService("Sample.ServiceA");
            Assert.IsNotNull(fieldA);
            Assert.AreEqual("_serviceAApiVersion", fieldA!.Name);

            // Should return the matching field for ServiceB
            var fieldB = clientProvider.GetApiVersionFieldForService("Sample.ServiceB");
            Assert.IsNotNull(fieldB);
            Assert.AreEqual("_serviceBApiVersion", fieldB!.Name);
        }

        [Test]
        public void GetApiVersionFieldForService_MultiService_WithNonMatchingNamespace_ReturnsNull()
        {
            // Setup multiservice client with multiple API version enums
            List<string> serviceAVersions = ["1.0", "2.0"];
            List<string> serviceBVersions = ["3.0", "4.0"];

            var serviceAEnumValues = serviceAVersions.Select(a => (a, a));
            var serviceBEnumValues = serviceBVersions.Select(a => (a, a));

            var serviceAEnum = InputFactory.StringEnum(
                "ServiceVersionA",
                serviceAEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceA");
            var serviceBEnum = InputFactory.StringEnum(
                "ServiceVersionB",
                serviceBEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceB");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            // Create operations with namespace set to each service
            var serviceAOperation = InputFactory.Operation(
                "ServiceAOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceA");

            var serviceBOperation = InputFactory.Operation(
                "ServiceBOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceB");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("ServiceAMethod", serviceAOperation),
                    InputFactory.BasicServiceMethod("ServiceBMethod", serviceBOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceAVersions, .. serviceBVersions],
                clients: () => [client],
                inputEnums: () => [serviceAEnum, serviceBEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            // Trigger the lazy initialization of Fields which populates _apiVersionFields
            _ = clientProvider!.Fields;

            // Should return null for non-matching namespace
            var field = clientProvider!.GetApiVersionFieldForService("Sample.ServiceC");
            Assert.IsNull(field);
        }

        [Test]
        public void GetApiVersionFieldForService_MultiService_CaseInsensitiveMatch()
        {
            // Setup multiservice client with multiple API version enums
            List<string> serviceAVersions = ["1.0", "2.0"];
            List<string> serviceBVersions = ["3.0", "4.0"];

            var serviceAEnumValues = serviceAVersions.Select(a => (a, a));
            var serviceBEnumValues = serviceBVersions.Select(a => (a, a));

            var serviceAEnum = InputFactory.StringEnum(
                "ServiceVersionA",
                serviceAEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceA");
            var serviceBEnum = InputFactory.StringEnum(
                "ServiceVersionB",
                serviceBEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Sample.ServiceB");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            // Create operations with namespace set to each service
            var serviceAOperation = InputFactory.Operation(
                "ServiceAOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceA");

            var serviceBOperation = InputFactory.Operation(
                "ServiceBOperation",
                parameters: [apiVersionParameter],
                ns: "Sample.ServiceB");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("ServiceAMethod", serviceAOperation),
                    InputFactory.BasicServiceMethod("ServiceBMethod", serviceBOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceAVersions, .. serviceBVersions],
                clients: () => [client],
                inputEnums: () => [serviceAEnum, serviceBEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            // Trigger the lazy initialization of Fields which populates _apiVersionFields
            _ = clientProvider!.Fields;

            // Should match case-insensitively
            var fieldLowerCase = clientProvider!.GetApiVersionFieldForService("sample.serviceA");
            Assert.IsNotNull(fieldLowerCase);
            Assert.AreEqual("_serviceAApiVersion", fieldLowerCase!.Name);

            var fieldUpperCase = clientProvider.GetApiVersionFieldForService("SAMPLE.SERVICEa");
            Assert.IsNotNull(fieldUpperCase);
            Assert.AreEqual("_serviceAApiVersion", fieldUpperCase!.Name);
        }

        [Test]
        public void GetApiVersionFieldForService_MultiService_SameLastSegment_ProducesUniqueFields()
        {
            // Regression test: when two services have different full namespaces but the same last
            // segment, using only the last segment would produce duplicate field names. The fix
            // uses the full namespace to guarantee uniqueness.
            List<string> serviceOneVersions = ["1.0", "2.0"];
            List<string> serviceTwoVersions = ["3.0", "4.0"];

            var serviceOneEnumValues = serviceOneVersions.Select(a => (a, a));
            var serviceTwoEnumValues = serviceTwoVersions.Select(a => (a, a));

            // Different full namespaces, same last segment ("Tests") — would collide with last-segment-only naming
            var serviceOneEnum = InputFactory.StringEnum(
                "ServiceOneVersions",
                serviceOneEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Azure.ServiceOne.Tests");
            var serviceTwoEnum = InputFactory.StringEnum(
                "ServiceTwoVersions",
                serviceTwoEnumValues,
                usage: InputModelTypeUsage.ApiVersionEnum,
                clientNamespace: "Azure.ServiceTwo.Tests");

            InputParameter apiVersionParameter = InputFactory.QueryParameter(
                "apiVersion",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                isApiVersion: true);

            var serviceOneOperation = InputFactory.Operation(
                "ServiceOneOperation",
                parameters: [apiVersionParameter],
                ns: "Azure.ServiceOne.Tests");

            var serviceTwoOperation = InputFactory.Operation(
                "ServiceTwoOperation",
                parameters: [apiVersionParameter],
                ns: "Azure.ServiceTwo.Tests");

            var client = InputFactory.Client(
                TestClientName,
                methods:
                [
                    InputFactory.BasicServiceMethod("ServiceOneMethod", serviceOneOperation),
                    InputFactory.BasicServiceMethod("ServiceTwoMethod", serviceTwoOperation)
                ],
                parameters: [apiVersionParameter],
                isMultiServiceClient: true);

            MockHelpers.LoadMockGenerator(
                apiVersions: () => [.. serviceOneVersions, .. serviceTwoVersions],
                clients: () => [client],
                inputEnums: () => [serviceOneEnum, serviceTwoEnum]);

            var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
            Assert.IsNotNull(clientProvider);

            // This should not crash — previously it threw due to duplicate field names
            Assert.DoesNotThrow(() => _ = clientProvider!.Fields);

            // Verify we have two distinct api version fields using the shortest unique namespace suffix
            var apiVersionFields = clientProvider!.Fields
                .Where(f => f.Name.Contains("ApiVersion", StringComparison.OrdinalIgnoreCase))
                .OrderBy(f => f.Name)
                .ToList();
            Assert.AreEqual(2, apiVersionFields.Count);
            Assert.AreNotEqual(apiVersionFields[0].Name, apiVersionFields[1].Name);

            // Shortest unique suffix: "ServiceOne.Tests" → "ServiceOneTests"
            Assert.AreEqual("_serviceOneTestsApiVersion", apiVersionFields[0].Name);
            Assert.AreEqual("_serviceTwoTestsApiVersion", apiVersionFields[1].Name);
        }

        [TestCase("{endpoint}")]
        [TestCase("{Endpoint}")]
        [TestCase("{ENDPOINT}")]
        public void ConvertUriTemplate_CaseInsensitiveEndpointLookup(string serverTemplate)
        {
            // Tests that the parameter lookup in ConvertUriTemplateToFormattableString is case-insensitive
            MockHelpers.LoadMockGenerator();
            var client = InputFactory.Client(
                TestClientName,
                parameters: [InputFactory.EndpointParameter(
                    "endpoint",
                    InputPrimitiveType.String,
                    isRequired: true,
                    scope: InputParameterScope.Client,
                    serverUrlTemplate: serverTemplate,
                    isEndpoint: true)]);
            var clientProvider = new ClientProvider(client);
            var constructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);

            Assert.IsNotNull(constructor);
            // Should not throw and should contain the Uri assignment
            var bodyText = constructor!.BodyStatements!.ToDisplayString();
            Assert.IsTrue(bodyText.Contains("_endpoint = new global::System.Uri($\""));
        }

        [Test]
        public void ConvertUriTemplate_CaseInsensitivePathParameterLookup()
        {
            // Tests template with mixed case placeholders like "{Endpoint}/services/{ApiVersion}"
            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{Endpoint}/{ApiVersion}";
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Test", InputFactory.Operation("test", uri: serverTemplate))],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint", // lowercase parameter name
                        InputPrimitiveType.String,
                        isRequired: true,
                        scope: InputParameterScope.Client,
                        serverUrlTemplate: serverTemplate,
                        isEndpoint: true),
                    InputFactory.PathParameter(
                        "apiVersion", // lowercase parameter name
                        InputPrimitiveType.String,
                        isRequired: true,
                        scope: InputParameterScope.Client)
                ]);
            var clientProvider = new ClientProvider(client);
            var constructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);

            Assert.IsNotNull(constructor);
            // Should not throw - case-insensitive lookup should find parameters
            var bodyText = constructor!.BodyStatements!.ToDisplayString();
            Assert.IsNotNull(bodyText);
            // Verify that the Uri is built according to the server template with case-insensitive parameter matching
            Assert.IsTrue(bodyText.Contains("$\"{endpoint}/{_apiVersion}\""));
        }

        [Test]
        public void ConvertUriTemplate_WithMultiplePlaceholders()
        {
            // Tests template with multiple placeholders: "{endpoint}/{apiVersion}/services/{subscriptionId}"
            MockHelpers.LoadMockGenerator();

            var serverTemplate = "{endpoint}/{apiVersion}/services/{subscriptionId}";
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Test", InputFactory.Operation("test", uri: serverTemplate))],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint",
                        InputPrimitiveType.String,
                        isRequired: true,
                        scope: InputParameterScope.Client,
                        serverUrlTemplate: serverTemplate,
                        isEndpoint: true),
                    InputFactory.PathParameter(
                        "apiVersion",
                        InputPrimitiveType.String,
                        isRequired: true,
                        scope: InputParameterScope.Client),
                    InputFactory.PathParameter(
                        "subscriptionId",
                        InputPrimitiveType.String,
                        isRequired: true,
                        scope: InputParameterScope.Client)
                ]);
            var clientProvider = new ClientProvider(client);
            var constructor = clientProvider.Constructors.FirstOrDefault(
                c => c.Signature.Initializer == null && c.Signature?.Modifiers == MethodSignatureModifiers.Internal);

            Assert.IsNotNull(constructor);
            var bodyText = constructor!.BodyStatements!.ToDisplayString();
            Assert.IsNotNull(bodyText);
            // Verify that the Uri is built according to the server template
            Assert.IsTrue(bodyText.Contains("$\"{endpoint}/{_apiVersion}/services/{_subscriptionId}\""));
        }

        [Test]
        public void TestParamAlias_ClientConstructorDoesNotDuplicateAliasedParameter()
        {
            // A client parameter "blob" with paramAlias "blobName" should not produce
            // a duplicate field when the operation also declares "blobName" as client-scoped.
            var clientParam = InputFactory.MethodParameter(
                "blob",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                paramAlias: "blobName");

            var operationParam = InputFactory.PathParameter(
                "blobName",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operation = InputFactory.Operation("Upload", parameters: [operationParam]);
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Upload", operation)],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                        scope: InputParameterScope.Client,
                        isEndpoint: true),
                    clientParam]);

            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // Should have exactly one field for the blob/blobName parameter (not two)
            var blobFields = clientProvider.Fields.Where(f => f.Name == "_blob" || f.Name == "_blobName").ToList();
            Assert.AreEqual(1, blobFields.Count,
                $"Expected 1 field but found {blobFields.Count}: {string.Join(", ", blobFields.Select(f => f.Name))}");
            Assert.AreEqual("_blob", blobFields[0].Name);

            // ClientParameters should contain only the "blob" parameter
            var blobParams = clientProvider.ClientParameters.Where(
                p => p.Name == "blob" || p.Name == "blobName").ToList();
            Assert.AreEqual(1, blobParams.Count,
                $"Expected 1 client parameter but found {blobParams.Count}: {string.Join(", ", blobParams.Select(p => p.Name))}");
            Assert.AreEqual("blob", blobParams[0].Name);
        }

        [Test]
        public void TestParamAlias_ClientConstructorKeepsBothWhenNoAlias()
        {
            // Without paramAlias, a client parameter "blob" and operation parameter "blobName"
            // should both appear since they have different names.
            var clientParam = InputFactory.MethodParameter(
                "blob",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operationParam = InputFactory.PathParameter(
                "blobName",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operation = InputFactory.Operation("Upload", parameters: [operationParam]);
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Upload", operation)],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                        scope: InputParameterScope.Client,
                        isEndpoint: true),
                    clientParam]);

            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // Without alias, both fields should exist
            var blobFields = clientProvider.Fields.Where(f => f.Name == "_blob" || f.Name == "_blobName").ToList();
            Assert.AreEqual(2, blobFields.Count,
                $"Expected 2 fields but found {blobFields.Count}: {string.Join(", ", blobFields.Select(f => f.Name))}");
        }

        [Test]
        public void TestParamAlias_MatchingNamesWithoutAliasDeduplicate()
        {
            // When the client parameter and operation parameter share the same name,
            // deduplication occurs by name even without paramAlias.
            var clientParam = InputFactory.MethodParameter(
                "blobName",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operationParam = InputFactory.PathParameter(
                "blobName",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operation = InputFactory.Operation("Upload", parameters: [operationParam]);
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Upload", operation)],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                        scope: InputParameterScope.Client,
                        isEndpoint: true),
                    clientParam]);

            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // Same name means they get deduplicated to one field
            var blobFields = clientProvider.Fields.Where(f => f.Name == "_blobName").ToList();
            Assert.AreEqual(1, blobFields.Count);
        }

        [Test]
        public void TestParamAlias_MethodParametersSkipAliasedClientParam()
        {
            // When a client parameter "blob" is aliased to "blobName" via @paramAlias,
            // the generated operation method should NOT have "blobName" as a method parameter
            // since it's already on the client.
            var clientParam = InputFactory.MethodParameter(
                "blob",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client,
                paramAlias: "blobName");

            var operationParam = InputFactory.PathParameter(
                "blobName",
                InputPrimitiveType.String,
                isRequired: true,
                scope: InputParameterScope.Client);

            var operation = InputFactory.Operation("Upload", parameters: [operationParam]);
            var client = InputFactory.Client(
                TestClientName,
                methods: [InputFactory.BasicServiceMethod("Upload", operation)],
                parameters: [
                    InputFactory.EndpointParameter(
                        "endpoint",
                        InputPrimitiveType.String,
                        defaultValue: InputFactory.Constant.String("https://default.endpoint.io"),
                        scope: InputParameterScope.Client,
                        isEndpoint: true),
                    clientParam]);

            var clientProvider = new ClientProvider(client);

            Assert.IsNotNull(clientProvider);

            // Get the generated operation methods (excluding subclient accessors and constructors)
            var operationMethods = clientProvider.Methods
                .Where(m => m.Signature?.Name == "Upload" || m.Signature?.Name == "UploadAsync")
                .ToList();

            Assert.IsTrue(operationMethods.Count > 0, "Should have Upload methods");

            foreach (var method in operationMethods)
            {
                var paramNames = method.Signature!.Parameters.Select(p => p.Name).ToList();
                Assert.IsFalse(paramNames.Contains("blobName"),
                    $"Method '{method.Signature.Name}' should not have 'blobName' parameter since it's an aliased client parameter. " +
                    $"Params: [{string.Join(", ", paramNames)}]");
                Assert.IsFalse(paramNames.Contains("blob"),
                    $"Method '{method.Signature.Name}' should not have 'blob' parameter since it's a client parameter. " +
                    $"Params: [{string.Join(", ", paramNames)}]");
            }
        }

        [Test]
        public void TestIsExactNameClientPreservesNameVerbatim()
        {
            // A client marked with isExactName should bypass ToIdentifierName() casing.
            var client = InputFactory.Client("snake_case_client", isExactName: true);
            var clientProvider = new ClientProvider(client);

            Assert.AreEqual("snake_case_client", clientProvider.Name);
        }

        [Test]
        public void TestNonExactNameClientStillCased()
        {
            // A client without isExactName should still go through ToIdentifierName().
            var client = InputFactory.Client("snake_case_client");
            var clientProvider = new ClientProvider(client);

            Assert.AreEqual("SnakeCaseClient", clientProvider.Name);
        }

        [Test]
        public void TestIsExactNameServiceMethodPreservesOperationNameVerbatim()
        {
            // A service method marked with isExactName should preserve the operation name verbatim
            // (no PascalCase transformation, no "List" -> "Get" rename).
            var inputOperation = InputFactory.Operation("snake_case_op", isExactName: true);
            var inputServiceMethod = InputFactory.BasicServiceMethod("snake_case_op", inputOperation, isExactName: true);
            var client = InputFactory.Client("TestClient", methods: [inputServiceMethod]);
            _ = new ClientProvider(client);

            // After CleanOperationNames runs in the ClientProvider constructor, names should be unchanged.
            Assert.AreEqual("snake_case_op", inputServiceMethod.Name);
            Assert.AreEqual("snake_case_op", inputServiceMethod.Operation.Name);
        }

        [Test]
        public void TestIsExactNameServiceMethodSkipsListToGetRename()
        {
            // The normal CleanOperationNames behavior renames "List" -> "GetAll" and "ListFoo" -> "GetFoo".
            // When isExactName is true, even an operation literally named "List" must be preserved verbatim.
            var inputOperation = InputFactory.Operation("List", isExactName: true);
            var inputServiceMethod = InputFactory.BasicServiceMethod("List", inputOperation, isExactName: true);
            var client = InputFactory.Client("TestClient", methods: [inputServiceMethod]);
            _ = new ClientProvider(client);

            Assert.AreEqual("List", inputServiceMethod.Name);
            Assert.AreEqual("List", inputServiceMethod.Operation.Name);
        }

        [Test]
        public void TestNonExactNameServiceMethodAppliesListRename()
        {
            // Sanity check that without isExactName the existing rename still applies.
            var inputOperation = InputFactory.Operation("List");
            var inputServiceMethod = InputFactory.BasicServiceMethod("List", inputOperation);
            var client = InputFactory.Client("TestClient", methods: [inputServiceMethod]);
            _ = new ClientProvider(client);

            Assert.AreEqual("GetAll", inputServiceMethod.Name);
            Assert.AreEqual("GetAll", inputServiceMethod.Operation.Name);
        }
        private static ClientProvider BuildMultipartClient(InputModelType bodyModel, bool bodyIsRequired = true)
        {
            var body = InputFactory.MethodParameter(
                "body",
                bodyModel,
                isRequired: bodyIsRequired,
                location: InputRequestLocation.Body);

            var bodyOperationParameter = InputFactory.BodyParameter(
                "body",
                bodyModel,
                isRequired: bodyIsRequired,
                contentTypes: ["multipart/form-data"],
                defaultContentType: "multipart/form-data");

            var operation = InputFactory.Operation(
                "Upload",
                requestMediaTypes: ["multipart/form-data"],
                parameters: [InputFactory.ContentTypeParameter("multipart/form-data"), bodyOperationParameter]);

            var serviceMethod = InputFactory.BasicServiceMethod("Upload", operation, parameters: [body]);
            var inputClient = InputFactory.Client("MultipartClient", methods: [serviceMethod]);

            MockHelpers.LoadMockGenerator(
                auth: () => new(new InputApiKeyAuth("mock", null), null),
                clients: () => [inputClient],
                inputModels: () => [bodyModel]);

            return ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!;
        }

        [Test]
        public void TestMultipartClient_UploadMethods_RequiredFileBody()
        {
            var inputModel = MultipartModel(
                "MultiPartRequest",
                [
                    NonFilePartProperty("id", InputPrimitiveType.String),
                    FilePartProperty("profileImage"),
                ]);

            var clientProvider = BuildMultipartClient(inputModel);
            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider, name => name == "Upload" || name == "UploadAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void TestMultipartClient_UploadMethods_MultiFileOnlyBody()
        {
            var inputModel = MultipartModel(
                "BinaryArrayPartsRequest",
                [MultiFilePartProperty("pictures")]);

            var clientProvider = BuildMultipartClient(inputModel);
            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider, name => name == "Upload" || name == "UploadAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void TestMultipartClient_UploadMethods_OptionalFileBody()
        {
            var inputModel = MultipartModel(
                "OptionalFileRequest",
                [
                    NonFilePartProperty("id", InputPrimitiveType.String),
                    FilePartProperty("optionalFile", isRequired: false),
                ]);

            var clientProvider = BuildMultipartClient(inputModel);
            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider, name => name == "Upload" || name == "UploadAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        [Test]
        public void TestMultipartClient_UploadMethods_OptionalBody()
        {
            var inputModel = MultipartModel(
                "MultiPartRequest",
                [
                    NonFilePartProperty("id", InputPrimitiveType.String),
                    FilePartProperty("profileImage"),
                ]);

            var clientProvider = BuildMultipartClient(inputModel, bodyIsRequired: false);
            var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(clientProvider, name => name == "Upload" || name == "UploadAsync"));
            var file = writer.Write();
            Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
        }

        private static InputModelProperty FilePartProperty(string name, bool isRequired = true)
            => InputFactory.Property(
                name,
                InputFactory.FileType(),
                isRequired: isRequired,
                serializationOptions: InputFactory.Serialization.Options(
                    multipart: InputFactory.Serialization.Multipart(name, isFilePart: true)));

        private static InputModelProperty MultiFilePartProperty(string name)
            => InputFactory.Property(
                name,
                InputFactory.Array(InputFactory.FileType()),
                isRequired: true,
                serializationOptions: InputFactory.Serialization.Options(
                    multipart: InputFactory.Serialization.Multipart(name, isFilePart: true, isMulti: true)));

        private static InputModelProperty NonFilePartProperty(string name, InputType type)
            => InputFactory.Property(
                name,
                type,
                isRequired: true,
                serializationOptions: InputFactory.Serialization.Options(
                    multipart: InputFactory.Serialization.Multipart(name, isFilePart: false, defaultContentTypes: ["text/plain"])));

        private static InputModelType MultipartModel(string name, IEnumerable<InputModelProperty> properties)
            => InputFactory.Model(name, usage: InputModelTypeUsage.Input | InputModelTypeUsage.MultipartFormData, properties: properties);

    }
}

