#include "global.h"
#include "dungeon_ai_attack.h"

#include "charge_move.h"
#include "code_8045A00.h"
#include "dungeon_message.h"
#include "code_806CD90.h"
#include "constants/direction.h"
#include "constants/dungeon_action.h"
#include "constants/iq_skill.h"
#include "constants/move_id.h"
#include "constants/status.h"
#include "constants/tactic.h"
#include "constants/targeting.h"
#include "constants/type.h"
#include "dungeon_action.h"
#include "dungeon_ai_targeting.h"
#include "dungeon_ai.h"
#include "dungeon_capabilities.h"
#include "dungeon_engine.h"
#include "dungeon_map_access.h"
#include "dungeon_pokemon_attributes.h"
#include "dungeon_random.h"
#include "dungeon_util.h"
#include "dungeon_visibility.h"
#include "items.h"
#include "move_checks.h"
#include "move_util.h"
#include "moves.h"
#include "position_util.h"
#include "status_checks_1.h"
#include "status_checks.h"
#include "structs/str_dungeon.h"
#include "targeting_flags.h"
#include "targeting.h"
#include "type_effectiveness.h"

#define REGULAR_ATTACK_INDEX 4

const s16 gRegularAttackWeights[] = {100, 20, 30, 40, 50};

extern const u8 *const gItemStickyDoesntWorkText[];
extern const u8 *const gPtrMysteriousPowerPreventedUseMessage[];
extern const u8 *const gUnknown_80FC6A8[];
extern const u8 *const gUnknown_80FC714[];
extern const u8 *const gUnknown_80FC718[];

EWRAM_DATA bool8 gCanAttackInDirection[NUM_DIRECTIONS] = {0};
EWRAM_DATA u8 gPotentialAttackTargetDirections[NUM_DIRECTIONS] = {0};
EWRAM_DATA s32 gPotentialAttackTargetWeights[NUM_DIRECTIONS] = {0};
EWRAM_DATA Entity *gPotentialTargets[NUM_DIRECTIONS] = {0};

extern void sub_8055A00(Entity *, u8, u32, u32, u32);
extern void sub_806A9B4(Entity *, u8);
extern bool8 sub_8044B28(void);
extern void sub_8057588(Entity *, u32);
extern void sub_806A1B0(Entity *);
extern Item *sub_8044D90(Entity *, s32, u32);
extern bool8 sub_8044D40(ActionContainer *, u32);
extern bool8 TryUseChosenMove(struct Entity *, u32, u32, u32, u32, struct Move *);
extern void sub_8045BF8(u8 *, struct Item *);
extern void sub_8044DF0(struct Entity *, u32, u32);
extern void sub_8071DA4(struct Entity *);
extern void sub_804AC20(DungeonPos *);
extern void sub_807EC28(bool8);
extern void sub_806A5B8(struct Entity *entity);

