using PkmnLib.Dynamic.Libraries.DataLoaders;
using PkmnLib.Dynamic.Plugins;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Static.Libraries;

namespace PkmnLib.Dynamic.Libraries;

/// <summary>
/// Helper class for building data from plugins.
/// </summary>
public static class LibraryLoader
{
    /// <summary>
    /// Result of loading plugins.
    /// </summary>
    public record LoadResult(ScriptRegistry Registry, ScriptResolver Resolver, IStaticLibrary StaticLibrary);

    /// <summary>
    /// Loads plugins and creates a static library from them.
    /// </summary>
    public static LoadResult LoadPlugins(IEnumerable<IPlugin> plugins)
    {
        var registry = new ScriptRegistry();
        var orderedPlugins = plugins.OrderBy(x => x.LoadOrder).ToList();
        var staticLibrary = CreateStaticLibrary(orderedPlugins);
        foreach (var plugin in orderedPlugins)
        {
            plugin.Register(registry);
        }
        if (registry.DamageCalculator is null)
            throw new InvalidOperationException("Damage calculator not found in plugins.");
        if (registry.BattleStatCalculator is null)
            throw new InvalidOperationException("Stat calculator not found in plugins.");
        if (registry.MiscLibrary is null)
            throw new InvalidOperationException("Misc library not found in plugins.");
        if (registry.CaptureLibrary is null)
            throw new InvalidOperationException("Capture library not found in plugins.");
        if (registry.ExperienceGainCalculator is null)
            throw new InvalidOperationException("Experience gain calculator not found in plugins.");
        var scriptResolver = new ScriptResolver(registry.ScriptTypes, registry.ItemScriptTypes);
        return new LoadResult(registry, scriptResolver, staticLibrary);
    }

    private static StaticLibraryImpl CreateStaticLibrary(IReadOnlyList<IPlugin> plugins)
    {
        var resourceProviders = plugins.OfType<IResourceProvider>().ToList();
        var settings = resourceProviders.Select(x => x.Settings).LastOrDefault(x => x != null);
        if (settings == null)
            throw new InvalidOperationException("Settings not found.");
        var typesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Types))
            .LastOrDefault(x => x != null);
        if (typesResult == null)
            throw new InvalidOperationException("Types resource not found.");
        var naturesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Natures))
            .LastOrDefault(x => x != null);
        if (naturesResult == null)
            throw new InvalidOperationException("Natures resource not found.");
        var movesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Moves))
            .LastOrDefault(x => x != null);
        if (movesResult == null)
            throw new InvalidOperationException("Moves resource not found.");
        var itemsResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Items))
            .LastOrDefault(x => x != null);
        if (itemsResult == null)
            throw new InvalidOperationException("Items resource not found.");
        var abilitiesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Abilities))
            .LastOrDefault(x => x != null);
        if (abilitiesResult == null)
            throw new InvalidOperationException("Abilities resource not found.");
        var growthRatesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.GrowthRates))
            .LastOrDefault(x => x != null);
        if (growthRatesResult == null)
            throw new InvalidOperationException("Growth rates resource not found.");
        var speciesResult = resourceProviders.Select(x => x.GetResource(ResourceFileType.Species))
            .LastOrDefault(x => x != null);
        if (speciesResult == null)
            throw new InvalidOperationException("Species resource not found.");

        // ReSharper disable once SuspiciousTypeConversion.Global
        var mutators = plugins.OfType<IPluginDataMutator>().ToList();

        using var typesStream = typesResult.Open();
        var typesLibrary = TypeDataLoader.LoadTypeLibrary(typesStream);
        using var naturesStream = naturesResult.Open();
        var naturesLibrary = NatureDataLoader.LoadNatureLibrary(naturesStream);
        using var movesStream = movesResult.Open();
        var movesLibrary = MoveDataLoader.LoadMoves(movesStream, typesLibrary,
            wrapper => mutators.ForEach(x => x.MutateMoveData(wrapper)));
        using var itemsStream = itemsResult.Open();
        var itemsLibrary = ItemDataLoader.LoadItems(itemsStream,
            items => mutators.ForEach(x => x.MutateItemData(items)));
        using var abilitiesStream = abilitiesResult.Open();
        var abilitiesLibrary = AbilityDataLoader.LoadAbilities(abilitiesStream,
            abilities => mutators.ForEach(x => x.MutateAbilityData(abilities)));
        using var growthRatesStream = growthRatesResult.Open();
        var growthRatesLibrary = GrowthRateDataLoader.LoadGrowthRates(growthRatesStream,
            growthRates => mutators.ForEach(x => x.MutateGrowthRateData(growthRates)));
        using var speciesStream = speciesResult.Open();
        var speciesLibrary = SpeciesDataLoader.LoadSpecies(speciesStream, typesLibrary,
            map => mutators.ForEach(x => x.MutateSpeciesData(map)));

        return new StaticLibraryImpl(settings, speciesLibrary, movesLibrary, abilitiesLibrary, typesLibrary,
            naturesLibrary, growthRatesLibrary, itemsLibrary);
    }
}