// 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.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tags;
using Microsoft.CodeAnalysis.Editor.Wpf;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Core.Imaging;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Imaging.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Shared;

internal readonly struct CompositeImage
{
    public readonly ImmutableArray<ImageCompositionLayer> Layers;
    public readonly IImageHandle ImageHandle;

    public CompositeImage(ImmutableArray<ImageCompositionLayer> layers, IImageHandle imageHandle)
    {
        this.Layers = layers;
        this.ImageHandle = imageHandle;
    }
}

[ExportImageIdService(Name = Name)]
[Order(Before = DefaultImageIdService.Name)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class VisualStudioImageIdService(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) : IImageIdService
{
    public const string Name = nameof(VisualStudioImageIdService);

    private readonly IThreadingContext _threadingContext = threadingContext;
    private readonly IVsImageService2 _imageService = (IVsImageService2)serviceProvider.GetService(typeof(SVsImageService));

    // We have to keep the image handles around to keep the compound glyph alive.
    private readonly List<CompositeImage> _compositeImages = [];

    public bool TryGetImageId(ImmutableArray<string> tags, out ImageId imageId)
    {
        _threadingContext.ThrowIfNotOnUIThread();

        imageId = GetImageId(tags);
        return imageId != default;
    }

    private ImageId GetImageId(ImmutableArray<string> tags)
    {
        var glyph = tags.GetFirstGlyph();
        switch (glyph)
        {
            case Glyph.AddReference:
                return GetCompositedImageId(
                    CreateLayer(Glyph.Reference.GetImageMoniker(), virtualXOffset: 1, virtualYOffset: 2),
                    CreateLayer(KnownMonikers.PendingAddNode, virtualWidth: 7, virtualXOffset: -1, virtualYOffset: -2));
        }

        return glyph.GetImageId();
    }

    private static ImageCompositionLayer CreateLayer(
        ImageMoniker imageMoniker,
        int virtualWidth = 16,
        int virtualYOffset = 0,
        int virtualXOffset = 0)
    {
        return new ImageCompositionLayer
        {
            VirtualWidth = virtualWidth,
            VirtualHeight = 16,
            ImageMoniker = imageMoniker,
            HorizontalAlignment = (uint)_UIImageHorizontalAlignment.IHA_Left,
            VerticalAlignment = (uint)_UIImageVerticalAlignment.IVA_Top,
            VirtualXOffset = virtualXOffset,
            VirtualYOffset = virtualYOffset,
        };
    }

    private ImageId GetCompositedImageId(params ImageCompositionLayer[] layers)
    {
        _threadingContext.ThrowIfNotOnUIThread();

        foreach (var compositeImage in _compositeImages)
        {
            if (compositeImage.Layers.SequenceEqual(layers))
            {
                return compositeImage.ImageHandle.Moniker.ToImageId();
            }
        }

        var imageHandle = _imageService.AddCustomCompositeImage(
                virtualWidth: 16, virtualHeight: 16,
                layerCount: layers.Length, layers: layers);

        _compositeImages.Add(new CompositeImage(layers.AsImmutableOrEmpty(), imageHandle));

        var moniker = imageHandle.Moniker;
        return moniker.ToImageId();
    }
}