void ChooseAIMove(Entity *pokemon)
{
    EntityInfo *pokemonInfo = GetEntInfo(pokemon);
    s32 i;
    s32 chargeStatus = STATUS_CHARGING;
    struct AIPossibleMove aiPossibleMove[MAX_MON_MOVES + 1];
    bool8 willNotUnlinkMove[MAX_MON_MOVES];
    s32 randomWeight;
    bool8 hasPPChecker;
    s32 numUsableMoves;
    s32 total;
    s32 weightCounter;
    bool8 hasWeakTypePicker;
    s32 regularAttackTargetDir;
    bool8 canTargetRegularAttack;
    s32 maxWeight;
    if (CannotAttack(pokemon, FALSE) ||
        ShouldMonsterRunAwayAndShowEffect(pokemon, TRUE) ||
        HasTactic(pokemon, TACTIC_KEEP_YOUR_DISTANCE) ||
        (pokemonInfo->cringeClassStatus.status == STATUS_CONFUSED && DungeonRandOutcome(gConfusedAttackChance)))
    {
        return;
    }
    if (pokemonInfo->bideClassStatus.status != STATUS_NONE)
    {
        for (i = 0; i < MAX_MON_MOVES; i++)
        {
            if (pokemonInfo->moves.moves[i].moveFlags & MOVE_FLAG_EXISTS &&
                MoveMatchesBideClassStatus(pokemon, &pokemonInfo->moves.moves[i]) &&
                pokemonInfo->bideClassStatus.moveSlot == i)
            {
                s32 chosenMoveIndex;
                SetMonsterActionFields(&pokemonInfo->action, ACTION_USE_MOVE_AI);
                chosenMoveIndex = i;
                if (i > 0 && pokemonInfo->moves.moves[i].moveFlags & MOVE_FLAG_SUBSEQUENT_IN_LINK_CHAIN)
                {
                    while (--chosenMoveIndex > 0)
                    {
                        if (!(pokemonInfo->moves.moves[chosenMoveIndex].moveFlags & MOVE_FLAG_SUBSEQUENT_IN_LINK_CHAIN))
                        {
                            break;
                        }
                    }
                }
                pokemonInfo->action.unk4[0].actionUseIndex = chosenMoveIndex;
                TargetTileInFront(pokemon);
                return;
            }
        }
    }
    total = 0;
    numUsableMoves = 0;
    for (i = 0; i < MAX_MON_MOVES; i++)
    {
        Move *move = &pokemonInfo->moves.moves[i];
        if (pokemonInfo->moves.moves[i].moveFlags & MOVE_FLAG_EXISTS)
        {
            if (pokemonInfo->moves.moves[i].moveFlags & MOVE_FLAG_ENABLED_FOR_AI)
            {
                numUsableMoves++;
            }
            total += move->PP;
        }
    }
    if (total == 0)
    {
        Move struggle;
        InitPokemonMove(&struggle, MOVE_STRUGGLE);
        AIConsiderMove(&aiPossibleMove[0], pokemon, &struggle);
        if (aiPossibleMove[0].canBeUsed)
        {
            SetMonsterActionFields(&pokemonInfo->action, ACTION_STRUGGLE);
            pokemonInfo->action.direction = aiPossibleMove[0].direction & DIRECTION_MASK;
            TargetTileInFront(pokemon);
        }
        return;
    }
    hasWeakTypePicker = IQSkillIsEnabled(pokemon, IQ_WEAK_TYPE_PICKER);
    hasPPChecker = IQSkillIsEnabled(pokemon, IQ_PP_CHECKER) != FALSE;
    total = 0;
    for (i = 0; i < MAX_MON_MOVES; i++)
    {
        willNotUnlinkMove[i] = TRUE;
    }
    if (hasPPChecker)
    {
        s32 minPP = 99;
        s32 linkedMoveStartIndex = 0;
        // Linked moves unlink if any move in the chain runs out of PP.
        // With PP Checker, the AI avoids this by not using linked moves if any move in the chain has 1 PP left.
        // This requires a separate check from the 0-PP check used for unlinked moves.
        for (i = 0; i < MAX_MON_MOVES; i++)
        {
            Move *move = &pokemonInfo->moves.moves[i];
            if (!(move->moveFlags & MOVE_FLAG_EXISTS))
            {
                break;
            }
            if (i != 0 && !(move->moveFlags & MOVE_FLAG_SUBSEQUENT_IN_LINK_CHAIN))
            {
                if (linkedMoveStartIndex + 1 < i && minPP <= 1 && linkedMoveStartIndex + 1 <= i)
                {
                    s32 j;
                    for (j = linkedMoveStartIndex; j < i; j++)
                    {
                        willNotUnlinkMove[j] = FALSE;
                    }
                }
                minPP = move->PP;
                linkedMoveStartIndex = i;
            }
            else
            {
                s32 newMinPP = move->PP;
                if (newMinPP > minPP)
                {
                    newMinPP = minPP;
                }
                minPP = newMinPP;
            }
        }
        if (linkedMoveStartIndex + 1 < i && minPP <= 1 && linkedMoveStartIndex + 1 <= i)
        {
            s32 j;
            for (j = linkedMoveStartIndex; j < i; j++)
            {
                willNotUnlinkMove[j] = FALSE;
            }
        }
    }
    for (i = 0; i < MAX_MON_MOVES; i++)
    {
        Move *move;
        aiPossibleMove[i].canBeUsed = FALSE;
        move = &pokemonInfo->moves.moves[i];
        if (move->moveFlags & MOVE_FLAG_EXISTS &&
            willNotUnlinkMove[i] &&
            CanAIUseMove(pokemon, i, hasPPChecker) &&
            move->moveFlags & MOVE_FLAG_ENABLED_FOR_AI)
        {
            aiPossibleMove[i].canBeUsed = TRUE;
            if (pokemonInfo->bideClassStatus.status == chargeStatus)
            {
                if (move->id == MOVE_CHARGE)
                {
                    aiPossibleMove[i].weight = 0;
                    continue;
                }
                else if (GetMoveTypeForMonster(pokemon, move) == TYPE_ELECTRIC)
                {
                    aiPossibleMove[i].weight = GetMoveAIWeight(move);
                }
                else
                {
                    aiPossibleMove[i].weight = 1;
                }
            }
            else if (hasWeakTypePicker)
            {
                struct AIPossibleMove *results = &aiPossibleMove[i];
                results->weight = AIConsiderMove(results, pokemon, move);
            }
            else
            {
                aiPossibleMove[i].weight = GetMoveAIWeight(move);
            }
            total += aiPossibleMove[i].weight;
        }
    }
    aiPossibleMove[REGULAR_ATTACK_INDEX].weight = 0;
    if (!IQSkillIsEnabled(pokemon, IQ_EXCLUSIVE_MOVE_USER) && pokemonInfo->bideClassStatus.status != chargeStatus)
    {
        aiPossibleMove[REGULAR_ATTACK_INDEX].canBeUsed = TRUE;
        if (pokemonInfo->bideClassStatus.status == chargeStatus)
        {
            // Never reached? Charge already skips the regular attack in the outer block.
            aiPossibleMove[REGULAR_ATTACK_INDEX].weight = 1;
        }
        else if (hasWeakTypePicker)
        {
            aiPossibleMove[REGULAR_ATTACK_INDEX].weight = 2;
        }
        else
        {
            aiPossibleMove[REGULAR_ATTACK_INDEX].weight = gRegularAttackWeights[numUsableMoves];
        }
        total += aiPossibleMove[REGULAR_ATTACK_INDEX].weight;
    }
    if (hasWeakTypePicker)
    {
        s32 i;
        maxWeight = 0;
        total = 0;
        for (i = 0; i < MAX_MON_MOVES + 1; i++)
        {
            if (!aiPossibleMove[i].canBeUsed)
            {
                aiPossibleMove[i].weight = 0;
            }
            else if (maxWeight < aiPossibleMove[i].weight)
            {
                maxWeight = aiPossibleMove[i].weight;
            }
        }
        for (i = 0; i < MAX_MON_MOVES + 1; i++)
        {
            if (aiPossibleMove[i].canBeUsed)
            {
                if (maxWeight != aiPossibleMove[i].weight)
                {
                    // Only the move(s) with the highest weight can be used, instead of a weighted random.
                    // This has the side effect of making the AI use a STAB ranged move consistently when at a distance.
                    aiPossibleMove[i].weight = 0;
                }
                total += aiPossibleMove[i].weight;
            }
        }
    }
    if (total == 0)
    {
        return;
    }
    randomWeight = DungeonRandInt(total);
    weightCounter = 0;
    if (!IQSkillIsEnabled(pokemon, IQ_EXCLUSIVE_MOVE_USER))
    {
        canTargetRegularAttack = TargetRegularAttack(pokemon, &regularAttackTargetDir, TRUE);
    }
    else
    {
        canTargetRegularAttack = FALSE;
        regularAttackTargetDir = DIRECTION_SOUTH;
    }
    for (i = 0; i <= REGULAR_ATTACK_INDEX; i++)
    {
        if (aiPossibleMove[i].canBeUsed && aiPossibleMove[i].weight != 0)
        {
            weightCounter += aiPossibleMove[i].weight;
            if (weightCounter >= randomWeight)
            {
                if (i == REGULAR_ATTACK_INDEX)
                {
                    if (canTargetRegularAttack)
                    {
                        SetMonsterActionFields(&pokemonInfo->action, ACTION_REGULAR_ATTACK);
                        pokemonInfo->action.direction = regularAttackTargetDir & DIRECTION_MASK;
                        TargetTileInFront(pokemon);
                    }
                }
                else
                {
                    AIConsiderMove(&aiPossibleMove[i], pokemon, &pokemonInfo->moves.moves[i]);
                    if (aiPossibleMove[i].canBeUsed)
                    {
                        s32 chosenMoveIndex;
                        SetMonsterActionFields(&pokemonInfo->action, ACTION_USE_MOVE_AI);
                        chosenMoveIndex = i;
                        if (i > 0 && pokemonInfo->moves.moves[i].moveFlags & MOVE_FLAG_SUBSEQUENT_IN_LINK_CHAIN)
                        {
                            while (--chosenMoveIndex > 0)
                            {
                                if (!(pokemonInfo->moves.moves[chosenMoveIndex].moveFlags & MOVE_FLAG_SUBSEQUENT_IN_LINK_CHAIN))
                                {
                                    break;
                                }
                            }
                        }
                        pokemonInfo->action.direction = aiPossibleMove[i].direction & DIRECTION_MASK;
                        pokemonInfo->action.unk4[0].actionUseIndex = chosenMoveIndex;
                        TargetTileInFront(pokemon);
                    }
                    else
                    {
                        break;
                    }
                }
                return;
            }
        }
    }
    if (canTargetRegularAttack)
    {
        SetMonsterActionFields(&pokemonInfo->action, ACTION_REGULAR_ATTACK);
        pokemonInfo->action.direction = regularAttackTargetDir & DIRECTION_MASK;
        TargetTileInFront(pokemon);
    }
}

