using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Tags;
using Microsoft.CodeAnalysis.Text;
using RobotNet.WebApp.Scripts.Monaco.Editor;
using System.Collections.Immutable;
using System.Reflection;
using System.Text.RegularExpressions;

namespace RobotNet.WebApp.Scripts.Monaco.Languages;

public static class CompletionExtensions
{
    private static readonly Dictionary<string, CompletionItemKind> s_roslynTagToCompletionItemKind = new()
    {
        { WellKnownTags.Public, CompletionItemKind.Keyword },
        { WellKnownTags.Protected, CompletionItemKind.Keyword },
        { WellKnownTags.Private, CompletionItemKind.Keyword },
        { WellKnownTags.Internal, CompletionItemKind.Keyword },
        { WellKnownTags.File, CompletionItemKind.File },
        { WellKnownTags.Project, CompletionItemKind.File },
        { WellKnownTags.Folder, CompletionItemKind.Folder },
        { WellKnownTags.Assembly, CompletionItemKind.File },
        { WellKnownTags.Class, CompletionItemKind.Class },
        { WellKnownTags.Constant, CompletionItemKind.Constant },
        { WellKnownTags.Delegate, CompletionItemKind.Function },
        { WellKnownTags.Enum, CompletionItemKind.Enum },
        { WellKnownTags.EnumMember, CompletionItemKind.EnumMember },
        { WellKnownTags.Event, CompletionItemKind.Event },
        { WellKnownTags.ExtensionMethod, CompletionItemKind.Method },
        { WellKnownTags.Field, CompletionItemKind.Field },
        { WellKnownTags.Interface, CompletionItemKind.Interface },
        { WellKnownTags.Intrinsic, CompletionItemKind.Text },
        { WellKnownTags.Keyword, CompletionItemKind.Keyword },
        { WellKnownTags.Label, CompletionItemKind.Text },
        { WellKnownTags.Local, CompletionItemKind.Variable },
        { WellKnownTags.Namespace, CompletionItemKind.Module },
        { WellKnownTags.Method, CompletionItemKind.Method },
        { WellKnownTags.Module, CompletionItemKind.Module },
        { WellKnownTags.Operator, CompletionItemKind.Operator },
        { WellKnownTags.Parameter, CompletionItemKind.Value },
        { WellKnownTags.Property, CompletionItemKind.Property },
        { WellKnownTags.RangeVariable, CompletionItemKind.Variable },
        { WellKnownTags.Reference, CompletionItemKind.Reference },
        { WellKnownTags.Structure, CompletionItemKind.Struct },
        { WellKnownTags.TypeParameter, CompletionItemKind.TypeParameter },
        { WellKnownTags.Snippet, CompletionItemKind.Snippet },
        { WellKnownTags.Error, CompletionItemKind.Text },
        { WellKnownTags.Warning, CompletionItemKind.Text },
    };

    private static CompletionTrigger GetCompletionTrigger(int kind, char? triggerCharacter, bool includeTriggerCharacter)
            => kind switch
            {
                1 => CompletionTrigger.Invoke,
                // https://github.com/dotnet/roslyn/issues/42982: Passing a trigger character
                // to GetCompletionsAsync causes a null ref currently.
                2 when includeTriggerCharacter => CompletionTrigger.CreateInsertionTrigger((char)triggerCharacter!),
                _ => CompletionTrigger.Invoke,
            };

    //private static readonly Regex EscapeRegex = new(@"([\\\$}])", RegexOptions.Compiled);
    //private static string GetAdjustedInsertTextWithPosition(
    //        CompletionChange change,
    //        int originalPosition,
    //        int newOffset,
    //        string? prependText = null)
    //{
    //    string newText = change.TextChange.NewText!;
    //    if (change.NewPosition is not int newPosition
    //        || newPosition >= change.TextChange.Span.Start + newText.Length)
    //    {
    //        return prependText + newText[newOffset..];
    //    }
    //    int midpoint = newPosition - change.TextChange.Span.Start;

    //    var beforeText = prependText + newText[newOffset..midpoint];
    //    if (beforeText != null) beforeText = EscapeRegex.Replace(beforeText, @"\$1");

    //    var afterText = newText[midpoint..];
    //    if (afterText != null) afterText = EscapeRegex.Replace(afterText, @"\$1");

    //    return beforeText + "$0" + afterText;
    //}

    private static ImmutableArray<char> BuildCommitCharacters(Microsoft.CodeAnalysis.Completion.CompletionList completions, ImmutableArray<CharacterSetModificationRule> characterRules, ImmutableArray<char>.Builder triggerCharactersBuilder)
    {
        if (completions is null) return [];

        triggerCharactersBuilder.AddRange(completions.Rules.DefaultCommitCharacters);

        foreach (var modifiedRule in characterRules)
        {
            switch (modifiedRule.Kind)
            {
                case CharacterSetModificationKind.Add:
                    triggerCharactersBuilder.AddRange(modifiedRule.Characters);
                    break;

                case CharacterSetModificationKind.Remove:
                    for (int i = 0; i < triggerCharactersBuilder.Count; i++)
                    {
                        if (modifiedRule.Characters.Contains(triggerCharactersBuilder[i]))
                        {
                            triggerCharactersBuilder.RemoveAt(i);
                            i--;
                        }
                    }

                    break;

                case CharacterSetModificationKind.Replace:
                    triggerCharactersBuilder.Clear();
                    triggerCharactersBuilder.AddRange(modifiedRule.Characters);
                    break;
            }
        }

        if (completions.SuggestionModeItem is not null)
        {
            triggerCharactersBuilder.Remove(' ');
        }

        if (triggerCharactersBuilder.Capacity == triggerCharactersBuilder.Count)
        {
            return triggerCharactersBuilder.MoveToImmutable();
        }
        else
        {
            var result = triggerCharactersBuilder.ToImmutable();
            triggerCharactersBuilder.Clear();
            return result;
        }
    }

