﻿// 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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.UnitTests.Classification;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.QuickInfo
{
    [UseExportProvider]
    public abstract class AbstractSemanticQuickInfoSourceTests
    {
        protected static FormattedClassification Text(string text)
            => FormattedClassifications.Text(text);

        protected static string Lines(params string[] lines)
            => string.Join("\r\n", lines);

        protected static FormattedClassification[] ExpectedClassifications(
            params FormattedClassification[] expectedClassifications)
        {
            return expectedClassifications;
        }

        protected static Tuple<string, string>[] NoClassifications()
            => null;

        internal static Action<QuickInfoItem> SymbolGlyph(Glyph expectedGlyph)
        {
            return qi =>
            {
                Assert.Contains(expectedGlyph, qi.Tags.GetGlyphs());
            };
        }

        internal static Action<QuickInfoItem> WarningGlyph(Glyph expectedGlyph)
            => SymbolGlyph(expectedGlyph);

        internal static void AssertSection(
            string expectedText,
            ImmutableArray<QuickInfoSection> sections,
            string textBlockKind,
            FormattedClassification[] expectedClassifications = null)
        {
            var textBlock = sections.FirstOrDefault(tb => tb.Kind == textBlockKind);
            var text = textBlock != null ? textBlock.TaggedParts : ImmutableArray<TaggedText>.Empty;
            AssertTaggedText(expectedText, text, expectedClassifications);
        }

        protected static void AssertTaggedText(
            string expectedText,
            ImmutableArray<TaggedText> taggedText,
#pragma warning disable IDE0060 // Remove unused parameter - https://github.com/dotnet/roslyn/issues/45893
            FormattedClassification[] expectedClassifications = null)
#pragma warning restore IDE0060 // Remove unused parameter
        {
            var actualText = string.Concat(taggedText.Select(tt => tt.Text));
            Assert.Equal(expectedText, actualText);
        }

        protected static Action<QuickInfoItem> MainDescription(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.Description, expectedClassifications);
        }

        protected static Action<QuickInfoItem> Documentation(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.DocumentationComments, expectedClassifications);
        }

        protected static Action<QuickInfoItem> Remarks(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.RemarksDocumentationComments, expectedClassifications);
        }

        protected static Action<QuickInfoItem> Returns(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.ReturnsDocumentationComments, expectedClassifications);
        }

        protected static Action<QuickInfoItem> Value(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.ValueDocumentationComments, expectedClassifications);
        }

        protected static Action<QuickInfoItem> TypeParameterMap(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.TypeParameters, expectedClassifications);
        }

        protected static Action<QuickInfoItem> AnonymousTypes(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.AnonymousTypes, expectedClassifications);
        }

        protected static Action<QuickInfoItem> NullabilityAnalysis(
            string expectedText,
            FormattedClassification[] expectedClassifications = null)
        {
            return item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.NullabilityAnalysis, expectedClassifications);
        }

        protected static Action<QuickInfoItem> NoTypeParameterMap
            => item => AssertSection(string.Empty, item.Sections, QuickInfoSectionKinds.TypeParameters);

        protected static Action<QuickInfoItem> Usage(string expectedText, bool expectsWarningGlyph = false)
        {
            return item =>
            {
                AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.Usage);

                if (expectsWarningGlyph)
                {
                    WarningGlyph(Glyph.CompletionWarning)(item);
                }
                else
                {
                    Assert.DoesNotContain(Glyph.CompletionWarning, item.Tags.GetGlyphs());
                }
            };
        }

        protected static Action<QuickInfoItem> Exceptions(string expectedText)
            => item => AssertSection(expectedText, item.Sections, QuickInfoSectionKinds.Exception);

        protected static Action<QuickInfoItem> Captures(string capturesText)
            => item => AssertSection(capturesText, item.Sections, QuickInfoSectionKinds.Captures);

        protected static async Task<bool> CanUseSpeculativeSemanticModelAsync(Document document, int position)
        {
            var service = document.GetLanguageService<ISyntaxFactsService>();
            var node = (await document.GetSyntaxRootAsync()).FindToken(position).Parent;

            return !service.GetMemberBodySpanForSpeculativeBinding(node).IsEmpty;
        }

        protected abstract Task TestAsync(string markup, params Action<QuickInfoItem>[] expectedResults);
    }
}