s32 AIConsiderMove(struct AIPossibleMove *aiPossibleMove, Entity *pokemon, Move *move)
{
    s32 targetingFlags;
    s32 moveWeight = 1;
    EntityInfo *pokemonInfo = GetEntInfo(pokemon);
    s32 numPotentialTargets = 0;
    s32 i;
    bool8 hasStatusChecker;
    s32 rangeTargetingFlags;
    s32 rangeTargetingFlags2;
    for (i = 0; i < NUM_DIRECTIONS; i++)
    {
        gCanAttackInDirection[i] = FALSE;
    }
    targetingFlags = GetMoveTargetAndRangeForPokemon(pokemon, move, TRUE);
    hasStatusChecker = IQSkillIsEnabled(pokemon, IQ_STATUS_CHECKER);
    aiPossibleMove->canBeUsed = FALSE;
    if ((pokemonInfo->cringeClassStatus.status == STATUS_TAUNTED && !MoveIgnoresTaunted(move)) ||
        (hasStatusChecker && !CanUseOnSelfWithStatusChecker(pokemon, move)))
    {
        return 1;
    }
    rangeTargetingFlags = targetingFlags & 0xF0;
    if (rangeTargetingFlags == TARGETING_FLAG_TARGET_OTHER ||
        rangeTargetingFlags == TARGETING_FLAG_TARGET_FRONTAL_CONE ||
        rangeTargetingFlags == TARGETING_FLAG_TARGET_AROUND)
    {
        if (pokemonInfo->blinkerClassStatus.status == STATUS_BLINKER)
        {
            u8 direction = pokemonInfo->action.direction;
            i = direction; // Fixes a regswap.
            if (!gCanAttackInDirection[i])
            {
                gCanAttackInDirection[i] = TRUE;
                gPotentialAttackTargetDirections[numPotentialTargets] = i;
                gPotentialAttackTargetWeights[numPotentialTargets] = 99;
                gPotentialTargets[numPotentialTargets] = NULL;
                numPotentialTargets++;
            }
        }
        else
        {
            for (i = 0; i < NUM_DIRECTIONS; i++)
            {
                // Double assignment to fix a regswap.
                s16 rangeTargetingFlags = rangeTargetingFlags2 = targetingFlags & 0xF0;
                const Tile *adjacentTile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[i].x,
                    pokemon->pos.y + gAdjacentTileOffsets[i].y);
                Entity *adjacentPokemon = adjacentTile->monster;
                if (adjacentPokemon != NULL && GetEntityType(adjacentPokemon) == ENTITY_MONSTER)
                {
                    if (rangeTargetingFlags != TARGETING_FLAG_TARGET_FRONTAL_CONE &&
                        rangeTargetingFlags != TARGETING_FLAG_TARGET_AROUND)
                    {
                        if (!CanAttackInDirection(pokemon, i))
                        {
                            continue;
                        }
                    }
                    numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, adjacentPokemon, move, hasStatusChecker);
                }
            }
        }
    }
    else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_ROOM)
    {
        s32 i;
        for (i = 0; i < DUNGEON_MAX_POKEMON; i++)
        {
            Entity *target = gDungeon->activePokemon[i];
            if (EntityExists(target) && CanSeeTarget(pokemon, target))
            {
                numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, target, move, hasStatusChecker);
            }
        }
    }
    else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_2_TILES_AHEAD)
    {
        for (i = 0; i < NUM_DIRECTIONS; i++)
        {
            const Tile *targetTile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[i].x,
                pokemon->pos.y + gAdjacentTileOffsets[i].y);
            if (CanAttackInDirection(pokemon, i))
            {
                Entity *targetPokemon = targetTile->monster;
                if (targetPokemon != NULL && GetEntityType(targetPokemon) == ENTITY_MONSTER)
                {
                    s32 prevNumPotentialTargets = numPotentialTargets;
                    numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, targetPokemon, move, hasStatusChecker);
                    if (prevNumPotentialTargets != numPotentialTargets)
                    {
                        continue;
                    }
                }
                targetTile = GetTile(pokemon->pos.x + gAdjacentTileOffsets[i].x * 2,
                    pokemon->pos.y + gAdjacentTileOffsets[i].y * 2);
                targetPokemon = targetTile->monster;
                if (targetPokemon != NULL && GetEntityType(targetPokemon) == ENTITY_MONSTER)
                {
                    numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, targetPokemon, move, hasStatusChecker);
                }
            }
        }
    }
    else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_LINE || rangeTargetingFlags == TARGETING_FLAG_CUT_CORNERS)
    {
        s32 maxRange = 1;
        s32 i;
        if (rangeTargetingFlags == TARGETING_FLAG_TARGET_LINE)
        {
            maxRange = 10;
        }
        for (i = 0; i < DUNGEON_MAX_POKEMON; i++)
        {
            Entity *target = gDungeon->activePokemon[i];
            if (EntityExists(target) && pokemon != target)
            {
                s32 direction = GetDirectionTowardsPosition(&pokemon->pos, &target->pos);
                if (!gCanAttackInDirection[direction] &&
                    CanSeeTarget(pokemon, target) &&
                    IsTargetInLineRange(pokemon, target, maxRange) &&
                    IsAITargetEligible(targetingFlags, pokemon, target, move, hasStatusChecker) &&
                    IsTargetInRange(pokemon, target, direction, maxRange))
                {
                    gCanAttackInDirection[direction] = TRUE;
                    gPotentialAttackTargetDirections[numPotentialTargets] = direction;
                    gPotentialAttackTargetWeights[numPotentialTargets] = WeightMove(pokemon, targetingFlags, target, GetMoveTypeForMonster(pokemon, move));
                    gPotentialTargets[numPotentialTargets] = target;
                    numPotentialTargets++;
                }
            }
        }
    }
    else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_FLOOR)
    {
        s32 i;
        for (i = 0; i < DUNGEON_MAX_POKEMON; i++)
        {
            Entity *target = gDungeon->activePokemon[i];
            if (EntityExists(target))
            {
                numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, target, move, hasStatusChecker);
            }
        }
    }
    else if (rangeTargetingFlags == TARGETING_FLAG_TARGET_SELF)
    {
        numPotentialTargets = TryAddTargetToAITargetList(numPotentialTargets, targetingFlags, pokemon, pokemon, move, hasStatusChecker);
    }
    if (numPotentialTargets == 0)
    {
        aiPossibleMove->canBeUsed = FALSE;
    }
    else
    {
        s32 totalWeight = 0;
        s32 maxWeight = 0;
        s32 weightCounter;
        s32 i;
        for (i = 0; i < numPotentialTargets; i++)
        {
            if (maxWeight < gPotentialAttackTargetWeights[i])
            {
                maxWeight = gPotentialAttackTargetWeights[i];
            }
        }
        for (i = 0; i < numPotentialTargets; i++)
        {
            if (maxWeight != gPotentialAttackTargetWeights[i])
            {
                gPotentialAttackTargetWeights[i] = 0;
            }
        }
        moveWeight = maxWeight;
        for (i = 0; i < numPotentialTargets; i++)
        {
            totalWeight += gPotentialAttackTargetWeights[i];
        }
        weightCounter = DungeonRandInt(totalWeight);
        for (i = 0; i < numPotentialTargets; i++)
        {
            weightCounter -= gPotentialAttackTargetWeights[i];
            if (weightCounter < 0)
            {
                break;
            }
        }
        aiPossibleMove->canBeUsed = TRUE;
        aiPossibleMove->direction = gPotentialAttackTargetDirections[i];
        aiPossibleMove->weight = 8;
    }
    return moveWeight;
}

