// 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.

#nullable disable

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.Debugging;

internal sealed partial class CSharpProximityExpressionsService
{
    internal sealed class Worker(SyntaxTree syntaxTree, int position)
    {
        private readonly SyntaxTree _syntaxTree = syntaxTree;
        private readonly int _position = position;

        private StatementSyntax _parentStatement;
        private SyntaxToken _token;
        private readonly List<string> _expressions = [];

        internal IList<string> Do(CancellationToken cancellationToken)
        {
            // First, find the containing statement.  We'll want to add the expressions in this
            // statement to the result.
            _token = _syntaxTree.GetRoot(cancellationToken).FindToken(_position);
            _parentStatement = _token.GetAncestor<StatementSyntax>();
            if (_parentStatement == null)
            {
                return null;
            }

            AddRelevantExpressions(_parentStatement, _expressions, includeDeclarations: false);
            AddPrecedingRelevantExpressions();
            AddFollowingRelevantExpressions(cancellationToken);
            AddCurrentDeclaration();
            AddMethodParameters();
            AddIndexerParameters();
            AddCatchParameters();
            AddThisExpression();
            AddValueExpression();

            var result = _expressions.Distinct().Where(e => e.Length > 0).ToList();
            return result.Count == 0 ? null : result;
        }

        private void AddValueExpression()
        {
            // If we're in a setter/adder/remover then add "value".
            if (_parentStatement.GetAncestorOrThis<AccessorDeclarationSyntax>() is (kind:
                    SyntaxKind.SetAccessorDeclaration or
                    SyntaxKind.InitAccessorDeclaration or
                    SyntaxKind.AddAccessorDeclaration or
                    SyntaxKind.RemoveAccessorDeclaration))
            {
                _expressions.Add("value");
            }
        }

        private void AddThisExpression()
        {
            // If it's an instance member, then also add "this".
            var memberDeclaration = _parentStatement.GetAncestorOrThis<MemberDeclarationSyntax>();
            if (!memberDeclaration.IsKind(SyntaxKind.GlobalStatement) && !memberDeclaration.GetModifiers().Any(SyntaxKind.StaticKeyword))
            {
                _expressions.Add("this");
            }
        }

        private void AddCatchParameters()
        {
            var block = GetImmediatelyContainingBlock();

            // if we're the start of a "catch(Goo e)" clause, then add "e".
            if (block != null && block?.Parent is CatchClauseSyntax catchClause &&
                catchClause.Declaration != null && catchClause.Declaration.Identifier.Kind() != SyntaxKind.None)
            {
                _expressions.Add(catchClause.Declaration.Identifier.ValueText);
            }
        }

        private BlockSyntax GetImmediatelyContainingBlock()
        {
            return IsFirstBlockStatement()
                ? (BlockSyntax)_parentStatement.Parent
                : _parentStatement is BlockSyntax block && block.OpenBraceToken == _token
                    ? (BlockSyntax)_parentStatement
                    : null;
        }

        private bool IsFirstBlockStatement()
            => _parentStatement.Parent is BlockSyntax parentBlockOpt && parentBlockOpt.Statements.FirstOrDefault() == _parentStatement;

        private void AddCurrentDeclaration()
        {
            if (_parentStatement is LocalDeclarationStatementSyntax)
            {
                AddRelevantExpressions(_parentStatement, _expressions, includeDeclarations: true);
            }
        }

        private void AddMethodParameters()
        {
            // and we're the start of a method, then also add the parameters of that method to
            // the proximity expressions.
            var block = GetImmediatelyContainingBlock();

            if (block != null && block.Parent is MemberDeclarationSyntax memberDeclaration)
            {
                var parameterList = memberDeclaration.GetParameterList();
                AddParameters(parameterList);
            }
            else if (block is null
                && _parentStatement.Parent is GlobalStatementSyntax { Parent: CompilationUnitSyntax compilationUnit } globalStatement
                && compilationUnit.Members.FirstOrDefault() == globalStatement)
            {
                _expressions.Add("args");
            }
        }

        private void AddIndexerParameters()
        {
            var block = GetImmediatelyContainingBlock();

            // and we're the start of a method, then also add the parameters of that method to
            // the proximity expressions.
            if (block != null &&
                block.Parent is AccessorDeclarationSyntax &&
                block.Parent.Parent is AccessorListSyntax &&
                block.Parent.Parent.Parent is IndexerDeclarationSyntax indexerDeclaration)
            {
                var parameterList = indexerDeclaration.ParameterList;
                AddParameters(parameterList);
            }
        }

        private void AddParameters(BaseParameterListSyntax parameterList)
        {
            if (parameterList != null)
            {
                _expressions.AddRange(
                    from p in parameterList.Parameters
                    select p.Identifier.ValueText);
            }
        }

        private void AddFollowingRelevantExpressions(CancellationToken cancellationToken)
        {
            var line = _syntaxTree.GetText(cancellationToken).Lines.IndexOf(_position);

            // If there's are more statements following us on the same line, then add them as
            // well. 
            for (var nextStatement = _parentStatement.GetNextStatement();
                 nextStatement != null && _syntaxTree.GetText(cancellationToken).Lines.IndexOf(nextStatement.SpanStart) == line;
                 nextStatement = nextStatement.GetNextStatement())
            {
                AddRelevantExpressions(nextStatement, _expressions, includeDeclarations: false);
            }
        }

