using System;
using static PKHeX.Core.Ball;
using static PKHeX.Core.Species;
using static PKHeX.Core.BallInheritanceResult;

namespace PKHeX.Core;

/// <summary>
/// Ball Inheritance Permissions for <see cref="EntityContext.Gen7"/> games.
/// </summary>
public sealed class BallContext7 : IBallContext
{
    public static readonly BallContext7 Instance = new();

    public bool CanBreedWithBall(ushort species, byte form, Ball ball)
    {
        // Eagerly return true for the most common case
        if (ball is Poke)
            return true;

        if (species >= Permit.Length)
            return false;
        var permitBit = GetPermitBit(ball);
        if (permitBit == BallType.None)
            return false;

        var permit = Permit[species];
        if ((permit & (1 << (byte)permitBit)) == 0)
            return false;
        return true;
    }

    public BallInheritanceResult CanBreedWithBall(ushort species, byte form, Ball ball, PKM pk)
    {
        // Eagerly return true for the most common case
        if (ball is Poke)
            return Valid;

        if (species >= Permit.Length)
            return Invalid;
        var permitBit = GetPermitBit(ball);
        if (permitBit == BallType.None)
            return Invalid;

        var permit = Permit[species];
        if ((permit & (1 << (byte)permitBit)) == 0)
            return Invalid;

        if (!BallContextHOME.IsAbilityPatchPossible(pk.Format, species) && !IsAbilityAllowed(species, form, pk, permitBit))
            return BadAbility;
        return Valid;
    }

    private static bool IsAbilityAllowed(ushort species, byte form, PKM pk, BallType permitBit) => permitBit switch
    {
        BallType.Gen3 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen3(species),
        BallType.Gen4 => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenGen4(species),
        BallType.Safari => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenSafari(species),
        BallType.Apricorn => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenApricorn(species, form),
        BallType.Beast => IsNotHidden(pk.AbilityNumber) || !IsBannedHiddenBeast(species, form),
        _ => true,
    };

    private static bool IsBannedHiddenBeast(ushort species, byte form) => species switch
    {
        (int)Voltorb => true,
        (int)Flabébé => form == 3, // Flabébé-Blue
        _ => false,
    };

    private static bool IsBannedHiddenApricorn(ushort species, byte form) => species switch
    {
        (int)NidoranF => true,
        (int)NidoranM => true,
        (int)Voltorb => true,
        (int)Bronzor => true,
        (int)Flabébé => form == 3, // Flabébé-Blue
        _ => false,
    };

    private static bool IsBannedHiddenSafari(ushort species) => species switch
    {
        (int)NidoranF => true,
        (int)NidoranM => true,
        (int)Volbeat => true,
        (int)Illumise => true,
        (int)Magnemite => true,
        (int)Voltorb => true,
        (int)Kangaskhan => true,
        (int)Tauros => true,
        (int)Ditto => true,
        (int)Miltank => true,
        (int)Beldum => true,
        (int)Bronzor => true,
        (int)Happiny => true,
        (int)Tyrogue => true,
        (int)Staryu => true,
        (int)Lunatone => true,
        (int)Solrock => true,
        (int)Rotom => true,
        (int)Klink => true,
        (int)Golett => true,
        _ => false,
    };

    private static bool IsNotHidden(int pkAbilityNumber) => pkAbilityNumber != 4;

    private static bool IsBannedHiddenGen4(ushort species) => species switch
    {
        // Fossil Only obtain
        (int)Archen => true,
        (int)Tyrunt => true,
        (int)Amaura => true,
        _ => false,
    };

    private static bool IsBannedHiddenGen3(ushort species) => species switch
    {
        // Fossil Only obtain
        (int)Archen => true,
        (int)Tyrunt => true,
        (int)Amaura => true,
        _ => false,
    };

    private static BallType GetPermitBit(Ball ball) => ball switch
    {
        Ultra => BallType.Gen3,
        Great => BallType.Gen3,
        Poke => BallType.Gen3,
        Safari => BallType.Safari,

        Net => BallType.Gen3,
        Dive => BallType.Gen3,
        Nest => BallType.Gen3,
        Repeat => BallType.Gen3,
        Timer => BallType.Gen3,
        Luxury => BallType.Gen3,
        Premier => BallType.Gen3,

        Dusk => BallType.Gen4,
        Heal => BallType.Gen4,
        Quick => BallType.Gen4,

        Fast => BallType.Apricorn,
        Level => BallType.Apricorn,
        Lure => BallType.Apricorn,
        Heavy => BallType.Apricorn,
        Love => BallType.Apricorn,
        Friend => BallType.Apricorn,
        Moon => BallType.Apricorn,

        Sport => BallType.Sport,
        Dream => BallType.Dream,
        Beast => BallType.Beast,
        _ => BallType.None,
    };

