using System.Diagnostics.CodeAnalysis;
using PkmnLib.Static;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.ScriptHandling;

/// <summary>
/// Class responsible for the creation of <see cref="Script"/> instances.
/// </summary>
public class ScriptResolver
{
    private IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> _scriptCtors;
    private IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> _itemScriptCtors;
    private readonly Dictionary<IItem, ItemScript> _itemBattleScripts = new();
    private readonly Dictionary<IItem, ItemScript> _itemScripts = new();

    /// <inheritdoc cref="ScriptResolver"/>
    public ScriptResolver(IReadOnlyDictionary<(ScriptCategory, StringKey), Func<Script>> scriptCtors,
        IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> itemScriptCtors)
    {
        _scriptCtors = scriptCtors;
        _itemScriptCtors = itemScriptCtors;
    }

    /// <summary>
    /// Try and create a new script for the given category and key. If the script does not exist, return false.
    /// </summary>
    public bool TryResolve(ScriptCategory category, StringKey key, IReadOnlyDictionary<StringKey, object?>? parameters,
        [MaybeNullWhen(false)] out Script script)
    {
        if (!_scriptCtors.TryGetValue((category, key), out var scriptCtor))
        {
            script = null;
            return false;
        }

        script = scriptCtor();
        if (script is IScriptOnInitialize scriptOnInitialize)
            scriptOnInitialize.OnInitialize(parameters);

        return true;
    }

    /// <summary>
    /// Try and resolve an item script for the given item. If the item does not have a script, return false.
    /// </summary>
    public bool TryResolveBattleItemScript(IItem item, [MaybeNullWhen(false)] out ItemScript script)
    {
        if (_itemBattleScripts.TryGetValue(item, out script))
        {
            return true;
        }

        var effect = item.BattleEffect;
        if (effect == null)
        {
            script = null;
            return false;
        }
        if (!_itemScriptCtors.TryGetValue(effect.Name, out var scriptCtor))
        {
            script = null;
            return false;
        }

        script = scriptCtor(item);
        script.OnInitialize(effect.Parameters);
        _itemBattleScripts[item] = script;
        return true;
    }

    /// <summary>
    /// Try and resolve an item script for the given item. If the item does not have a script, return false.
    /// </summary>
    public bool TryResolveItemScript(IItem item, [MaybeNullWhen(false)] out ItemScript script)
    {
        if (_itemScripts.TryGetValue(item, out script))
        {
            return true;
        }

        var effect = item.Effect;
        if (effect == null)
        {
            script = null;
            return false;
        }
        if (!_itemScriptCtors.TryGetValue(effect.Name, out var scriptCtor))
        {
            script = null;
            return false;
        }

        script = scriptCtor(item);
        script.OnInitialize(effect.Parameters);
        _itemScripts[item] = script;
        return true;
    }
}