using System.Linq.Expressions;
using System.Reflection;
using PkmnLib.Dynamic.AI.Explicit;

namespace PkmnLib.Plugin.Gen7.AI;

public static class ExplicitAIFunctionRegistration
{
    public static void RegisterAIFunctions(ExplicitAIHandlers handlers)
    {
        var baseType = typeof(Script);
        foreach (var type in typeof(ExplicitAIFunctionRegistration).Assembly.GetTypes()
                     .Where(t => baseType.IsAssignableFrom(t)))
        {
            var attribute = type.GetCustomAttribute<ScriptAttribute>();
            if (attribute == null)
                continue;

            if (attribute.Category == ScriptCategory.Move)
            {
                InitializeMoveFailFunction(handlers, type, attribute);
                InitializeMoveScoreFunction(handlers, type, attribute);
            }
        }

        handlers.GeneralMoveAgainstTargetScore.Add("predicted_damage", AIDamageFunctions.PredictedDamageScore);

        AISwitchFunctions.RegisterAISwitchFunctions(handlers);
    }

    #region Reflection based function initialization

    private static void InitializeMoveFailFunction(ExplicitAIHandlers handlers, Type type, ScriptAttribute attribute)
    {
        var failureMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m =>
            m.GetCustomAttribute<AIMoveFailureFunctionAttribute>() != null);
        if (failureMethod == null)
            return;
        if (failureMethod.ReturnType != typeof(bool) || failureMethod.GetParameters().Length != 2 ||
            failureMethod.GetParameters()[0].ParameterType != typeof(IExplicitAI) ||
            failureMethod.GetParameters()[1].ParameterType != typeof(MoveOption))
        {
            throw new InvalidOperationException(
                $"Method {failureMethod.Name} in {type.Name} must return bool and take an IExplicitAI and a MoveOption as parameters.");
        }
        var aiParam = Expression.Parameter(typeof(IExplicitAI), "ai");
        var optionParam = Expression.Parameter(typeof(MoveOption), "option");
        var functionExpression = Expression.Lambda<AIBoolHandler>(
            Expression.Call(null, failureMethod, aiParam, optionParam), aiParam, optionParam).Compile();
        handlers.MoveFailureCheck.Add(attribute.Name, functionExpression);
    }

    private static void InitializeMoveScoreFunction(ExplicitAIHandlers handlers, Type type, ScriptAttribute attribute)
    {
        var scoreMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(m =>
            m.GetCustomAttribute<AIMoveScoreFunctionAttribute>() != null);
        if (scoreMethod == null)
            return;
        if (scoreMethod.ReturnType != typeof(void) || scoreMethod.GetParameters().Length != 3 ||
            scoreMethod.GetParameters()[0].ParameterType != typeof(IExplicitAI) ||
            scoreMethod.GetParameters()[1].ParameterType != typeof(MoveOption) ||
            scoreMethod.GetParameters()[2].ParameterType != typeof(int).MakeByRefType())
        {
            throw new InvalidOperationException(
                $"Method {scoreMethod.Name} in {type.Name} must return void and take an IExplicitAI, a MoveOption, and a ref int as parameters.");
        }
        var aiParam = Expression.Parameter(typeof(IExplicitAI), "ai");
        var optionParam = Expression.Parameter(typeof(MoveOption), "option");
        var scoreParam = Expression.Parameter(typeof(int).MakeByRefType(), "score");
        var functionExpression = Expression.Lambda<AIScoreMoveHandler>(
                Expression.Call(null, scoreMethod, aiParam, optionParam, scoreParam), aiParam, optionParam, scoreParam)
            .Compile();
        handlers.MoveEffectScore.Add(attribute.Name, functionExpression);
    }

    #endregion
}