﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

namespace Microsoft.CodeAnalysis.ImplementInterface
{
    internal abstract partial class AbstractImplementInterfaceService
    {
        private static class AccessibilityHelper
        {
            public static bool IsLessAccessibleThan(ISymbol? first, INamedTypeSymbol second)
            {
                if (first is null)
                {
                    return false;
                }

                if (first.DeclaredAccessibility <= Accessibility.NotApplicable ||
                    second.DeclaredAccessibility <= Accessibility.NotApplicable)
                {
                    return false;
                }

                if (first.DeclaredAccessibility < second.DeclaredAccessibility)
                {
                    return true;
                }

                switch (first)
                {
                    case IPropertySymbol propertySymbol:
                        if (IsTypeLessAccessibleThanOtherType(propertySymbol.Type, second, new()))
                        {
                            return true;
                        }

                        if (IsLessAccessibleThan(propertySymbol.GetMethod, second))
                        {
                            return true;
                        }

                        if (IsLessAccessibleThan(propertySymbol.SetMethod, second))
                        {
                            return true;
                        }

                        return false;

                    case IMethodSymbol methodSymbol:
                        if (IsTypeLessAccessibleThanOtherType(methodSymbol.ReturnType, second, new()))
                        {
                            return true;
                        }

                        foreach (var parameter in methodSymbol.Parameters)
                        {
                            if (IsTypeLessAccessibleThanOtherType(parameter.Type, second, new()))
                            {
                                return true;
                            }
                        }

                        foreach (var typeArg in methodSymbol.TypeArguments)
                        {
                            if (IsTypeLessAccessibleThanOtherType(typeArg, second, new()))
                            {
                                return true;
                            }
                        }

                        return false;

                    case IEventSymbol eventSymbol:
                        return IsTypeLessAccessibleThanOtherType(eventSymbol.Type, second, new());

                    default:
                        return false;
                }
            }

            private static bool IsTypeLessAccessibleThanOtherType(ITypeSymbol? first, INamedTypeSymbol second, HashSet<ITypeSymbol> alreadyCheckingTypes)
            {
                if (first is null)
                {
                    return false;
                }

                alreadyCheckingTypes.Add(first);

                if (first is ITypeParameterSymbol typeParameter)
                {
                    foreach (var constraint in typeParameter.ConstraintTypes)
                    {
                        if (alreadyCheckingTypes.Contains(constraint))
                        {
                            continue;
                        }

                        if (IsTypeLessAccessibleThanOtherType(constraint, second, alreadyCheckingTypes))
                        {
                            return true;
                        }
                    }
                }

                if (first.DeclaredAccessibility <= Accessibility.NotApplicable ||
                    second.DeclaredAccessibility <= Accessibility.NotApplicable)
                {
                    return false;
                }

                if (first.DeclaredAccessibility < second.DeclaredAccessibility)
                {
                    return true;
                }

                if (first is INamedTypeSymbol namedType)
                {
                    foreach (var genericParam in namedType.TypeArguments)
                    {
                        if (alreadyCheckingTypes.Contains(genericParam))
                        {
                            continue;
                        }

                        if (IsTypeLessAccessibleThanOtherType(genericParam, second, alreadyCheckingTypes))
                        {
                            return true;
                        }
                    }
                }

                if (IsTypeLessAccessibleThanOtherType(first.ContainingType, second, alreadyCheckingTypes))
                {
                    return true;
                }

                return false;
            }
        }
    }
}
