// 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.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Snippets.SnippetProviders;

namespace Microsoft.CodeAnalysis.Snippets;

internal abstract class AbstractSnippetService(IEnumerable<Lazy<ISnippetProvider, LanguageMetadata>> lazySnippetProviders) : ISnippetService
{
    private readonly ImmutableArray<Lazy<ISnippetProvider, LanguageMetadata>> _lazySnippetProviders = [.. lazySnippetProviders];
    private readonly Dictionary<string, ISnippetProvider> _identifierToProviderMap = [];
    private readonly object _snippetProvidersLock = new();
    private ImmutableArray<ISnippetProvider> _snippetProviders;

    /// <summary>
    /// This should never be called prior to GetSnippetsAsync because it gets populated
    /// at that point in time.
    /// </summary>
    public ISnippetProvider GetSnippetProvider(string snippetIdentifier)
    {
        Contract.ThrowIfFalse(_identifierToProviderMap.ContainsKey(snippetIdentifier));
        return _identifierToProviderMap[snippetIdentifier];
    }

    /// <summary>
    /// Iterates through all providers and determines if the snippet 
    /// can be added to the Completion list at the corresponding position.
    /// </summary>
    public ImmutableArray<SnippetData> GetSnippets(SnippetContext context, CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<SnippetData>.GetInstance(out var arrayBuilder);
        EnsureSnippetsLoaded(context.Document.Project.Language);
        foreach (var provider in _snippetProviders)
        {
            if (provider.IsValidSnippetLocation(context, cancellationToken))
                arrayBuilder.Add(new(provider.Identifier, provider.Description, provider.AdditionalFilterTexts));
        }

        return arrayBuilder.ToImmutableAndClear();
    }

    internal void EnsureSnippetsLoaded(string language)
    {
        lock (_snippetProvidersLock)
        {
            if (_snippetProviders.IsDefault)
            {
                using var _ = ArrayBuilder<ISnippetProvider>.GetInstance(out var arrayBuilder);
                foreach (var provider in _lazySnippetProviders.Where(p => p.Metadata.Language == language))
                {
                    var providerData = provider.Value;
                    Debug.Assert(!_identifierToProviderMap.TryGetValue(providerData.Identifier, out var _));
                    _identifierToProviderMap.Add(providerData.Identifier, providerData);
                    arrayBuilder.Add(providerData);
                }

                _snippetProviders = arrayBuilder.ToImmutable();
            }
        }
    }
}
