using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.AI;

/// <summary>
/// The base class for implementing an AI for Pokémon.
/// </summary>
[PublicAPI, UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
public abstract class PokemonAI
{
    /// <summary>
    /// The name of the AI.
    /// </summary>
    public StringKey Name { get; }

    /// <inheritdoc cref="PokemonAI" />
    protected PokemonAI(StringKey name)
    {
        Name = name;
    }

    /// <summary>
    /// Gets the choice for the Pokémon.
    /// </summary>
    public abstract ITurnChoice GetChoice(IBattle battle, IPokemon pokemon);

    /// <summary>
    /// For a given user and move, returns the valid targets for that move.
    /// </summary>
    public IEnumerable<(byte side, byte position)> GetValidTargetsForMove(IPokemon user, ILearnedMove move)
    {
        var userBattleData = user.BattleData!;
        switch (move.MoveData.Target)
        {
            case MoveTarget.Adjacent:
                yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
                break;
            case MoveTarget.AdjacentAlly:
                if (userBattleData.Position > 0)
                    yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
                if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
                    yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
                break;
            case MoveTarget.AdjacentAllySelf:
                if (userBattleData.Position > 0)
                    yield return (userBattleData.SideIndex, (byte)(userBattleData.Position - 1));
                if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
                    yield return (userBattleData.SideIndex, (byte)(userBattleData.Position + 1));
                yield return (userBattleData.SideIndex, userBattleData.Position);
                break;
            case MoveTarget.AdjacentOpponent:
                yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
                if (userBattleData.Position > 0)
                    yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position - 1));
                if (userBattleData.Battle.PositionsPerSide > userBattleData.Position + 1)
                    yield return (GetOppositeSide(userBattleData.SideIndex), (byte)(userBattleData.Position + 1));
                break;
            case MoveTarget.All:
                yield return (userBattleData.SideIndex, userBattleData.Position);
                break;
            case MoveTarget.AllAdjacent:
                yield return (userBattleData.SideIndex, userBattleData.Position);
                break;
            case MoveTarget.AllAdjacentOpponent:
                yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
                break;
            case MoveTarget.AllAlly:
                yield return (userBattleData.SideIndex, userBattleData.Position);
                break;
            case MoveTarget.AllOpponent:
                yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
                break;
            case MoveTarget.Any:
                foreach (var side in userBattleData.Battle.Sides)
                {
                    foreach (var pokemon in side.Pokemon)
                    {
                        if (pokemon?.BattleData == null)
                            continue;
                        yield return (side.Index, pokemon.BattleData!.Position);
                    }
                }
                break;
            case MoveTarget.RandomOpponent:
                yield return (GetOppositeSide(userBattleData.SideIndex), userBattleData.Position);
                break;
            case MoveTarget.SelfUse:
                yield return (userBattleData.SideIndex, userBattleData.Position);
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        yield break;
        byte GetOppositeSide(byte side) => side == 0 ? (byte)1 : (byte)0;
    }

    public static List<PokemonAI> InstantiateAis(IDynamicLibrary library)
    {
        return AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes())
            .Where(type => type.IsSubclassOf(typeof(PokemonAI)) && !type.IsAbstract).Select(x =>
            {
                var ctorWithLibrary = x.GetConstructor([typeof(IDynamicLibrary)]);
                if (ctorWithLibrary != null)
                    return Activator.CreateInstance(x, library);
                var defaultCtor = x.GetConstructor(Type.EmptyTypes);
                if (defaultCtor != null)
                    return Activator.CreateInstance(x);
                throw new InvalidOperationException($"No suitable constructor found for {x.Name}. " +
                                                    "Ensure it has a constructor with IDynamicLibrary parameter or a default constructor.");
            }).Cast<PokemonAI>().ToList();
    }
}