﻿// 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.Threading;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Utilities;
using IVsLanguageBlock = Microsoft.VisualStudio.TextManager.Interop.IVsLanguageBlock;
using IVsTextLines = Microsoft.VisualStudio.TextManager.Interop.IVsTextLines;
using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
{
    internal abstract partial class AbstractLanguageService<TPackage, TLanguageService>
        : IVsLanguageBlock
    {
        public int GetCurrentBlock(
            IVsTextLines pTextLines,
            int iCurrentLine,
            int iCurrentChar,
            VsTextSpan[] ptsBlockSpan,
            out string pbstrDescription,
            out int pfBlockAvailable)
        {
            var snapshot = this.EditorAdaptersFactoryService.GetDataBuffer(pTextLines).CurrentSnapshot;
            var position = snapshot?.TryGetPosition(iCurrentLine, iCurrentChar);
            if (position == null)
            {
                pbstrDescription = null;
                pfBlockAvailable = 0;
                return VSConstants.S_OK;
            }

            (string description, TextSpan span)? foundBlock = null;

            var uiThreadOperationExecutor = this.Package.ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                ServicesVSResources.Current_block,
                ServicesVSResources.Determining_current_block,
                allowCancellation: true,
                showProgress: false,
                action: context =>
                {
                    foundBlock = VsLanguageBlock.GetCurrentBlock(snapshot, position.Value, context.UserCancellationToken);
                });

            pfBlockAvailable = foundBlock != null ? 1 : 0;
            pbstrDescription = foundBlock?.description;

            if (foundBlock != null && ptsBlockSpan != null && ptsBlockSpan.Length >= 1)
            {
                ptsBlockSpan[0] = foundBlock.Value.span.ToSnapshotSpan(snapshot).ToVsTextSpan();
            }

            return VSConstants.S_OK;
        }
    }

    internal static class VsLanguageBlock
    {
        public static (string description, TextSpan span)? GetCurrentBlock(
            ITextSnapshot snapshot,
            int position,
            CancellationToken cancellationToken)
        {
            var document = snapshot.GetOpenDocumentInCurrentContextWithChanges();
            if (document == null || !document.SupportsSyntaxTree)
            {
                return null;
            }

            var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
            var syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken);
            var node = syntaxFactsService.GetContainingMemberDeclaration(syntaxRoot, position, useFullSpan: false);
            if (node == null)
            {
                return null;
            }

            var description = syntaxFactsService.GetDisplayName(node,
                DisplayNameOptions.IncludeMemberKeyword |
                DisplayNameOptions.IncludeParameters |
                DisplayNameOptions.IncludeType |
                DisplayNameOptions.IncludeTypeParameters);

            return (description, node.Span);
        }
    }
}