bool8 IsTargetInLineRange(Entity *user, Entity *target, s32 range)
{
    s32 distanceX = user->pos.x - target->pos.x;
    s32 distanceY, distance;
    s32 direction;
    if (distanceX < 0)
    {
        distanceX = -distanceX;
    }
    distanceY = user->pos.y - target->pos.y;
    if (distanceY < 0)
    {
        distanceY = -distanceY;
    }
    distance = distanceY;
    if (distanceY < distanceX)
    {
        distance = distanceX;
    }
    if (distance > RANGED_ATTACK_RANGE || distance > range)
    {
        return FALSE;
    }
    direction = -1;
    if (distanceX == distanceY)
    {
        if (user->pos.x < target->pos.x &&
            (user->pos.y < target->pos.y || user->pos.y > target->pos.y))
        {
            returnTrue:
            return TRUE;
        }
        if (user->pos.x > target->pos.x); // Fixes register loading order.
        direction = DIRECTION_SOUTHWEST;
        if (user->pos.x <= target->pos.x || user->pos.y <= target->pos.y)
        {
            goto checkDirectionSet;
        }
        goto returnTrue;
    }
    else if (user->pos.x == target->pos.x && user->pos.y < target->pos.y)
    {
        return TRUE;
    }
    else if (user->pos.x < target->pos.x && user->pos.y == target->pos.y)
    {
        return TRUE;
    }
    else if (user->pos.x == target->pos.x && user->pos.y > target->pos.y)
    {
        return TRUE;
    }
    else if (user->pos.x > target->pos.x && user->pos.y == target->pos.y)
    {
        direction = DIRECTION_WEST;
    }
    checkDirectionSet:
    if (direction < 0)
    {
        return FALSE;
    }
    return TRUE;
}

