﻿// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;

// Import Roslyn.Utilities with an alias to avoid conflicts with AsyncLazy<T>. This implementation relies on
// AsyncLazy<T> from vs-threading, and not the one from Roslyn.
using RoslynUtilities = Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.UnitTests;

[Export(typeof(IVsService<,>))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[PartNotDiscoverable]
internal class StubVsServiceExporter<TService, TInterface> : IVsService<TService, TInterface>
    where TService : class
    where TInterface : class
{
    private readonly AsyncLazy<TInterface> _serviceGetter;

    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public StubVsServiceExporter(
        [Import(typeof(SAsyncServiceProvider))] IAsyncServiceProvider2 asyncServiceProvider,
        JoinableTaskContext joinableTaskContext)
    {
        _serviceGetter = new AsyncLazy<TInterface>(() => asyncServiceProvider.GetServiceAsync<TService, TInterface>(joinableTaskContext.Factory, throwOnFailure: true)!, joinableTaskContext.Factory);
    }

    /// <inheritdoc />
    public Task<TInterface> GetValueAsync(CancellationToken cancellationToken)
        => _serviceGetter.GetValueAsync(cancellationToken);

    /// <inheritdoc />
    public Task<TInterface?> GetValueOrNullAsync(CancellationToken cancellationToken)
    {
        var value = GetValueAsync(cancellationToken);
        if (value.IsCompleted)
        {
            return TransformResult(value);
        }

        return value.ContinueWith(
            static t => TransformResult(t),
            CancellationToken.None, // token is already passed to antecedent, and this is a tiny sync continuation, so no need to make it also cancelable.
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default).Unwrap();

        static Task<TInterface?> TransformResult(Task<TInterface> task)
        {
            Debug.Assert(task.IsCompleted);
            if (task.Status == TaskStatus.Faulted)
            {
                // Our caller never wants exceptions, so return a cached null value
                return RoslynUtilities::SpecializedTasks.Null<TInterface>();
            }
            else
            {
                // Whether this is cancelled or ran to completion, we return the value as-is
                return RoslynUtilities::SpecializedTasks.AsNullable(task);
            }
        }
    }
}