    public static ReadOnlySpan<byte> Permit =>
    [
        0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x7B, 0x03, 0x03, 0x7B, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, // 000-019
        0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x03, 0x03, 0x6B, 0x03, 0x4B, // 020-039
        0x03, 0x6F, 0x03, 0x2F, 0x03, 0x03, 0x7F, 0x03, 0x3F, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6B, 0x03, // 040-059
        0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x2F, 0x03, 0x6F, // 060-079
        0x03, 0x6F, 0x03, 0x2F, 0x2F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x6F, 0x03, 0x2F, 0x03, // 080-099
        0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x2F, 0x03, 0x6F, 0x03, 0x6F, 0x2F, 0x6F, 0x6B, 0x03, 0x6F, 0x03, // 100-119
        0x6B, 0x03, 0x6F, 0x7F, 0x03, 0x03, 0x03, 0x7F, 0x6F, 0x6F, 0x03, 0x6F, 0x00, 0x6B, 0x03, 0x03, 0x03, 0x23, 0x23, 0x03, // 120-139
        0x23, 0x03, 0x23, 0x6B, 0x00, 0x00, 0x00, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x4B, 0x03, // 140-159
        0x03, 0x2F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x6F, 0x6F, 0x6B, 0x03, 0x6F, 0x03, 0x6F, // 160-179
        0x03, 0x03, 0x03, 0x6F, 0x03, 0x6B, 0x03, 0x2F, 0x03, 0x07, 0x6F, 0x2F, 0x03, 0x6F, 0x6F, 0x03, 0x03, 0x03, 0x6F, 0x03, // 180-199
        0x6F, 0x00, 0x2F, 0x2F, 0x6F, 0x03, 0x6B, 0x2F, 0x03, 0x6F, 0x03, 0x2B, 0x4B, 0x2F, 0x6F, 0x6B, 0x2F, 0x03, 0x6B, 0x03, // 200-219
        0x6B, 0x03, 0x6B, 0x6F, 0x03, 0x6B, 0x6B, 0x6B, 0x6F, 0x03, 0x03, 0x2F, 0x03, 0x03, 0x2F, 0x6F, 0x23, 0x03, 0x6B, 0x6F, // 220-239
        0x6F, 0x6F, 0x03, 0x00, 0x00, 0x00, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, // 240-259
        0x03, 0x2B, 0x03, 0x2F, 0x07, 0x3B, 0x03, 0x03, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, // 260-279
        0x6B, 0x03, 0x03, 0x6F, 0x03, 0x2F, 0x03, 0x6F, 0x03, 0x03, 0x33, 0x03, 0x03, 0x2B, 0x03, 0x03, 0x6B, 0x03, 0x6F, 0x6F, // 280-299
        0x23, 0x03, 0x6B, 0x6B, 0x6F, 0x07, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x2B, 0x2B, 0x37, 0x37, 0x6F, 0x2F, 0x03, 0x6F, 0x03, // 300-319
        0x6B, 0x03, 0x2B, 0x03, 0x6F, 0x6B, 0x03, 0x6F, 0x6F, 0x03, 0x03, 0x27, 0x03, 0x6B, 0x03, 0x27, 0x27, 0x27, 0x27, 0x6F, // 320-339
        0x03, 0x6F, 0x03, 0x6B, 0x03, 0x23, 0x03, 0x23, 0x03, 0x6B, 0x03, 0x6B, 0x6F, 0x6F, 0x03, 0x27, 0x03, 0x6F, 0x2F, 0x6B, // 340-359
        0x2F, 0x6B, 0x03, 0x6F, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x6B, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x00, 0x00, 0x00, // 360-379
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6F, 0x03, 0x03, 0x2F, // 380-399
        0x03, 0x3B, 0x03, 0x6F, 0x03, 0x03, 0x6F, 0x03, 0x23, 0x03, 0x23, 0x03, 0x2B, 0x03, 0x03, 0x3B, 0x03, 0x27, 0x6F, 0x03, // 400-419
        0x2B, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x23, 0x03, 0x2F, 0x23, 0x03, 0x2F, 0x03, 0x6B, 0x6F, // 420-439
        0x6F, 0x2B, 0x23, 0x6F, 0x03, 0x03, 0x6B, 0x6F, 0x03, 0x6F, 0x03, 0x6F, 0x03, 0x27, 0x03, 0x2F, 0x6B, 0x03, 0x6B, 0x6B, // 440-459
        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x6B, // 460-479
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, // 480-499
        0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, 0x03, 0x23, // 500-519
        0x03, 0x03, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x6B, 0x6B, 0x03, 0x03, 0x23, 0x03, 0x03, 0x23, 0x23, // 520-539
        0x4B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x6B, 0x03, 0x6B, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x23, 0x6B, 0x03, 0x6B, // 540-559
        0x03, 0x6B, 0x03, 0x03, 0x23, 0x03, 0x00, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x6B, 0x03, 0x03, 0x6B, 0x03, 0x03, // 560-579
        0x6B, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x6B, 0x23, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x6B, 0x23, 0x03, 0x03, 0x03, 0x6B, // 580-599
        0x03, 0x03, 0x4B, 0x03, 0x03, 0x6B, 0x03, 0x4B, 0x03, 0x03, 0x6B, 0x03, 0x03, 0x03, 0x03, 0x03, 0x23, 0x03, 0x6B, 0x4B, // 600-619
        0x03, 0x6B, 0x6B, 0x03, 0x6B, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x23, 0x23, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x00, 0x00, // 620-639
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x03, 0x03, // 640-659
        0x03, 0x4B, 0x03, 0x4B, 0x4B, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x03, 0x4B, // 660-679
        0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x4B, 0x03, 0x00, 0x03, 0x00, 0x03, // 680-699
        0x03, 0x4B, 0x4B, 0x4B, 0x4B, 0x03, 0x03, 0x4B, 0x4B, 0x03, 0x03, 0x03, 0x03, 0x03, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, // 700-719
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4B, 0x43, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x43, 0x4B, // 720-739
        0x43, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x43, 0x4B, // 740-759
        0x43, 0x4B, 0x43, 0x43, 0x4B, 0x4B, 0x4B, 0x4B, 0x43, 0x4B, 0x43, 0x4B, 0x00, 0x00, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, 0x4B, // 760-779
        0x4B, 0x4B, 0x4B, 0x43, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 780-799
    ];

    private enum BallType : byte
    {
        Gen3 = 0,
        Gen4 = 1,
        Safari = 2,
        Apricorn = 3,
        Sport = 4,
        Dream = 5,
        Beast = 6,

        None = 9,
    }
}