s32 TryAddTargetToAITargetList(s32 numPotentialTargets, s32 targetingFlags, Entity *user, Entity *target, Move *move, bool32 hasStatusChecker)
{
    s32 direction;
    s32 targetingFlags2 = (s16) targetingFlags;
    bool8 hasStatusChecker2 = hasStatusChecker;
    EntityInfo *userData = GetEntInfo(user);
    if ((user->pos.x == target->pos.x && user->pos.y == target->pos.y) ||
        (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_ROOM ||
        (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_FLOOR ||
        (targetingFlags2 & 0xF0) == TARGETING_FLAG_TARGET_SELF)
    {
        direction = userData->action.direction;
    }
    else
    {
        direction = GetDirectionTowardsPosition(&user->pos, &target->pos);
    }
    if (!gCanAttackInDirection[direction] &&
        IsAITargetEligible(targetingFlags2, user, target, move, hasStatusChecker2))
    {
        gCanAttackInDirection[direction] = TRUE;
        do { gPotentialAttackTargetDirections[numPotentialTargets] = direction; } while (0);
        gPotentialAttackTargetWeights[numPotentialTargets] = WeightMove(user, targetingFlags2, target, GetMoveTypeForMonster(user, move));
        gPotentialTargets[numPotentialTargets] = target;
        numPotentialTargets++;
    }
    return numPotentialTargets;
}

bool8 IsAITargetEligible(s32 targetingFlags, Entity *user, Entity *target, Move *move, bool32 hasStatusChecker)
{
    EntityInfo *targetData;
    s32 targetingFlags2 = (s16) targetingFlags;
    bool8 hasStatusChecker2 = hasStatusChecker;
    bool8 hasTarget = FALSE;
    u32 categoryTargetingFlags = targetingFlags2 & 0xF;
    u32 *categoryTargetingFlags2 = &categoryTargetingFlags; // Fixes a regswap.
    if (*categoryTargetingFlags2 == TARGETING_FLAG_TARGET_OTHER)
    {
        if (GetTreatmentBetweenMonsters(user, target, FALSE, TRUE) == TREATMENT_TREAT_AS_ENEMY)
        {
            hasTarget = TRUE;
        }
    }
    else if (categoryTargetingFlags == TARGETING_FLAG_HEAL_TEAM)
    {
        goto checkCanTarget;
    }
    else if (categoryTargetingFlags == TARGETING_FLAG_LONG_RANGE)
    {
        targetData = GetEntInfo(target);
        goto checkThirdParty;
    }
    else if (categoryTargetingFlags == TARGETING_FLAG_ATTACK_ALL)
    {
        targetData = GetEntInfo(target);
        if (user == target)
        {
            goto returnFalse;
        }
        checkThirdParty:
        hasTarget = TRUE;
        if (targetData->shopkeeper == SHOPKEEPER_MODE_SHOPKEEPER ||
            targetData->monsterBehavior == BEHAVIOR_DIGLETT ||
            targetData->monsterBehavior == BEHAVIOR_RESCUE_TARGET)
        {
            returnFalse:
            return FALSE;
        }
    }
    else if (categoryTargetingFlags == TARGETING_FLAG_BOOST_TEAM)
    {
        if (user == target)
        {
            goto returnFalse;
        }
        checkCanTarget:
        if (GetTreatmentBetweenMonsters(user, target, FALSE, TRUE) == TREATMENT_TREAT_AS_ALLY)
        {
            hasTarget = TRUE;
        }
    }
    else if ((u16) (categoryTargetingFlags - 3) <= 1) // categoryTargetingFlags == TARGETING_FLAG_ITEM
    {
        hasTarget = TRUE;
    }

    if (hasTarget)
    {
        if (hasStatusChecker2)
        {
            if (!CanUseOnTargetWithStatusChecker(user, target, move))
            {
                goto returnFalse;
            }
            if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_SET_TRAP)
            {
                goto rollMoveUseChance;
            }
            else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_HP)
            {
                if (!HasLowHealth(target))
                {
                    if (*categoryTargetingFlags2);
                    goto returnFalse;
                }
            }
            else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_STATUS)
            {
                if (!HasNegativeStatus(target))
                {
                    if (*categoryTargetingFlags2); // Flips the conditional.
                    goto returnFalse;
                }
            }
            else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_DREAM_EATER)
            {
                if (!IsSleeping(target))
                {
                    if (*categoryTargetingFlags2); // Flips the conditional.
                    goto returnFalse;
                }
            }
            else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_EXPOSE)
            {
                targetData = GetEntInfo(target);
                if ((targetData->types[0] != TYPE_GHOST && targetData->types[1] != TYPE_GHOST) || targetData->exposed)
                {
                    if (*categoryTargetingFlags2); // Flips the conditional.
                    goto returnFalse;
                }
            }
            else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_HEAL_ALL)
            {
                if (!HasNegativeStatus(target) && !HasLowHealth(target))
                {
                    if (*categoryTargetingFlags2); // Flips the conditional.
                    goto returnFalse;
                }
            }
        }
        else if ((targetingFlags2 & 0xF00) == TARGETING_FLAG_SET_TRAP)
        {
            s32 useChance;
            rollMoveUseChance:
            useChance = GetMoveAccuracyOrAIChance(move, ACCURACY_AI_CONDITION_RANDOM_CHANCE);
            if (DungeonRandInt(100) >= useChance)
            {
                goto returnFalse;
            }
        }
    }
    return hasTarget;
}