    private static CompletionItemKind GetCompletionItemKind(ImmutableArray<string> tags)
    {
        foreach (var tag in tags)
        {
            if (s_roslynTagToCompletionItemKind.TryGetValue(tag, out var itemKind))
            {
                return itemKind;
            }
        }

        return CompletionItemKind.Text;
    }

    public static async Task<IEnumerable<CompletionItem>> GetCompletionAsync(this AdhocWorkspace workspace, DocumentId documentId, int line, int column, int kind, char? triggerCharacter)
    {
        if (triggerCharacter == ' ') return [];

        var document = workspace.CurrentSolution.GetDocument(documentId);
        if (document is null) return [];

        var sourceText = await document.GetTextAsync();
        var position = sourceText.Lines.GetPosition(new LinePosition(line - 1, column - 1));
        var completionService = CompletionService.GetService(document);

        if (completionService == null) return [];

        if (kind == 3 && !completionService.ShouldTriggerCompletion(sourceText, position, GetCompletionTrigger(kind - 1, triggerCharacter, includeTriggerCharacter: true)))
        {
            return [];
        }

        Microsoft.CodeAnalysis.Completion.CompletionList? completionList = null;
        try
        {
            completionList = await completionService.GetCompletionsAsync(
                    document,
                    position,
                    GetCompletionTrigger(kind - 1, triggerCharacter, includeTriggerCharacter: false));
        }
        catch (Exception ex)
        {
            // Log the exception or handle it as needed
            Console.WriteLine($"Error getting completions: position:{position} \n {ex}");
        }

        if (completionList is null || completionList.ItemsList.Count <= 0) return [];

        var typedSpan = completionService.GetDefaultCompletionListSpan(sourceText, position);
        string typedText = sourceText.GetSubText(typedSpan).ToString();
        ImmutableArray<string> filteredItems = typedText != string.Empty
            ? [.. completionService.FilterItems(document, [.. completionList.ItemsList], typedText).Select(i => i.DisplayText)]
            : [];

        bool expectingImportedItems = workspace.Options.GetOption(new PerLanguageOption<bool?>("CompletionOptions", "ShowItemsFromUnimportedNamespaces", defaultValue: null), LanguageNames.CSharp) == true;
        var syntax = await document.GetSyntaxTreeAsync();
        var replacingSpanStartPosition = sourceText.Lines.GetLinePosition(typedSpan.Start);
        var replacingSpanEndPosition = sourceText.Lines.GetLinePosition(typedSpan.End);
        var triggerCharactersBuilder = ImmutableArray.CreateBuilder<char>(completionList.Rules.DefaultCommitCharacters.Length);
        var completionsBuilder = new List<CompletionItem>();

        foreach (var completion in completionList.ItemsList)
        {
            SingleEditOperation[]? additionalTextEdits = null;
            char sortTextPrepend = '0';

            if (!completion.Properties.TryGetValue("InsertionText", out string? insertText))
            {
                if (GetCompletionItemKind(completion.Tags) == CompletionItemKind.Keyword)
                {
                    insertText = completion.DisplayText;
                }
                else
                {
                    continue;
                }
            }

            if (string.IsNullOrEmpty(insertText)) continue;

            var commitCharacters = BuildCommitCharacters(completionList, completion.Rules.CommitCharacterRules, triggerCharactersBuilder).Select(c => c.ToString());

            completionsBuilder.Add(new CompletionItem
            {
                Label = completion.DisplayTextPrefix + completion.DisplayText + completion.DisplayTextSuffix,
                Kind = GetCompletionItemKind(completion.Tags),
                Documentation = "",
                InsertText = insertText,
                Range = new Range
                {
                    StartLineNumber = replacingSpanStartPosition.Line + 1,
                    EndLineNumber = replacingSpanEndPosition.Line + 1,
                    StartColumn = replacingSpanStartPosition.Character + 1,
                    EndColumn = replacingSpanEndPosition.Character + 1,
                },
                AdditionalTextEdits = additionalTextEdits,
                // Ensure that unimported items are sorted after things already imported.
                SortText = expectingImportedItems ? sortTextPrepend + completion.SortText : completion.SortText,
                FilterText = completion.FilterText,
                Detail = completion.InlineDescription,
                Preselect = completion.Rules.MatchPriority == MatchPriority.Preselect || filteredItems.Contains(completion.DisplayText),
                CommitCharacters = [.. commitCharacters],
                Tags = null,
                Command = null,
                InsertTextRules = null,
            });
        }

        return completionsBuilder;
    }
}
