using PkmnLib.Dynamic.Libraries;
using PkmnLib.Static.Species;

namespace PkmnLib.Plugin.Gen7.Libraries.Battling;

public class Gen7CaptureLibrary : ICaptureLibrary
{
    private readonly Gen7PluginConfiguration _configuration;

    public Gen7CaptureLibrary(Gen7PluginConfiguration configuration)
    {
        _configuration = configuration;
    }

    public bool HasPokemonBeenCaughtBefore(ISpecies species) => _configuration.TimesSpeciesCaught(species) > 0;

    /// <inheritdoc />
    public CaptureResult TryCapture(IPokemon target, IItem captureItem, IBattleRandom random)
    {
        var maxHealth = target.BoostedStats.Hp;
        var currentHealth = target.CurrentHealth;
        var catchRate = target.Species.CaptureRate;

        if (target.Library.ScriptResolver.TryResolveBattleItemScript(captureItem, out var script) &&
            script is PokeballScript pokeballScript)
        {
            pokeballScript.ChangeCatchRate(target, ref catchRate);
        }

        byte bonusStatus = 1;
        target.RunScriptHook<IScriptChangeCatchRateBonus>(x =>
            x.ChangeCatchRateBonus(target, captureItem, ref bonusStatus));

        var modifiedCatchRate = (3.0f * maxHealth - 2.0f * currentHealth) * catchRate / (3.0f * maxHealth);
        modifiedCatchRate *= bonusStatus;

        var shakeProbability = 65536 / Math.Pow(255 / modifiedCatchRate, 0.1875f);
        byte shakes = 0;
        if (modifiedCatchRate >= 255)
        {
            shakes = 4;
        }
        else
        {
            var timesCaught = _configuration.TimesSpeciesCaught(target.Species);
            if (timesCaught > 30)
            {
                var criticalCaptureModifier = timesCaught switch
                {
                    > 600 => 2.5f,
                    > 450 => 2.0f,
                    > 300 => 1.5f,
                    > 150 => 1.0f,
                    > 30 => 0.5f,
                    // Default arm, should be heuristically unreachable (due to the timesCaught > 30 check above)
                    _ => throw new ArgumentOutOfRangeException(),
                };
                var criticalCaptureChance = (int)(modifiedCatchRate * criticalCaptureModifier / 6);
                if (random.GetInt(0, 256) < criticalCaptureChance)
                {
                    return new CaptureResult(true, 1, true);
                }
            }

            for (var i = 0; i < 4; i++)
            {
                if (random.GetInt(0, 65536) < shakeProbability)
                {
                    shakes++;
                }
            }
        }
        var success = shakes >= 4;
        return new CaptureResult(success, shakes, false);
    }
}