s32 WeightMove(Entity *user, s32 targetingFlags, Entity *target, u32 moveType)
{
    EntityInfo *targetData;
    s32 targetingFlags2 = (s16) targetingFlags;
    u8 moveType2 = moveType;
    u8 weight = 1;
    targetData = GetEntInfo(target);
    if (!targetData->isNotTeamMember)
        return 1;
    if ((targetingFlags2 & 0xF) != TARGETING_FLAG_TARGET_OTHER)
        return 1;

    if (IQSkillIsEnabled(user, IQ_EXP_GO_GETTER))
    {
        // BUG: expYieldRankings has lower values as the Pokémon's experience yield increases.
        // This causes Exp. Go-Getter to prioritize Pokémon worth less experience
        // instead of Pokémon worth more experience.
        weight = gDungeon->expYieldRankings[targetData->id];
    }
    else if (IQSkillIsEnabled(user, IQ_EFFICIENCY_EXPERT))
    {
        weight = -12 - targetData->HP;
        if (weight == 0)
        {
            weight = 1;
        }
    }
    else if (IQSkillIsEnabled(user, IQ_WEAK_TYPE_PICKER))
    {
       weight = WeightWeakTypePicker(user, target, moveType2) + 1;
    }
    return weight;
}

bool8 TargetRegularAttack(Entity *pokemon, u32 *targetDir, bool8 checkPetrified)
{
    EntityInfo *pokemonInfo = GetEntInfo(pokemon);
    s32 numPotentialTargets = 0;
    s32 direction = pokemonInfo->action.direction;
    s32 faceTurnLimit = pokemonInfo->blinkerClassStatus.status == STATUS_BLINKER ? 1 : 8;
    s32 i;
    s32 potentialAttackTargetDirections[NUM_DIRECTIONS];
    s32 potentialAttackTargetWeights[NUM_DIRECTIONS];
    bool8 hasTargetingIQ = IQSkillIsEnabled(pokemon, IQ_EXP_GO_GETTER) || IQSkillIsEnabled(pokemon, IQ_EFFICIENCY_EXPERT);
    bool8 hasStatusChecker = IQSkillIsEnabled(pokemon, IQ_STATUS_CHECKER);
    for (i = 0; i < faceTurnLimit; i++, direction++)
    {
        Entity *target;
        direction &= DIRECTION_MASK;
        target = GetTile(pokemon->pos.x + gAdjacentTileOffsets[direction].x,
            pokemon->pos.y + gAdjacentTileOffsets[direction].y)->monster;
        if (target != NULL &&
            GetEntityType(target) == ENTITY_MONSTER &&
            CanAttackInDirection(pokemon, direction) &&
            GetTreatmentBetweenMonsters(pokemon, target, FALSE, checkPetrified) == TREATMENT_TREAT_AS_ENEMY &&
            (!hasStatusChecker || GetEntInfo(target)->frozenClassStatus.status != STATUS_FROZEN))
        {
            potentialAttackTargetDirections[numPotentialTargets] = direction;
            potentialAttackTargetWeights[numPotentialTargets] = WeightMove(pokemon, TARGETING_FLAG_TARGET_OTHER, target, TYPE_NONE);
            if (!hasTargetingIQ)
            {
                *targetDir = direction;
                return TRUE;
            }
            numPotentialTargets++;
        }
    }
    if (numPotentialTargets == 0)
    {
        return FALSE;
    }
    else
    {
        s32 totalWeight = 0;
        s32 maxWeight = 0;
        s32 weightCounter;
        s32 i;
        for (i = 0; i < numPotentialTargets; i++)
        {
            if (maxWeight < potentialAttackTargetWeights[i])
            {
                maxWeight = potentialAttackTargetWeights[i];
            }
        }
        for (i = 0; i < numPotentialTargets; i++)
        {
            if (maxWeight != potentialAttackTargetWeights[i])
            {
                potentialAttackTargetWeights[i] = 0;
            }
        }
        for (i = 0; i < numPotentialTargets; i++)
        {
            totalWeight += potentialAttackTargetWeights[i];
        }
        weightCounter = DungeonRandInt(totalWeight);
        for (i = 0; i < numPotentialTargets; i++)
        {
            weightCounter -= potentialAttackTargetWeights[i];
            if (weightCounter < 0)
            {
                break;
            }
        }
        *targetDir = potentialAttackTargetDirections[i];
        return TRUE;
    }

}