        private void AddPrecedingRelevantExpressions()
        {
            // If we're not the first statement in this block, 
            // and there's an expression or declaration statement directly above us,
            // then add the expressions from that as well.

            StatementSyntax previousStatement;

            if (_parentStatement is BlockSyntax block &&
                block.CloseBraceToken == _token)
            {
                // If we're at the last brace of a block, use the last
                // statement in the block.
                previousStatement = block.Statements.LastOrDefault();
            }
            else
            {
                previousStatement = _parentStatement.GetPreviousStatement();
            }

            if (previousStatement != null)
            {
                switch (previousStatement.Kind())
                {
                    case SyntaxKind.ExpressionStatement:
                    case SyntaxKind.LocalDeclarationStatement:
                        AddRelevantExpressions(previousStatement, _expressions, includeDeclarations: true);
                        break;
                    case SyntaxKind.DoStatement:
                        AddExpressionTerms((previousStatement as DoStatementSyntax).Condition, _expressions);
                        AddLastStatementOfConstruct(previousStatement);
                        break;
                    case SyntaxKind.ForStatement:
                    case SyntaxKind.ForEachStatement:
                    case SyntaxKind.ForEachVariableStatement:
                    case SyntaxKind.IfStatement:
                    case SyntaxKind.CheckedStatement:
                    case SyntaxKind.UncheckedStatement:
                    case SyntaxKind.WhileStatement:
                    case SyntaxKind.LockStatement:
                    case SyntaxKind.SwitchStatement:
                    case SyntaxKind.TryStatement:
                    case SyntaxKind.UsingStatement:
                        AddRelevantExpressions(previousStatement, _expressions, includeDeclarations: false);
                        AddLastStatementOfConstruct(previousStatement);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                // This is the first statement of the block. Go to the nearest enclosing statement and add its expressions
                var statementAncestor = _parentStatement.Ancestors().OfType<StatementSyntax>().FirstOrDefault(node => !node.IsKind(SyntaxKind.Block));
                if (statementAncestor != null)
                {
                    AddRelevantExpressions(statementAncestor, _expressions, includeDeclarations: true);
                }
            }
        }

        private void AddLastStatementOfConstruct(StatementSyntax statement)
        {
            if (statement == null)
            {
                return;
            }

            switch (statement.Kind())
            {
                case SyntaxKind.Block:
                    AddLastStatementOfConstruct((statement as BlockSyntax).Statements.LastOrDefault());
                    break;
                case SyntaxKind.BreakStatement:
                case SyntaxKind.ContinueStatement:
                    AddLastStatementOfConstruct(statement.GetPreviousStatement());
                    break;
                case SyntaxKind.CheckedStatement:
                case SyntaxKind.UncheckedStatement:
                    AddLastStatementOfConstruct((statement as CheckedStatementSyntax).Block);
                    break;
                case SyntaxKind.DoStatement:
                    AddLastStatementOfConstruct((statement as DoStatementSyntax).Statement);
                    break;
                case SyntaxKind.ForStatement:
                    AddLastStatementOfConstruct((statement as ForStatementSyntax).Statement);
                    break;
                case SyntaxKind.ForEachStatement:
                case SyntaxKind.ForEachVariableStatement:
                    AddLastStatementOfConstruct((statement as CommonForEachStatementSyntax).Statement);
                    break;
                case SyntaxKind.IfStatement:
                    var ifStatement = statement as IfStatementSyntax;
                    AddLastStatementOfConstruct(ifStatement.Statement);
                    if (ifStatement.Else != null)
                    {
                        AddLastStatementOfConstruct(ifStatement.Else.Statement);
                    }

                    break;
                case SyntaxKind.LockStatement:
                    AddLastStatementOfConstruct((statement as LockStatementSyntax).Statement);
                    break;
                case SyntaxKind.SwitchStatement:
                    var switchStatement = statement as SwitchStatementSyntax;
                    foreach (var section in switchStatement.Sections)
                    {
                        AddLastStatementOfConstruct(section.Statements.LastOrDefault());
                    }

                    break;
                case SyntaxKind.TryStatement:
                    var tryStatement = statement as TryStatementSyntax;
                    if (tryStatement.Finally != null)
                    {
                        AddLastStatementOfConstruct(tryStatement.Finally.Block);
                    }
                    else
                    {
                        AddLastStatementOfConstruct(tryStatement.Block);
                        foreach (var catchClause in tryStatement.Catches)
                        {
                            AddLastStatementOfConstruct(catchClause.Block);
                        }
                    }

                    break;
                case SyntaxKind.UsingStatement:
                    AddLastStatementOfConstruct((statement as UsingStatementSyntax).Statement);
                    break;
                case SyntaxKind.WhileStatement:
                    AddLastStatementOfConstruct((statement as WhileStatementSyntax).Statement);
                    break;
                default:
                    AddRelevantExpressions(statement, _expressions, includeDeclarations: false);
                    break;
            }
        }
    }
}
