// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Immutable;
using Azure.Bicep.Types;
using Azure.Bicep.Types.Index;
using Bicep.Core.Extensions;
using Bicep.Core.Resources;
using Bicep.Core.TypeSystem.Types;
using Microsoft.WindowsAzure.ResourceStack.Common.Extensions;

namespace Bicep.Core.TypeSystem.Providers.Az
{
    public class AzResourceTypeLoader : IResourceTypeLoader
    {
        private readonly ITypeLoader typeLoader;
        private readonly AzResourceTypeFactory resourceTypeFactory;
        private readonly ImmutableDictionary<ResourceTypeReference, CrossFileTypeReference> availableTypes;
        private readonly ImmutableDictionary<string, ImmutableDictionary<string, ImmutableArray<CrossFileTypeReference>>> availableFunctions;
        private readonly TypeSettings? typeSettings;

        public AzResourceTypeLoader(ITypeLoader typeLoader, TypeIndex? typeIndex = null)
        {
            this.typeLoader = typeLoader;
            resourceTypeFactory = new AzResourceTypeFactory();
            var indexedTypes = typeIndex ?? typeLoader.LoadTypeIndex();
            typeSettings = indexedTypes.Settings;
            availableTypes = indexedTypes.Resources.ToImmutableDictionary(
                kvp => ResourceTypeReference.Parse(kvp.Key),
                kvp => kvp.Value);
            availableFunctions = indexedTypes.ResourceFunctions.ToImmutableDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.ToImmutableDictionary(
                    x => x.Key,
                    x => x.Value.ToImmutableArray(),
                    StringComparer.OrdinalIgnoreCase),
                StringComparer.OrdinalIgnoreCase);
        }

        public IEnumerable<ResourceTypeReference> GetAvailableTypes() => availableTypes.Keys;

        public bool HasType(ResourceTypeReference reference) => availableTypes.ContainsKey(reference);

        public ResourceTypeComponents LoadType(ResourceTypeReference reference)
        {
            var typeLocation = availableTypes[reference];

            if (!availableFunctions.TryGetValue(reference.FormatType(), out var apiFunctions) ||
                reference.ApiVersion is null ||
                !apiFunctions.TryGetValue(reference.ApiVersion, out var functions))
            {
                functions = [];
            }

            var functionOverloads = functions
                .SelectMany(typeLocation => resourceTypeFactory.GetResourceFunctionOverloads(typeLoader.LoadResourceFunctionType(typeLocation)))
                .Where(x => x.Name.StartsWithOrdinalInsensitively(LanguageConstants.ListFunctionPrefix));

            var serializedResourceType = typeLoader.LoadResourceType(typeLocation);
            return resourceTypeFactory.GetResourceType(serializedResourceType, functionOverloads);
        }

        public ObjectLikeType? LoadConfigurationType()
        {
            if (typeSettings?.ConfigurationType is not { } reference)
            {
                return null;
            }

            var serializedConfigurationType = typeLoader.LoadType(reference);

            if (resourceTypeFactory.GetConfigurationType(serializedConfigurationType) is not ObjectLikeType configurationType)
            {
                throw new InvalidOperationException($"Extension configuration type at index {reference.Index} in \"{reference.RelativePath}\" is not a valid ObjectLikeType.");
            }

            return configurationType;
        }
    }
}