bool8 IsTargetInRange(Entity *pokemon, Entity *targetPokemon, s32 direction, s32 maxRange)
{
    s32 distanceX = pokemon->pos.x - targetPokemon->pos.x;
    s32 effectiveMaxRange;
    if (distanceX < 0)
    {
        distanceX = -distanceX;
    }
    effectiveMaxRange = pokemon->pos.y - targetPokemon->pos.y;
    if (effectiveMaxRange < 0)
    {
        effectiveMaxRange = -effectiveMaxRange;
    }
    if (effectiveMaxRange < distanceX)
    {
        effectiveMaxRange = distanceX;
    }
    if (effectiveMaxRange > maxRange)
    {
        effectiveMaxRange = maxRange;
    }
    if (!IQSkillIsEnabled(pokemon, IQ_COURSE_CHECKER))
    {
        // BUG: effectiveMaxRange is already capped at maxRange, so this condition always evaluates to TRUE.
        // The AI also has range checks elsewhere, so this doesn't become an issue in most cases.
        // If the AI has the Long Toss or Pierce statuses and Course Checker is disabled,
        // this incorrect check causes the AI to throw items at targets further than 10 tiles away.
        if (effectiveMaxRange <= maxRange)
        {
            return TRUE;
        }
    }
    else
    {
        s32 currentPosX = pokemon->pos.x;
        s32 currentPosY = pokemon->pos.y;
        s32 adjacentTileOffsetX = gAdjacentTileOffsets[direction].x;
        s32 adjacentTileOffsetY = gAdjacentTileOffsets[direction].y;
        s32 i;
        for (i = 0; i <= effectiveMaxRange; i++)
        {
            const Tile *mapTile;
            currentPosX += adjacentTileOffsetX;
            currentPosY += adjacentTileOffsetY;
            if (currentPosX <= 0 || currentPosY <= 0 ||
                currentPosX >= DUNGEON_MAX_SIZE_X - 1 || currentPosY >= DUNGEON_MAX_SIZE_Y - 1)
            {
                break;
            }
            while (0); // Extra label needed to swap branch locations in ASM.
            mapTile = GetTile(currentPosX, currentPosY);
            if (!(mapTile->terrainType & (TERRAIN_TYPE_NORMAL | TERRAIN_TYPE_SECONDARY)))
            {
                break;
            }
            if (mapTile->monster == targetPokemon)
            {
                return TRUE;
            }
            if (mapTile->monster != NULL)
            {
                break;
            }
        }
    }
    return FALSE;
}

