﻿// 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;
using System.Composition;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.InlineMethod;

namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.InlineMethod
{
    [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InlineMethod), Shared]
    [Export(typeof(CSharpInlineMethodRefactoringProvider))]
    internal sealed class CSharpInlineMethodRefactoringProvider :
        AbstractInlineMethodRefactoringProvider<BaseMethodDeclarationSyntax, StatementSyntax, ExpressionSyntax, InvocationExpressionSyntax>
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public CSharpInlineMethodRefactoringProvider()
            : base(CSharpSyntaxFacts.Instance, CSharpSemanticFactsService.Instance)
        {
        }

        protected override ExpressionSyntax? GetRawInlineExpression(BaseMethodDeclarationSyntax methodDeclarationSyntax)
        {
            var blockSyntaxNode = methodDeclarationSyntax.Body;
            if (blockSyntaxNode != null)
            {
                // 1. If it is an ordinary method with block
                var blockStatements = blockSyntaxNode.Statements;
                if (blockStatements.Count == 1)
                {
                    var statementSyntax = blockStatements[0];
                    return statementSyntax switch
                    {
                        // Note: For this case this will return null in Callee()
                        // void Caller() { Callee(); }
                        // void Callee() { return; }
                        // Refactoring won't be provided for this case.
                        ReturnStatementSyntax returnStatementSyntax => returnStatementSyntax.Expression,
                        ExpressionStatementSyntax expressionStatementSyntax => expressionStatementSyntax.Expression,
                        ThrowStatementSyntax throwStatementSyntax => throwStatementSyntax.Expression,
                        _ => null
                    };
                }
            }
            else
            {
                // 2. If it is an Arrow Expression
                var arrowExpressionNode = methodDeclarationSyntax.ExpressionBody;
                return arrowExpressionNode?.Expression;
            }

            return null;
        }

        protected override SyntaxNode GenerateTypeSyntax(ITypeSymbol symbol, bool allowVar)
            => symbol.GenerateTypeSyntax(allowVar);

        protected override ExpressionSyntax GenerateLiteralExpression(ITypeSymbol typeSymbol, object? value)
            => ExpressionGenerator.GenerateExpression(CSharpSyntaxGenerator.Instance, typeSymbol, value, canUseFieldReference: true);

        protected override bool IsFieldDeclarationSyntax(SyntaxNode node)
            => node.IsKind(SyntaxKind.FieldDeclaration);

        protected override bool IsValidExpressionUnderExpressionStatement(ExpressionSyntax expressionNode)
        {
            // C# Expression Statements defined in the language reference
            // expression_statement
            //     : statement_expression ';'
            //     ;
            //
            // statement_expression
            //     : invocation_expression
            //     | null_conditional_invocation_expression
            //     | object_creation_expression
            //     | assignment
            //     | post_increment_expression
            //     | post_decrement_expression
            //     | pre_increment_expression
            //     | pre_decrement_expression
            //     | await_expression
            //     ;
            var isNullConditionalInvocationExpression = IsNullConditionalInvocationExpression(expressionNode);

            return expressionNode.IsKind(SyntaxKind.InvocationExpression)
                   || isNullConditionalInvocationExpression
                   || expressionNode.IsKind(SyntaxKind.ObjectCreationExpression)
                   || expressionNode is AssignmentExpressionSyntax
                   || expressionNode.IsKind(SyntaxKind.PreIncrementExpression)
                   || expressionNode.IsKind(SyntaxKind.PreDecrementExpression)
                   || expressionNode.IsKind(SyntaxKind.PostIncrementExpression)
                   || expressionNode.IsKind(SyntaxKind.PostDecrementExpression)
                   || expressionNode.IsKind(SyntaxKind.AwaitExpression);
        }

        protected override bool CanBeReplacedByThrowExpression(SyntaxNode syntaxNode)
        {
            // C# Throw Expression definition in language reference:
            // 'A throw expression is permitted in only the following syntactic contexts:
            // As the second or third operand of a ternary conditional operator ?:
            // As the second operand of a null coalescing operator ??
            // As the body of an expression-bodied lambda or method.'
            var parent = syntaxNode.Parent;
            if (parent is ConditionalExpressionSyntax conditionalExpressionSyntax)
            {
                return syntaxNode.Equals(conditionalExpressionSyntax.WhenTrue)
                    || syntaxNode.Equals(conditionalExpressionSyntax.WhenFalse);
            }

            if (parent is BinaryExpressionSyntax binaryExpressionSyntax && binaryExpressionSyntax.IsKind(SyntaxKind.CoalesceExpression))
            {
                return syntaxNode.Equals(binaryExpressionSyntax.Right);
            }

            if (parent is LambdaExpressionSyntax lambdaExpressionSyntax)
            {
                return lambdaExpressionSyntax.ExpressionBody != null;
            }

            return parent.IsKind(SyntaxKind.ArrowExpressionClause);
        }

        private static bool IsNullConditionalInvocationExpression(ExpressionSyntax expressionSyntax)
        {
            // Check if the expression syntax is like an invocation expression nested inside ConditionalAccessExpressionSyntax.
            // For example: a?.b.c()
            if (expressionSyntax is ConditionalAccessExpressionSyntax conditionalAccessExpressionSyntax)
            {
                var whenNotNull = conditionalAccessExpressionSyntax.WhenNotNull;
                // If the expression is ended with an invocation
                // (if the expressions in the middle are not ConditionalAccessExpressionSyntax),
                // like a?.b.e.c(), the syntax tree would be
                // ConditionalAccessExpressionSyntax -> InvocationExpression.
                // And in case of example like a?.b?.d?.c();
                // This is case it would be
                // ConditionalAccessExpressionSyntax -> ConditionalAccessExpressionSyntax -> ... -> InvocationExpression.
                return whenNotNull.IsKind(SyntaxKind.InvocationExpression) || IsNullConditionalInvocationExpression(whenNotNull);
            }

            return false;
        }
    }
}
