// 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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.Language.Intellisense;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Peek;

internal sealed class DefinitionPeekableItem : PeekableItem
{
    private readonly Workspace _workspace;
    private readonly ProjectId _projectId;
    private readonly SymbolKey _symbolKey;
    private readonly IMetadataAsSourceFileService _metadataAsSourceFileService;
    private readonly IGlobalOptionService _globalOptions;
    private readonly IThreadingContext _threadingContext;

    public DefinitionPeekableItem(
        Workspace workspace, ProjectId projectId, SymbolKey symbolKey,
        IPeekResultFactory peekResultFactory,
        IMetadataAsSourceFileService metadataAsSourceService,
        IGlobalOptionService globalOptions,
        IThreadingContext threadingContext)
        : base(peekResultFactory)
    {
        _workspace = workspace;
        _projectId = projectId;
        _symbolKey = symbolKey;
        _metadataAsSourceFileService = metadataAsSourceService;
        _globalOptions = globalOptions;
        _threadingContext = threadingContext;
    }

    public override IEnumerable<IPeekRelationship> Relationships
        => [PredefinedPeekRelationships.Definitions];

    public override IPeekResultSource GetOrCreateResultSource(string relationshipName)
        => new ResultSource(this);

    private sealed class ResultSource : IPeekResultSource
    {
        private readonly DefinitionPeekableItem _peekableItem;

        public ResultSource(DefinitionPeekableItem peekableItem)
            => _peekableItem = peekableItem;

        public void FindResults(string relationshipName, IPeekResultCollection resultCollection, CancellationToken cancellationToken, IFindPeekResultsCallback callback)
        {
            if (relationshipName != PredefinedPeekRelationships.Definitions.Name)
                return;

            // Note: this is called on a background thread, but we must block the thread since the API doesn't support proper asynchrony.
            var success = _peekableItem._threadingContext.JoinableTaskFactory.Run(async () => await FindResultsAsync(
                resultCollection, callback, cancellationToken).ConfigureAwait(false));
            if (!success)
                callback.ReportFailure(new Exception(EditorFeaturesResources.No_information_found));
        }

        private async Task<bool> FindResultsAsync(IPeekResultCollection resultCollection, IFindPeekResultsCallback callback, CancellationToken cancellationToken)
        {
            var workspace = _peekableItem._workspace;
            var solution = workspace.CurrentSolution;
            var project = solution.GetProject(_peekableItem._projectId);
            if (project is null)
                return false;

            var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
            if (compilation is null)
                return false;

            var symbol = _peekableItem._symbolKey.Resolve(compilation, ignoreAssemblyKey: true, cancellationToken: cancellationToken).Symbol;
            if (symbol == null)
                return false;

            var sourceLocations = symbol.Locations.Where(l => l.IsInSource).ToList();

            if (sourceLocations.Count == 0)
            {
                // It's a symbol from metadata, so we want to go produce it from metadata
                var options = _peekableItem._globalOptions.GetMetadataAsSourceOptions();
                var declarationFile = await _peekableItem._metadataAsSourceFileService.GetGeneratedFileAsync(workspace, project, symbol, signaturesOnly: false, options: options, cancellationToken: cancellationToken).ConfigureAwait(false);
                var peekDisplayInfo = new PeekResultDisplayInfo(declarationFile.DocumentTitle, declarationFile.DocumentTooltip, declarationFile.DocumentTitle, declarationFile.DocumentTooltip);
                var identifierSpan = declarationFile.IdentifierLocation.GetLineSpan().Span;
                var entityOfInterestSpan = PeekHelpers.GetEntityOfInterestSpan(symbol, workspace, declarationFile.IdentifierLocation, cancellationToken);
                resultCollection.Add(PeekHelpers.CreateDocumentPeekResult(declarationFile.FilePath, identifierSpan, entityOfInterestSpan, peekDisplayInfo, _peekableItem.PeekResultFactory, isReadOnly: true));
            }

            var processedSourceLocations = 0;
            foreach (var declaration in sourceLocations)
            {
                var declarationLocation = declaration.GetMappedLineSpan();

                var entityOfInterestSpan = PeekHelpers.GetEntityOfInterestSpan(symbol, workspace, declaration, cancellationToken);
                resultCollection.Add(PeekHelpers.CreateDocumentPeekResult(declarationLocation.Path, declarationLocation.Span, entityOfInterestSpan, _peekableItem.PeekResultFactory));
                callback.ReportProgress(100 * ++processedSourceLocations / sourceLocations.Count);
            }

            return true;
        }
    }
}
