﻿// 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;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.IntegrationTest.Utilities;
using Microsoft.VisualStudio.IntegrationTest.Utilities.Input;
using Roslyn.Test.Utilities;
using Xunit;
using Xunit.Abstractions;

namespace Roslyn.VisualStudio.IntegrationTests.CSharp
{
    [Collection(nameof(SharedIntegrationHostFixture))]
    [Trait(Traits.Feature, Traits.Features.Completion)]
    public class CSharpIntelliSense : AbstractEditorTest
    {
        protected override string LanguageName => LanguageNames.CSharp;

        public CSharpIntelliSense(VisualStudioInstanceFactory instanceFactory)
            : base(instanceFactory, nameof(CSharpIntelliSense))
        {
        }

        public override async Task InitializeAsync()
        {
            await base.InitializeAsync().ConfigureAwait(true);

            // Disable import completion.
            VisualStudio.Workspace.SetImportCompletionOption(false);
        }

        [WpfTheory, CombinatorialData]
        public void AtNamespaceLevel(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"$$");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys("usi");
            VisualStudio.Editor.Verify.CompletionItemsExist("using");

            VisualStudio.Editor.SendKeys(VirtualKey.Tab);
            VisualStudio.Editor.Verify.CurrentLineText("using$$", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void SpeculativeTInList(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class C
{
    $$
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys("pub");
            VisualStudio.Editor.Verify.CompletionItemsExist("public");

            VisualStudio.Editor.SendKeys(' ');
            VisualStudio.Editor.Verify.CurrentLineText("public $$", assertCaretPosition: true);

            VisualStudio.Editor.SendKeys('t');
            VisualStudio.Editor.Verify.CompletionItemsExist("T");

            VisualStudio.Editor.SendKeys(' ');
            VisualStudio.Editor.SendKeys("Goo<T>() { }");
            VisualStudio.Editor.Verify.TextContains(@"
class C
{
    public T Goo<T>() { }$$
}",
assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void VerifyCompletionListMembersOnStaticTypesAndCompleteThem(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
public class Program
{
    static void Main(string[] args)
    {
        NavigateTo$$
    }
}

public static class NavigateTo
{
    public static void Search(string s){ }
    public static void Navigate(int i){ }
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys('.');
            VisualStudio.Editor.Verify.CompletionItemsExist("Search", "Navigate");

            VisualStudio.Editor.SendKeys('S', VirtualKey.Tab);
            VisualStudio.Editor.Verify.CurrentLineText("NavigateTo.Search$$", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void CtrlAltSpace(bool showCompletionInArgumentLists)
        {
            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            // Note: the completion needs to be unambiguous for the test to be deterministic.
            // Otherwise the result might depend on the state of MRU list.

            VisualStudio.Editor.SendKeys("names");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" Goo", VirtualKey.Enter);
            VisualStudio.Editor.SendKeys('{', VirtualKey.Enter, '}', VirtualKey.Up, VirtualKey.Enter);

            VisualStudio.Editor.SendKeys("pu");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" cla");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" Program", VirtualKey.Enter);
            VisualStudio.Editor.SendKeys('{', VirtualKey.Enter, '}', VirtualKey.Up, VirtualKey.Enter);

            VisualStudio.Editor.SendKeys("pub");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" stati");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" voi");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" Main(string[] args)", VirtualKey.Enter);
            VisualStudio.Editor.SendKeys('{', VirtualKey.Enter, '}', VirtualKey.Up, VirtualKey.Enter);

            VisualStudio.Editor.SendKeys("System.Console.");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys("writeline();");
            VisualStudio.Editor.Verify.CurrentLineText("System.Console.WriteLine();$$", assertCaretPosition: true);

            VisualStudio.Editor.SendKeys(VirtualKey.Home, Shift(VirtualKey.End), VirtualKey.Delete);
            VisualStudio.Editor.SendKeys(new KeyPress(VirtualKey.Space, ShiftState.Ctrl | ShiftState.Alt));

            VisualStudio.Editor.SendKeys("System.Console.");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys("writeline();");
            VisualStudio.Editor.Verify.CurrentLineText("System.Console.writeline();$$", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void CtrlAltSpaceOption(bool showCompletionInArgumentLists)
        {
            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            VisualStudio.Editor.SendKeys("names");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" Goo");
            VisualStudio.Editor.Verify.CurrentLineText("namespace Goo$$", assertCaretPosition: true);

            ClearEditor();
            VisualStudio.Editor.SetUseSuggestionMode(true);

            VisualStudio.Editor.SendKeys("nam");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(" Goo");
            VisualStudio.Editor.Verify.CurrentLineText("nam Goo$$", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void CtrlSpace(bool showCompletionInArgumentLists)
        {
            SetUpEditor("class c { void M() {$$ } }");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys(Ctrl(VirtualKey.Space));
            VisualStudio.Editor.Verify.CompletionItemsExist("System");
        }

        [WpfTheory, CombinatorialData]
        public void NavigatingWithDownKey(bool showCompletionInArgumentLists)
        {
            SetUpEditor("class c { void M() {$$ } }");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys('c');
            VisualStudio.Editor.Verify.CurrentCompletionItem("c");
            VisualStudio.Editor.Verify.CompletionItemsExist("c");

            VisualStudio.Editor.SendKeys(VirtualKey.Down);
            VisualStudio.Editor.Verify.CurrentCompletionItem("char");
            VisualStudio.Editor.Verify.CompletionItemsExist("char");
        }

        [WpfTheory, CombinatorialData]
        public void XmlDocCommentIntelliSense(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    ///$$
    void Main(string[] args)
    {
    
    }
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys("<s");
            VisualStudio.Editor.Verify.CompletionItemsExist("see", "seealso", "summary");

            // 🐛 Workaround for https://github.com/dotnet/roslyn/issues/33824
            var completionItems = VisualStudio.Editor.GetCompletionItems();
            var targetIndex = Array.IndexOf(completionItems, "see");
            var currentIndex = Array.IndexOf(completionItems, VisualStudio.Editor.GetCurrentCompletionItem());
            if (currentIndex != targetIndex)
            {
                var key = currentIndex < targetIndex ? VirtualKey.Down : VirtualKey.Up;
                var keys = Enumerable.Repeat(key, Math.Abs(currentIndex - targetIndex)).Cast<object>().ToArray();
                VisualStudio.Editor.SendKeys(keys);
            }

            VisualStudio.Editor.SendKeys(VirtualKey.Enter);
            VisualStudio.Editor.Verify.CurrentLineText("///<see cref=\"$$\"/>", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void XmlTagCompletion(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
/// $$
class C { }
");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys("<summary>");
            VisualStudio.Editor.Verify.CurrentLineText("/// <summary>$$</summary>", assertCaretPosition: true);

            SetUpEditor(@"
/// <summary>$$
class C { }
");

            VisualStudio.Editor.SendKeys("</");
            VisualStudio.Editor.Verify.CurrentLineText("/// <summary></summary>$$", assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void SignatureHelpShowsUp(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    void Main(string[] args)
    {
        $$
    }
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            VisualStudio.Editor.SendKeys("Mai");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys("(");

            VisualStudio.Editor.Verify.CurrentSignature("void Class1.Main(string[] args)");
            VisualStudio.Editor.Verify.CurrentParameter("args", "");
        }

        [WpfTheory, CombinatorialData]
        [WorkItem("https://github.com/dotnet/roslyn/issues/33825")]
        public void CompletionUsesTrackingPointsInTheFaceOfAutomaticBraceCompletion(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    void Main(string[] args)
    $$
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            VisualStudio.Editor.SendKeys(
                '{',
                VirtualKey.Enter,
                "                 ");

            VisualStudio.Editor.InvokeCompletionList();

            VisualStudio.Editor.SendKeys('}');

            VisualStudio.Editor.Verify.TextContains(@"
class Class1
{
    void Main(string[] args)
    {
    }$$
}",
assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        [WorkItem("https://github.com/dotnet/roslyn/issues/33823")]
        public void CommitOnShiftEnter(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    void Main(string[] args)
    {
        $$
    }
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            VisualStudio.Editor.SendKeys(
                'M',
                Shift(VirtualKey.Enter));

            VisualStudio.Editor.Verify.TextContains(@"
class Class1
{
    void Main(string[] args)
    {
        Main
$$
    }
}",
assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        public void LineBreakOnShiftEnter(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    void Main(string[] args)
    {
        $$
    }
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(true);

            VisualStudio.Editor.SendKeys(
                'M',
                Shift(VirtualKey.Enter));

            VisualStudio.Editor.Verify.TextContains(@"
class Class1
{
    void Main(string[] args)
    {
        Main
$$
    }
}",
assertCaretPosition: true);

        }

        [WpfTheory, CombinatorialData]
        public void CommitOnLeftCurly(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"
class Class1
{
    $$
}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SetUseSuggestionMode(false);

            VisualStudio.Editor.SendKeys("int P { g");
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys("{");

            VisualStudio.Editor.Verify.TextContains(@"
class Class1
{
    int P { get { $$} }
}",
assertCaretPosition: true);
        }

        [WpfTheory, CombinatorialData]
        [WorkItem("https://github.com/dotnet/roslyn/issues/33822")]
        public void EnsureTheCaretIsVisibleAfterALongEdit(bool showCompletionInArgumentLists)
        {
            var visibleColumns = VisualStudio.Editor.GetVisibleColumnCount();
            var variableName = new string('a', (int)(0.75 * visibleColumns));
            SetUpEditor($@"
public class Program
{{
    static void Main(string[] args)
    {{
        var {variableName} = 0;
        {variableName} = $$
    }}
}}");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            Assert.True(variableName.Length > 0);
            VisualStudio.Editor.SendKeys(
                VirtualKey.Delete,
                "aaa",
                VirtualKey.Tab);
            var actualText = VisualStudio.Editor.GetText();
            Assert.Contains($"{variableName} = {variableName}", actualText);
            Assert.True(VisualStudio.Editor.IsCaretOnScreen());
            Assert.True(VisualStudio.Editor.GetCaretColumn() > visibleColumns, "This test is inconclusive if the view didn't need to move to keep the caret on screen.");
        }

        [WpfTheory, CombinatorialData]
        public void DismissOnSelect(bool showCompletionInArgumentLists)
        {
            SetUpEditor(@"$$");

            VisualStudio.Workspace.SetTriggerCompletionInArgumentLists(showCompletionInArgumentLists);

            VisualStudio.Editor.SendKeys(Ctrl(VirtualKey.Space));
            Assert.True(VisualStudio.Editor.IsCompletionActive());

            VisualStudio.Editor.SendKeys(Ctrl(VirtualKey.A));
            Assert.False(VisualStudio.Editor.IsCompletionActive());
        }
    }
}