void HandleUseMoveAIAction(Entity *target)
{
    EntityInfo *entityInfo;
    s32 counter;

    counter = 0;
    while (TRUE) {
        if (counter >= sub_8070828(target, TRUE))
            break;
        entityInfo = GetEntInfo(target);
        sub_8055A00(target, entityInfo->action.unk4[0].actionUseIndex, 1, 0, 0);
        if (!EntityExists(target))
            break;
        if (sub_8044B28())
            break;
        if (entityInfo->unk159)
            break;
        counter++;
    }

    sub_8057588(target, 1);
    if (EntityExists(target))
        sub_806A9B4(target, GetEntInfo(target)->action.unk4[0].actionUseIndex);

    sub_806A1B0(target);
}

void HandleUseOrbAction(Entity *pokemon)
{
    bool8 r4;
    Item *item;
    Item IVar5;
    EntityInfo *entityInfo; // r7
    ActionContainer act;
    Move move;
    struct AIPossibleMove sp28;
    bool8 r8;

    entityInfo = GetEntInfo(pokemon);
    item = sub_8044D90(pokemon, 0, 21);
    IVar5 = *item;

    if (item->flags & ITEM_FLAG_STICKY) {
        sub_8045BF8(gFormatBuffer_Items[0], item);
        LogMessageByIdWithPopupCheckUser(pokemon, *gItemStickyDoesntWorkText);
        return;
    }

    act = entityInfo->action;

    if (IsBossFight()) {
        LogMessageByIdWithPopupCheckUser(pokemon, *gPtrMysteriousPowerPreventedUseMessage);
        r4 = TRUE;
    }
    else {
        r8 = TRUE;
        InitPokemonMove(&move, GetItemMoveID(IVar5.id));

        if (!entityInfo->isTeamLeader) {
            sp28.canBeUsed = r8;
            sp28.weight = 10;
            AIConsiderMove(&sp28, pokemon, entityInfo->moves.moves);

            if (sp28.canBeUsed) {
                entityInfo->action.direction = sp28.direction & 7;
                TargetTileInFront(pokemon);
            }
        }

        if (entityInfo->cringeClassStatus.status == 1) {
            SubstitutePlaceholderStringTags(gFormatBuffer_Monsters[0], pokemon, 0);
            LogMessageByIdWithPopupCheckUser(pokemon, *gUnknown_80FC714);
            r4 = FALSE;
            r8 = FALSE;
        }
        else if (entityInfo->cringeClassStatus.status == 7) {
            SubstitutePlaceholderStringTags(gFormatBuffer_Monsters[0], pokemon, 0);
            LogMessageByIdWithPopupCheckUser(pokemon, *gUnknown_80FC718);
            r4 = FALSE;
            r8 = FALSE;
        }
        else if (entityInfo->burnClassStatus.status == 4)
        {
            SubstitutePlaceholderStringTags(gFormatBuffer_Monsters[0], pokemon, 0);
            LogMessageByIdWithPopupCheckUser(pokemon, *gUnknown_80FC6A8);
            r4 = FALSE;
            r8 = FALSE;
        }

        if (r8) {
            if ((IVar5.id == ITEM_SWITCHER_ORB) || (IVar5.id == ITEM_POUNCE_ORB)) {
                sub_8044DF0(pokemon, 0, 122);
                move.moveFlags |= MOVE_FLAG_SET;
                move.moveFlags |= MOVE_FLAG_ENABLED_FOR_AI;
                move.PP = 10;
                TryUseChosenMove(pokemon, 0, IVar5.id, 0, 0, &move);
                r4 = FALSE;
            }
            else {
                r4 = TryUseChosenMove(pokemon, 0, IVar5.id, 0, 0, &move);
            }
        }
        else {
            r4 = FALSE;
        }

    }

    if (EntityExists(pokemon)) {
        if (r4)
            sub_8044DF0(pokemon, 0, 122);

        sub_8071DA4(pokemon);
        sub_806CE68(pokemon, 8);

        if (entityInfo->isTeamLeader) {
            sub_804AC20(&pokemon->pos);
            sub_807EC28(FALSE);
        }

        sub_806A5B8(pokemon);
        sub_8075900(pokemon, gDungeon->unk3A08);
    }
    else if (r4)
        sub_8044D40(&act, 0);

    if (!sub_8044B28())
        sub_806A1B0(pokemon);
}
