using System.Runtime.CompilerServices;
using PkmnLib.Dynamic.Events;
using PkmnLib.Dynamic.Models;
using PkmnLib.Static;

namespace PkmnLib.Dynamic.ScriptHandling;

/// <summary>
/// Helper functions for script execution.
/// </summary>
public static class ScriptExecution
{
    /// <summary>
    /// Executes a hook on all scripts in a source.
    /// </summary>
    public static void RunScriptHook<TScriptHook>(this IScriptSource source, Action<TScriptHook> hook)
    {
        var iterator = source.GetScripts();
        List<ScriptCategory>? suppressedCategories = null;
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is IScriptOnBeforeAnyHookInvoked onBeforeAnyHookInvoked)
                onBeforeAnyHookInvoked.OnBeforeAnyHookInvoked(ref suppressedCategories);
        }
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is not TScriptHook scriptHook)
                continue;
            if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
                continue;
            hook(scriptHook);
        }
    }

    /// <summary>
    /// Executes a hook on all scripts in a source.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void RunScriptHook<TScriptHook>(this IEnumerable<IScriptSource> sources, Action<TScriptHook> hook)
    {
        var iterator = sources.Distinct().SelectMany(x => x.GetScripts()).ToArray();
        List<ScriptCategory>? suppressedCategories = null;
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is IScriptOnBeforeAnyHookInvoked onBeforeAnyHookInvoked)
                onBeforeAnyHookInvoked.OnBeforeAnyHookInvoked(ref suppressedCategories);
        }
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is not TScriptHook scriptHook)
                continue;
            if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
                continue;
            hook(scriptHook);
        }
    }

    /// <summary>
    /// Executes a hook on all scripts in a list of sources. Note that this does not walk through the parents of the
    /// sources, but only the sources themselves.
    /// </summary>
    public static void RunScriptHook<TScriptHook>(this IReadOnlyList<IEnumerable<ScriptContainer>> source,
        Action<TScriptHook> hook)
    {
        List<ScriptCategory>? suppressedCategories = null;
        var iterator = new ScriptIterator(source);
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is IScriptOnBeforeAnyHookInvoked onBeforeAnyHookInvoked)
                onBeforeAnyHookInvoked.OnBeforeAnyHookInvoked(ref suppressedCategories);
        }
        foreach (var container in iterator)
        {
            if (container.IsEmpty)
                continue;
            var script = container.Script;
            if (script is not TScriptHook scriptHook)
                continue;
            if (suppressedCategories != null && suppressedCategories.Contains(script.Category))
                continue;
            hook(scriptHook);
        }
    }

    /// <summary>
    /// Executes a script on an item.
    /// </summary>
    public static void RunItemScript(this IItem item, ScriptResolver scriptResolver, IPokemon? target, IPokemon user,
        IBattle battle, EventHook eventHook)
    {
        if (!scriptResolver.TryResolveBattleItemScript(item, out var itemScript))
        {
            return;
        }
        if (!itemScript.IsItemUsable)
            return;

        itemScript.OnInitialize(item.BattleEffect!.Parameters);
        var targetType = itemScript.TargetType;
        if (targetType == ItemTargetType.None)
        {
            itemScript.OnUse(eventHook);
        }
        else
        {
            if (target == null)
                throw new ArgumentNullException(nameof(target), "Item script requires a target.");
            if (!targetType.IsValidTarget(battle, user, target))
                throw new InvalidOperationException("Item script target is not valid for the required target type.");
            if (!itemScript.IsTargetValid(target))
                throw new InvalidOperationException("Item script target is not valid.");
            itemScript.OnUseWithTarget(target, eventHook);
        }
    }
}