﻿// 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.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.AddPackage;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Packaging;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.AddImport
{
    internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
    {
        /// <summary>
        /// This is the top level 'Install Nuget Package' code action we show in 
        /// the lightbulb.  It will have children to 'Install Latest', 
        /// 'Install Version 'X' ..., and 'Install with package manager'.
        /// </summary>
        private class ParentInstallPackageCodeAction : CodeAction.CodeActionWithNestedActions
        {
            /// <summary>
            /// This code action only works by installing a package.  As such, it requires a non document change (and is
            /// thus restricted in which hosts it can run).
            /// </summary>
            public override ImmutableArray<string> Tags => RequiresNonDocumentChangeTags;

            /// <summary>
            /// Even though we have child actions, we mark ourselves as explicitly non-inlinable.
            /// We want to the experience of having the top level item the user has to see and
            /// navigate through, and we don't want our child items confusingly being added to the
            /// top level light-bulb where it's not clear what effect they would have if invoked.
            /// </summary>
            public ParentInstallPackageCodeAction(
                Document document,
                AddImportFixData fixData,
                IPackageInstallerService installerService)
                : base(string.Format(FeaturesResources.Install_package_0, fixData.PackageName),
                       CreateNestedActions(document, fixData, installerService),
                       isInlinable: false,
                       priority: CodeActionPriority.Low) // Adding a nuget reference is lower priority than other fixes..
            {
                Contract.ThrowIfFalse(fixData.Kind == AddImportFixKind.PackageSymbol);
            }

            private static ImmutableArray<CodeAction> CreateNestedActions(
                Document document,
                AddImportFixData fixData,
                IPackageInstallerService installerService)
            {
                // Determine what versions of this package are already installed in some project
                // in this solution.  We'll offer to add those specific versions to this project,
                // followed by an option to "Find and install latest version."
                var installedVersions = installerService.GetInstalledVersions(fixData.PackageName).NullToEmpty();
                using var _ = ArrayBuilder<CodeAction>.GetInstance(out var codeActions);

                // First add the actions to install a specific version.
                codeActions.AddRange(installedVersions.Select(
                    v => CreateCodeAction(
                        document, fixData, installerService, versionOpt: v, isLocal: true)));

                // Now add the action to install the specific version.
                var preferredVersion = fixData.PackageVersionOpt;
                if (preferredVersion == null || !installedVersions.Contains(preferredVersion))
                {
                    codeActions.Add(CreateCodeAction(
                        document, fixData, installerService, preferredVersion, isLocal: false));
                }

                // And finally the action to show the package manager dialog.
                codeActions.Add(new InstallWithPackageManagerCodeAction(installerService, fixData.PackageName));
                return codeActions.ToImmutable();
            }

            private static CodeAction CreateCodeAction(
                Document document,
                AddImportFixData fixData,
                IPackageInstallerService installerService,
                string versionOpt,
                bool isLocal)
            {
                var title = versionOpt == null
                    ? FeaturesResources.Find_and_install_latest_version
                    : isLocal
                        ? string.Format(FeaturesResources.Use_local_version_0, versionOpt)
                        : string.Format(FeaturesResources.Install_version_0, versionOpt);

                var installOperation = new InstallPackageDirectlyCodeActionOperation(
                    installerService, document, fixData.PackageSource, fixData.PackageName, versionOpt,
                    includePrerelease: false, isLocal: isLocal);

                // Nuget hits should always come after other results.
                return new InstallPackageAndAddImportCodeAction(
                    document, fixData, title, installOperation);
            }
        }
    }
}
