using YMhut.Box.Core.Tools;
using YMhut.Box.Core.Plugins;
using YMhut.Box.WinUI.ViewModels.Tools;

namespace YMhut.Box.WinUI.Views.Tools;

public interface IToolPageFactory
{
    IReadOnlyCollection<string> RegisteredIds { get; }

    ToolPageBase Create(IToolModule module, Action? goBack = null);

    bool IsRegistered(string id);

    ToolPageRegistrationReport Validate(ToolCatalog catalog);
}

public sealed record ToolPageRegistrationReport(
    int CatalogCount,
    int RegisteredCount,
    int ResolvableCount,
    int NativeCatalogCount,
    int IgnoredDynamicCount,
    IReadOnlyList<string> MissingIds)
{
    public bool IsComplete => MissingIds.Count == 0;
}

public sealed partial class ToolPageRegistry : IToolPageFactory
{
    private readonly Dictionary<string, ToolPageDescriptor> _descriptors = new(StringComparer.OrdinalIgnoreCase);

    public static ToolPageRegistry Current { get; } = CreateDefault();

    public IReadOnlyCollection<string> RegisteredIds => _descriptors.Keys.ToArray();

    private ToolPageRegistry()
    {
    }

    public ToolPageBase Create(IToolModule module, Action? goBack = null)
    {
        if (module is PluginToolModule pluginModule || PluginIds.IsPluginToolId(module.Id))
        {
            if (module is not PluginToolModule typedPluginModule)
            {
                throw new InvalidOperationException($"Plugin tool module is not loaded: {module.Id}");
            }

            return new PluginHostPage(typedPluginModule.Plugin, typedPluginModule.Surface, goBack);
        }

        if (!_descriptors.TryGetValue(module.Id, out var descriptor))
        {
            throw new InvalidOperationException($"Tool page is not registered: {module.Id}");
        }

        return descriptor.Factory(module, goBack);
    }

    public bool IsRegistered(string id) =>
        PluginIds.IsPluginToolId(id) ||
        ExternalToolModule.IsExternalToolId(id) ||
        BuiltinReferenceToolModule.IsBuiltinToolId(id) ||
        _descriptors.ContainsKey(id);

    public ToolPageRegistrationReport Validate(ToolCatalog catalog)
    {
        var nativeModules = catalog.Modules
            .Where(RequiresNativeRegistration)
            .ToArray();
        var dynamicCount = catalog.Modules.Count - nativeModules.Length;
        var resolvable = catalog.Modules.Count(module => !RequiresNativeRegistration(module) || IsRegistered(module.Id));
        var missing = catalog.Modules
            .Where(RequiresNativeRegistration)
            .Where(module => !IsRegistered(module.Id))
            .Select(module => module.Id)
            .Order(StringComparer.OrdinalIgnoreCase)
            .ToArray();

        return new ToolPageRegistrationReport(
            catalog.Modules.Count,
            _descriptors.Count,
            resolvable,
            nativeModules.Length,
            dynamicCount,
            missing);
    }

    private static bool RequiresNativeRegistration(IToolModule module)
    {
        return module is ToolModule &&
            !PluginIds.IsPluginToolId(module.Id) &&
            !ExternalToolModule.IsExternalToolId(module.Id) &&
            !BuiltinReferenceToolModule.IsBuiltinToolId(module.Id);
    }

    private void Register<TPage, TViewModel>(
        string id,
        Func<IToolModule, Action?, TPage> factory)
        where TPage : ToolPageBase
        where TViewModel : AdaptiveToolViewModel
    {
        _descriptors[id] = new ToolPageDescriptor(
            id,
            typeof(TPage),
            typeof(TViewModel),
            (module, goBack) => factory(module, goBack));
    }

    private static ToolPageRegistry CreateDefault()
    {
        var registry = new ToolPageRegistry();
        RegisterGenerated(registry);
        return registry;
    }

    static partial void RegisterGenerated(ToolPageRegistry registry);

    private sealed record ToolPageDescriptor(
        string Id,
        Type PageType,
        Type ViewModelType,
        Func<IToolModule, Action?, ToolPageBase> Factory);
}
