﻿// 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.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders
{
    internal class InKeywordRecommender : AbstractSyntacticSingleKeywordRecommender
    {
        public InKeywordRecommender()
            : base(SyntaxKind.InKeyword)
        {
        }

        protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken)
        {
            var syntaxTree = context.SyntaxTree;
            return
                IsValidContextInForEachClause(context) ||
                IsValidContextInFromClause(context, cancellationToken) ||
                IsValidContextInJoinClause(context, cancellationToken) ||
                IsInParameterModifierContext(position, context) ||
                syntaxTree.IsAnonymousMethodParameterModifierContext(position, context.LeftToken) ||
                syntaxTree.IsPossibleLambdaParameterModifierContext(position, context.LeftToken, cancellationToken) ||
                context.TargetToken.IsConstructorOrMethodParameterArgumentContext() ||
                context.TargetToken.IsTypeParameterVarianceContext();
        }

        private static bool IsInParameterModifierContext(int position, CSharpSyntaxContext context)
        {
            if (context.SyntaxTree.IsParameterModifierContext(
                    position, context.LeftToken, includeOperators: true, out var parameterIndex, out var previousModifier))
            {
                if (previousModifier is SyntaxKind.None or SyntaxKind.ScopedKeyword)
                {
                    return true;
                }

                if (previousModifier == SyntaxKind.ThisKeyword &&
                    parameterIndex == 0 &&
                    context.SyntaxTree.IsPossibleExtensionMethodContext(context.LeftToken))
                {
                    return true;
                }
            }

            return false;
        }

        private static bool IsValidContextInForEachClause(CSharpSyntaxContext context)
        {
            // cases:
            //   foreach (var v |
            //   foreach (var v i|
            //   foreach (var (x, y) |

            var token = context.TargetToken;

            if (token.Kind() == SyntaxKind.IdentifierToken)
            {
                if (token.Parent is ForEachStatementSyntax statement && token == statement.Identifier)
                {
                    return true;
                }
            }
            else if (token.Kind() == SyntaxKind.CloseParenToken)
            {
                var statement = token.GetAncestor<ForEachVariableStatementSyntax>();
                if (statement != null && token.Span.End == statement.Variable.Span.End)
                {
                    return true;
                }
            }

            return false;
        }

        private static bool IsValidContextInFromClause(CSharpSyntaxContext context, CancellationToken cancellationToken)
        {
            var token = context.TargetToken;

            if (token.Kind() == SyntaxKind.IdentifierToken)
            {
                // case:
                //   from x |
                if (token.GetPreviousToken(includeSkipped: true).IsKindOrHasMatchingText(SyntaxKind.FromKeyword))
                {
                    var typeSyntax = token.Parent as TypeSyntax;
                    if (!typeSyntax.IsPotentialTypeName(context.SemanticModel, cancellationToken))
                    {
                        return true;
                    }
                }

                if (token.Parent is FromClauseSyntax fromClause)
                {
                    // case:
                    //   from int x |
                    if (token == fromClause.Identifier && fromClause.Type != null)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static bool IsValidContextInJoinClause(CSharpSyntaxContext context, CancellationToken cancellationToken)
        {
            var token = context.TargetToken;

            if (token.Kind() == SyntaxKind.IdentifierToken)
            {
                var joinClause = token.Parent?.FirstAncestorOrSelf<JoinClauseSyntax>();
                if (joinClause != null)
                {
                    // case:
                    //   join int x |
                    if (token == joinClause.Identifier && joinClause.Type != null)
                    {
                        return true;
                    }

                    // case:
                    //   join x |
                    if (joinClause.Type != null &&
                        joinClause.Type is IdentifierNameSyntax joinIdentifier &&
                        token == joinIdentifier.Identifier &&
                        !joinClause.Type.IsPotentialTypeName(context.SemanticModel, cancellationToken))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
    }
}
