My AI in my processing game that I want to chase the player across the map, such that they shoud be able to navigate obstacles in the way; technically works, however theres a weird behaviour of the enemy ai that makes them 'twitch' / backtrack their path at seemingly the same time intervals,  could you please diagnose and suggest changes to the code in order to fix this behaviour. It is completely fine to change the code structure if needed

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;

enum AnimationState {
    IDLE, WALKING, ATTACKING, DAMAGED, DYING
}

class Enemy {

    float x, y;
    float speed = 1.5;
    int health;
    int maxHealth = 100;


    float respawnTime;
    float respawnTimer;

    List<PVector> path;
    int pathIndex;
    int updatePathInterval = 60; // Update path every 60 frames
    int frameCount;

    boolean active;
    AnimationState currentState = AnimationState.IDLE;
    HashMap<AnimationState, Animation> animations = new HashMap<>();
    Animation currentAnim;
    int stateTimer = 0;
    int damagedDuration = 30; 
    int dyingDuration = 60; 
    int attackingDuration = 55; //Duration for attacking state in frames (11 frames * 5 frame delay)
    boolean hasDealtDamage = false; //to ensure damage is dealt only once per attack
    

    PImage spriteSheet;
    int spriteWidth = 90;
    int spriteHeight = 64;
    Player player; // Reference to the player

    Enemy(float respawnTime, Player playerRef) {
        this.respawnTime = respawnTime;
        this.respawnTimer = respawnTime;
        this.health = maxHealth;
        this.player = playerRef;
        this.spriteSheet = random(1) < 0.5 ? enemySpriteSheet1 : enemySpriteSheet2;

        animations.put(AnimationState.ATTACKING, new Animation(0, 11, 5, false));
        animations.put(AnimationState.DYING, new Animation(1, 13, 5, false));
        animations.put(AnimationState.DAMAGED, new Animation(2, 4, 5, false));
        animations.put(AnimationState.IDLE, new Animation(3, 8, 5, true));
        animations.put(AnimationState.WALKING, new Animation(4, 10, 5, true));

        // Set the initial animation
        currentAnim = animations.get(currentState);
        currentAnim.reset();
        respawn(); //this takes care of initital position
    }


void update(Tile[][] map ) {

    if (!active) {//downtime after being killed
        respawnTimer -= 1.0 / frameRate;
        if (respawnTimer <= 0) {
                active = true;
                respawn();
        }
        return;
    }
            switch (currentState) {
            case DAMAGED:
            case DYING:
            case ATTACKING:
                handleState(map);
                break;
            default:
                handleMovement(map);
                break;
        }

        currentAnim.update();   
    }

    void handleState(Tile[][] map) {
        stateTimer--;
        if (stateTimer <= 0) {
            switch (currentState) {
                case DYING:
                    active = false;
                    respawnTimer = respawnTime;
                    break;
                case ATTACKING:
                    setState(AnimationState.IDLE);
                    break;
                case DAMAGED:
                    setState(AnimationState.WALKING);
                    break;
            }
        }

        // Specific actions for states
        if (currentState == AnimationState.ATTACKING && !hasDealtDamage) {
            checkPlayerDamage();
        }
    }

void handleMovement(Tile[][] map) {
        // Increment frame count and update path if needed
        frameCount++;
        if (frameCount >= updatePathInterval) {
            updatePath(map);
            frameCount = 0;
        }

        // Check distance to player
        float distanceToPlayer = dist(x, y, player.x, player.y);
        if (distanceToPlayer < TILE_SIZE * 1.5f) { // Attack range
            attack();
            return;
        }

        // Move along the path if it exists
        if (path != null && !path.isEmpty() && pathIndex < path.size()) {
            PVector target = path.get(pathIndex);
            float dx = target.x - x;
            float dy = target.y - y;
            float distance = sqrt(dx * dx + dy * dy);

            if (distance < speed) {
                // Reached the current target node
                pathIndex++;
                if (pathIndex >= path.size()) {
                    path = null;
                    setState(AnimationState.IDLE);
                }
            } else {
                // Move towards the target node
                float ratio = speed / distance;
                float newX = x + dx * ratio;
                float newY = y + dy * ratio;

                // Check for collision before moving
                if (!isCollision(newX, newY, map)) {
                    x = newX;
                    y = newY;
                    setState(AnimationState.WALKING);
                } else {
                    // Collision detected, recalculate path
                    updatePath(map);
                }
            }
        } else {
            setState(AnimationState.IDLE);
        }
    }

void setState(AnimationState newState) {
        if (currentState != newState) {
            currentState = newState;
            currentAnim = animations.get(currentState);
            currentAnim.reset();

            // Set state timers based on the new state
            switch (newState) {
                case DAMAGED:
                    stateTimer = damagedDuration;
                    break;
                case DYING:
                    stateTimer = dyingDuration;
                    break;
                case ATTACKING:
                    stateTimer = attackingDuration;
                    hasDealtDamage = false; // Reset damage flag
                    break;
                default:
                    // No timer needed for IDLE and WALKING
                    break;
            }
        }
    }

    void attack() {
        setState(AnimationState.ATTACKING);
        soundManager.playEnemyAttack();
    }

    void checkPlayerDamage( ) {
        if (dist(player.x, player.y, x, y) < TILE_SIZE && player.canTakeDamage() && isAlive() && !hasDealtDamage) {
            player.takeDamage();
            hasDealtDamage = true;
            hitFlash();
            // Note: You might want to trigger screen shake and red flash from here,
            // or implement a method in the main class that the Enemy can call.
        }
    }

