using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using RobotNet.WebApp.Scripts.Monaco.Documentation;

namespace RobotNet.WebApp.Scripts.Monaco.Languages;

public static class SignatureHelpExtensions
{
    public static async Task<SignatureHelpResult?> GetSignatureHelpAsync(this Document document, int line, int column)
    {
        var invocation = await GetInvocation(document, line - 1, column - 1);
        if (invocation is null) return null;

        var response = new SignatureHelp();
        foreach (var comma in invocation.Separators)
        {
            if (comma.Span.Start > invocation.Position)
            {
                break;
            }
            response.ActiveParameter += 1;
        }

        var signaturesSet = new HashSet<SignatureInformation>();
        var bestScore = int.MinValue;
        SignatureInformation? bestScoredItem = null;

        var types = invocation.ArgumentTypes;
        ISymbol? throughSymbol = null;
        ISymbol? throughType = null;
        var methodGroup = invocation.SemanticModel.GetMemberGroup(invocation.Receiver).OfType<IMethodSymbol>();
        if (invocation.Receiver is MemberAccessExpressionSyntax syntax)
        {
            var throughExpression = syntax.Expression;
            throughSymbol = invocation.SemanticModel.GetSpeculativeSymbolInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsExpression).Symbol;
            throughType = invocation.SemanticModel.GetSpeculativeTypeInfo(invocation.Position, throughExpression, SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
            var includeInstance = throughSymbol != null && throughSymbol is not ITypeSymbol ||
                throughExpression is LiteralExpressionSyntax ||
                throughExpression is TypeOfExpressionSyntax;
            var includeStatic = throughSymbol is INamedTypeSymbol || throughType != null;
            methodGroup = methodGroup.Where(m => m.IsStatic && includeStatic || !m.IsStatic && includeInstance);
        }
        else if (invocation.Receiver is SimpleNameSyntax && invocation.IsInStaticContext)
        {
            methodGroup = methodGroup.Where(m => m.IsStatic || m.MethodKind == MethodKind.LocalFunction);
        }

        foreach (var methodOverload in methodGroup)
        {
            var signature = BuildSignature(methodOverload);
            signaturesSet.Add(signature);

            var score = InvocationScore(methodOverload, types);
            if (score > bestScore)
            {
                bestScore = score;
                bestScoredItem = signature;
            }
        }

        var signaturesList = signaturesSet.ToList();
        response.Signatures = [.. signaturesList];
        if(bestScoredItem == null)
        {
            response.ActiveSignature = -1;
        }
        else
        {
            response.ActiveSignature = signaturesList.IndexOf((SignatureInformation)bestScoredItem);
        }

        return new SignatureHelpResult()
        {
            Value = response,
        };
    }

    private static async Task<InvocationContext?> GetInvocation(Document document, int line, int column)
    {
        var sourceText = await document.GetTextAsync();
        var position = sourceText.Lines.GetPosition(new LinePosition(line, column));
        var tree = await document.GetSyntaxTreeAsync();

        if (tree is null) return null;

        var root = await tree.GetRootAsync();
        var node = root.FindToken(position).Parent;

        // Walk up until we find a node that we're interested in.
        while (node != null)
        {
            if (node is InvocationExpressionSyntax invocation && invocation.ArgumentList.Span.Contains(position))
            {
                var semanticModel = await document.GetSemanticModelAsync();
                return semanticModel is null ? null : new InvocationContext(semanticModel, position, invocation.Expression, invocation.ArgumentList, invocation.IsInStaticContext());
            }

            if (node is BaseObjectCreationExpressionSyntax objectCreation && (objectCreation.ArgumentList?.Span.Contains(position) ?? false))
            {
                var semanticModel = await document.GetSemanticModelAsync();
                return semanticModel is null ? null : new InvocationContext(semanticModel, position, objectCreation, objectCreation.ArgumentList, objectCreation.IsInStaticContext());
            }

            if (node is AttributeSyntax attributeSyntax && (attributeSyntax.ArgumentList?.Span.Contains(position) ?? false))
            {
                var semanticModel = await document.GetSemanticModelAsync();
                return semanticModel is null ? null : new InvocationContext(semanticModel, position, attributeSyntax, attributeSyntax.ArgumentList, attributeSyntax.IsInStaticContext());
            }

            node = node.Parent;
        }

        return null;
    }

    private static int InvocationScore(IMethodSymbol symbol, IEnumerable<TypeInfo> types)
    {
        var parameters = symbol.Parameters;
        if (parameters.Length < types.Count())
        {
            return int.MinValue;
        }

        var score = 0;
        var invocationEnum = types.GetEnumerator();
        var definitionEnum = parameters.GetEnumerator();
        while (invocationEnum.MoveNext() && definitionEnum.MoveNext())
        {
            if (invocationEnum.Current.ConvertedType == null)
            {
                // 1 point for having a parameter
                score += 1;
            }
            else if (SymbolEqualityComparer.Default.Equals(invocationEnum.Current.ConvertedType, definitionEnum.Current.Type))
            {
                // 2 points for having a parameter and being
                // the same type
                score += 2;
            }
        }

        return score;
    }

    private static SignatureInformation BuildSignature(IMethodSymbol symbol)
    {
        var StructuredDocumentation = DocumentationConverter.GetStructuredDocumentation(symbol);

        return new SignatureInformation
        {
            Documentation = new MarkdownString()
            {
                Value = StructuredDocumentation?.SummaryText ?? "",
            },
            Label = symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
            Parameters = [..symbol.Parameters.Select(parameter => new ParameterInformation()
            {
                Label = parameter.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
                Documentation = new MarkdownString()
                {
                    Value = StructuredDocumentation?.GetParameterText(parameter.Name) ?? string.Empty,
                },
            })],
            ActiveParameter = null,
        };
    }

}
