using PkmnLib.Dynamic.AI.Explicit;
using PkmnLib.Plugin.Gen7.AI;

namespace PkmnLib.Plugin.Gen7.Scripts.Moves;

public abstract class ChangeUserStats : Script, IScriptOnInitialize, IScriptOnSecondaryEffect
{
    private readonly Statistic _stat;
    private sbyte _amount;

    protected ChangeUserStats(Statistic stat)
    {
        _stat = stat;
    }

    /// <inheritdoc />
    public void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
    {
        if (parameters == null)
        {
            throw new ArgumentNullException(nameof(parameters));
        }

        if (!parameters.TryGetValue("amount", out var amount) || amount == null)
        {
            throw new ArgumentException("Parameter 'amount' is required.");
        }

        _amount = (sbyte)(int)amount;
    }

    /// <inheritdoc />
    public void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
    {
        move.User.ChangeStatBoost(_stat, _amount, true, false);
    }

    protected static bool WouldMoveFail(MoveOption option, Statistic stat) =>
        option.Move.User.StatBoost.GetStatistic(stat) == StatBoostStatisticSet.MaxStatBoost;

    protected static void GetMoveEffectScore(MoveOption option, Statistic stat, ref int score)
    {
        if (option.Move.Move.SecondaryEffect == null ||
            !option.Move.Move.SecondaryEffect.Parameters.TryGetValue("amount", out var amountObj) ||
            amountObj is not int amount)
        {
            return;
        }
        var statisticSet = new StatBoostStatisticSet();
        statisticSet.SetStatistic(stat, (sbyte)amount);
        score = AIHelperFunctions.GetScoreForTargetStatRaise(score, option.Move, option.Move.User, statisticSet);
    }
}

[Script(ScriptCategory.Move, "change_user_attack")]
public class ChangeUserAttack : ChangeUserStats
{
    public ChangeUserAttack() : base(Statistic.Attack)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) => WouldMoveFail(option, Statistic.Attack);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.Attack, ref score);
}

[Script(ScriptCategory.Move, "change_user_defense")]
public class ChangeUserDefense : ChangeUserStats
{
    public ChangeUserDefense() : base(Statistic.Defense)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) => WouldMoveFail(option, Statistic.Defense);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.Defense, ref score);
}

[Script(ScriptCategory.Move, "change_user_special_attack")]
public class ChangeUserSpecialAttack : ChangeUserStats
{
    public ChangeUserSpecialAttack() : base(Statistic.SpecialAttack)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) =>
        WouldMoveFail(option, Statistic.SpecialAttack);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.SpecialAttack, ref score);
}

[Script(ScriptCategory.Move, "change_user_special_defense")]
public class ChangeUserSpecialDefense : ChangeUserStats
{
    public ChangeUserSpecialDefense() : base(Statistic.SpecialDefense)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) =>
        WouldMoveFail(option, Statistic.SpecialDefense);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.SpecialDefense, ref score);
}

[Script(ScriptCategory.Move, "change_user_speed")]
public class ChangeUserSpeed : ChangeUserStats
{
    public ChangeUserSpeed() : base(Statistic.Speed)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) => WouldMoveFail(option, Statistic.Speed);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.Speed, ref score);
}

[Script(ScriptCategory.Move, "change_user_accuracy")]
public class ChangeUserAccuracy : ChangeUserStats
{
    public ChangeUserAccuracy() : base(Statistic.Accuracy)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) => WouldMoveFail(option, Statistic.Accuracy);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.Accuracy, ref score);
}

[Script(ScriptCategory.Move, "change_user_evasion")]
public class ChangeUserEvasion : ChangeUserStats
{
    public ChangeUserEvasion() : base(Statistic.Evasion)
    {
    }

    [AIMoveFailureFunction]
    public static bool AIMoveWillFail(IExplicitAI ai, MoveOption option) => WouldMoveFail(option, Statistic.Evasion);

    [AIMoveScoreFunction]
    public static void AIMoveEffectScore(IExplicitAI ai, MoveOption option, ref int score) =>
        GetMoveEffectScore(option, Statistic.Evasion, ref score);
}