    void updatePath(Tile[][] map) {
        int startX = floor(x / TILE_SIZE);
        int startY = floor(y / TILE_SIZE);
        int endX = floor(player.x / TILE_SIZE);
        int endY = floor(player.y / TILE_SIZE);

        path = aStar(map, startX, startY, endX, endY);
        pathIndex = 0;
    }

   List<PVector> aStar(Tile[][] map, int startX, int startY, int goalX, int goalY) {
        PriorityQueue<Node> openSet = new PriorityQueue<>();
        boolean[][] closedSet = new boolean[map.length][map[0].length];
        Node[][] nodeMap = new Node[map.length][map[0].length];

        // Initialize nodes
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                nodeMap[i][j] = new Node(i, j);
            }
        }

        Node start = nodeMap[startX][startY];
        Node goal = nodeMap[goalX][goalY];
        start.g = 0;
        start.f = heuristic(start, goal);
        openSet.add(start);

        while (!openSet.isEmpty()) {
            Node current = openSet.poll();

            if (current.equals(goal)) {
                return reconstructPath(current);
            }

            closedSet[current.x][current.y] = true;

            // Explore neighbors (4-directional)
            for (int[] dir : new int[][] { {0, 1}, {1, 0}, {0, -1}, {-1, 0} }) {
                int neighborX = current.x + dir[0];
                int neighborY = current.y + dir[1];

                // Check map bounds
                if (neighborX < 0 || neighborX >= map.length || neighborY < 0 || neighborY >= map[0].length) {
                    continue;
                }

                // Skip if already evaluated or blocked
                if (closedSet[neighborX][neighborY] || map[neighborX][neighborY].type != TileType.EMPTY) {
                    continue;
                }

                Node neighbor = nodeMap[neighborX][neighborY];
                float tentativeG = current.g + 1; // Assuming uniform cost

                if (tentativeG < neighbor.g) {
                    neighbor.parent = current;
                    neighbor.g = tentativeG;
                    neighbor.f = neighbor.g + heuristic(neighbor, goal);

                    if (!openSet.contains(neighbor)) {
                        openSet.add(neighbor);
                    }
                }
            }
        }

        return null; // No path found
    }


    float heuristic(Node a, Node b) {
        return abs(a.x - b.x) + abs(a.y - b.y);
    }

    List<PVector> reconstructPath(Node goal) {
        List<PVector> path = new ArrayList<>();
        Node current = goal;

        while (current != null) {
            path.add(new PVector(current.x * TILE_SIZE + TILE_SIZE / 2, current.y * TILE_SIZE + TILE_SIZE / 2));
            current = current.parent;
        }
        Collections.reverse(path);
        return path;
    }

    

     void takeDamage(int damage) {
        health -= damage;
        if (!isAlive()) {
            setState(AnimationState.DYING);
            soundManager.playEnemyDie();
        } else {
            setState(AnimationState.DAMAGED);
            soundManager.playEnemyHit();
        }
    }

    void respawn() {
        setState(AnimationState.IDLE);
        active = true;
        health = maxHealth;
        PVector safePosition = findSafeSpawnPosition();
        x = safePosition.x;
        y = safePosition.y;
    }

    boolean isAlive() {
        return health > 0;
    }

    boolean isCollision(float newX, float newY, Tile[][] map) {
        // Determine which tile the new position is in
        int tileX = floor(newX / TILE_SIZE);
        int tileY = floor(newY / TILE_SIZE);

        // Check map bounds
        if (tileX < 0 || tileX >= map.length || tileY < 0 || tileY >= map[0].length) {
            return true; // Treat out-of-bounds as collision
        }

        // Check if the tile is walkable
        return map[tileX][tileY].type != TileType.EMPTY;
    }

     void display() {
        if (!active) return;
        int rowIndex = currentAnim.row;
        int currentFrame = currentAnim.getCurrentFrame();

        int spriteX = currentFrame * spriteWidth;
        int spriteY = rowIndex * spriteHeight;
        pushMatrix();
        translate(x, y);

        boolean flip = false;
        if ((currentState == AnimationState.WALKING || currentState == AnimationState.ATTACKING) 
            && path != null && !path.isEmpty() && path.get(pathIndex).x < x) {
            flip = true;
            scale(-1, 1); 
        }      
       // float drawX = flip ? spriteWidth / 2f : -spriteWidsth / 2f;
        float drawX = -spriteWidth / 2;
        image(spriteSheet, drawX, -spriteHeight + spriteHeight / 4, spriteWidth, spriteHeight,
                spriteX, spriteY, spriteX + spriteWidth, spriteY + spriteHeight); // Source position and size

        popMatrix();

        if (health < maxHealth && currentState != AnimationState.DYING) {
            drawHealthBar();
        }
    }


    void drawHealthBar() {
        float barWidth = TILE_SIZE;
        float healthRatio = (float)health / maxHealth;
        float barHeight = 5;
        fill(255, 0, 0);
        rect(x - barWidth / 2, y - TILE_SIZE / 2 - 10,barWidth * healthRatio, barHeight);
        noFill();
        }
    }

class Node implements Comparable<Node> {
    int x, y;
    float g = Float.MAX_VALUE;
    float f = Float.MAX_VALUE;
    Node parent;

    Node(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int compareTo(Node other) {
        return Float.compare(this.f, other.f);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Node other = (Node) obj;
        return this.x == other.x && this.y == other.y;
    }

    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}