﻿// 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.

using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.InlineRename;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;

namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename
{
    internal class InlineRenameAdornmentManager : IDisposable
    {
        private readonly IWpfTextView _textView;
        private readonly IGlobalOptionService _globalOptionService;
        private readonly IWpfThemeService? _themeService;
        private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker;
        private readonly IAsynchronousOperationListenerProvider _listenerProvider;
        private readonly InlineRenameService _renameService;
        private readonly IEditorFormatMapService _editorFormatMapService;
        private readonly IInlineRenameColorUpdater? _dashboardColorUpdater;

        private readonly IAdornmentLayer _adornmentLayer;

        private static readonly ConditionalWeakTable<InlineRenameSession, object> s_createdViewModels =
            new ConditionalWeakTable<InlineRenameSession, object>();

        public InlineRenameAdornmentManager(
            InlineRenameService renameService,
            IEditorFormatMapService editorFormatMapService,
            IInlineRenameColorUpdater? dashboardColorUpdater,
            IWpfTextView textView,
            IGlobalOptionService globalOptionService,
            IWpfThemeService? themeService,
            IAsyncQuickInfoBroker asyncQuickInfoBroker,
            IAsynchronousOperationListenerProvider listenerProvider)
        {
            _renameService = renameService;
            _editorFormatMapService = editorFormatMapService;
            _dashboardColorUpdater = dashboardColorUpdater;
            _textView = textView;
            _globalOptionService = globalOptionService;
            _themeService = themeService;
            _asyncQuickInfoBroker = asyncQuickInfoBroker;
            _listenerProvider = listenerProvider;
            _adornmentLayer = textView.GetAdornmentLayer(InlineRenameAdornmentProvider.AdornmentLayerName);

            _renameService.ActiveSessionChanged += OnActiveSessionChanged;
            _textView.Closed += OnTextViewClosed;

            UpdateAdornments();
        }

        public void Dispose()
        {
            _renameService.ActiveSessionChanged -= OnActiveSessionChanged;
            _textView.Closed -= OnTextViewClosed;
        }

        private void OnTextViewClosed(object sender, EventArgs e)
            => Dispose();

        private void OnActiveSessionChanged(object sender, EventArgs e)
            => UpdateAdornments();

        private void UpdateAdornments()
        {
            _adornmentLayer.RemoveAllAdornments();

            if (_renameService.ActiveSession != null &&
                ViewIncludesBufferFromWorkspace(_textView, _renameService.ActiveSession.Workspace))
            {
                _dashboardColorUpdater?.UpdateColors();

                var adornment = GetAdornment();

                if (adornment is null)
                {
                    return;
                }

                _themeService?.ApplyThemeToElement(adornment);

                _adornmentLayer.AddAdornment(
                    AdornmentPositioningBehavior.ViewportRelative,
                    null, // Set no visual span because we don't want the editor to automatically remove the adornment if the overlapping span changes
                    tag: null,
                    adornment,
                    (tag, adornment) => ((InlineRenameAdornment)adornment).Dispose());
            }
        }

        private InlineRenameAdornment? GetAdornment()
        {
            if (_renameService.ActiveSession is null)
            {
                return null;
            }

            var useInlineAdornment = _globalOptionService.GetOption(InlineRenameUIOptionsStorage.UseInlineAdornment);
            if (useInlineAdornment)
            {
                if (!_textView.HasAggregateFocus)
                {
                    // For the rename flyout, the adornment is dismissed on focus lost. There's
                    // no need to keep an adornment on every textview for show/hide behaviors
                    return null;
                }

                // Get the active selection to make sure the rename text is selected in the same way
                var originalSpan = _renameService.ActiveSession.TriggerSpan;
                var selectionSpan = _textView.Selection.SelectedSpans.First();

                var start = selectionSpan.IsEmpty
                    ? 0
                    : selectionSpan.Start - originalSpan.Start; // The length from the identifier to the start of selection

                var length = selectionSpan.IsEmpty
                    ? originalSpan.Length
                    : selectionSpan.Length;

                var identifierSelection = new TextSpan(start, length);

                var adornment = new RenameFlyout(
                    (RenameFlyoutViewModel)s_createdViewModels.GetValue(_renameService.ActiveSession, session => new RenameFlyoutViewModel(session, identifierSelection, registerOleComponent: true, _globalOptionService)),
                    _textView,
                    _themeService,
                    _asyncQuickInfoBroker,
                    _listenerProvider);

                return adornment;
            }
            else
            {
                var newAdornment = new RenameDashboard(
                    (RenameDashboardViewModel)s_createdViewModels.GetValue(_renameService.ActiveSession, session => new RenameDashboardViewModel(session)),
                    _editorFormatMapService,
                    _textView);

                return newAdornment;
            }
        }

        private static bool ViewIncludesBufferFromWorkspace(IWpfTextView textView, Workspace workspace)
        {
            return textView.BufferGraph.GetTextBuffers(b => GetWorkspace(b.AsTextContainer()) == workspace)
                                       .Any();
        }

        private static Workspace? GetWorkspace(SourceTextContainer textContainer)
        {
            Workspace.TryGetWorkspace(textContainer, out var workspace);
            return workspace;
        }
    }
}
