#include "rlc_turn_systems.h"
#include "rlc_components.h"
#include "rlc_equipment_spawner.h"
#include "rlc_items.h"
#include "rlc_log.h"
#include "rlc_fov.h"
#include "rlc_npc_ai.h"
#include "rlc_pathfinding.h"
#include "rlc_turn_manager.h"
#include "rlc_tween.h"
#include "rlc_tween_components.h"
#include "dungeon_builder.h"
#include <stdlib.h>

/* ============================================================
 * HELPER FUNCTIONS
 * ============================================================ */

/* Forward declarations */
static bool is_tile_occupied(ecs_world_t *world, int x, int y, ecs_entity_t ignore);

/* Constants for Action Costs */
#define ATTACK_DELAY_BASE 20

/* Helper: Calculate attack delay */
static int calculate_attack_delay(int speed) {
    /* Higher speed = lower delay */
    /* Base 20, reduce by speed/5. Min delay 5. */
    int delay = ATTACK_DELAY_BASE - (speed / 5);
    if (delay < 5) delay = 5;
    return delay;
}

/* Helper: Find free inventory slot */
static int find_free_slot(ecs_world_t *world, ecs_entity_t player) {
    bool occupied[12] = {false};
    ecs_query_t *q = ecs_query(world, {
        .terms = {{.id = ecs_id(InventorySlot)},
                  {.first.id = ContainedBy, .second.id = player}}
    });
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        InventorySlot *slots = ecs_field(&it, InventorySlot, 0);
        for (int i = 0; i < it.count; i++) {
            if (slots[i].slot_index >= 0 && slots[i].slot_index < 12) {
                occupied[slots[i].slot_index] = true;
            }
        }
    }
    ecs_query_fini(q);
    for (int i = 0; i < 12; i++) {
        if (!occupied[i]) return i;
    }
    return -1;
}

/* Helper: Unequip item of specific type */
static void unequip_slot_type(ecs_world_t *world, ecs_entity_t player,
                              ecs_entity_t tag) {
    ecs_query_t *q = ecs_query(world, {
        .terms = {{.id = tag},
                  {.first.id = ContainedBy, .second.id = player}}
    });
    ecs_iter_t it = ecs_query_iter(world, q);
    while (ecs_query_next(&it)) {
        for (int i = 0; i < it.count; i++) {
            ecs_remove_id(world, it.entities[i], tag);
        }
    }
    ecs_query_fini(q);
}

/* Helper: Execute Immediate Movement */
static bool execute_move_immediate(ecs_world_t *world, ecs_entity_t actor, int dx, int dy) {
    GridPosition *grid = ecs_get_mut(world, actor, GridPosition);
    if (!grid) return false;

    int new_x = grid->x + dx;
    int new_y = grid->y + dy;

    DungeonContext *dungeon = (DungeonContext *)ecs_get_ctx(world);
    if (!dungeon) return false;

    Tile t = dungeon_context_get_tile(dungeon, new_x, new_y);
    if (t != TILE_FLOOR) {
        /* Blocked by wall */
        return false;
    }

    /* Check for entity collision */
    if (is_tile_occupied(world, new_x, new_y, actor)) {
        return false;
    }

    /* Move successful */
    grid->x = new_x;
    grid->y = new_y;
    ecs_modified(world, actor, GridPosition);

    RlcFOV *fov = ecs_get_mut(world, actor, RlcFOV);
    if (fov) {
        fov->needs_update = true;
        ecs_modified(world, actor, RlcFOV);
    }

    bool is_player = ecs_has(world, actor, Player);
    
    /* Update camera if player */
    if (is_player) {
        ecs_query_t *q = ecs_query(world, {
            .terms = {{ .id = ecs_id(Camera) }}
        });
        if (!q) return true;
        ecs_iter_t cam_it = ecs_query_iter(world, q);
        while (ecs_query_next(&cam_it)) {
            Camera *cam = ecs_field(&cam_it, Camera, 0);
            if (cam && cam[0].follow_player) {
                cam[0].target_x = (float)new_x;
                cam[0].target_y = (float)new_y;
            }
            ecs_iter_fini(&cam_it); /* Required for early exit */
            break;
        }
        ecs_query_fini(q);
    }

    return true;
}

/* Helper: Find equipped weapon entity for an actor */
static ecs_entity_t find_equipped_weapon(ecs_world_t *world, ecs_entity_t actor) {
    ecs_query_t *q = ecs_query(world, {
        .terms = {
            { .id = ecs_id(EquippedWeapon) },
            { .first.id = ContainedBy, .second.id = actor }
        }
    });
    if (!q) return 0;

    ecs_entity_t weapon = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    if (ecs_query_next(&it)) {
        weapon = it.entities[0];
        ecs_iter_fini(&it);
    }
    ecs_query_fini(q);
    return weapon;
}

/* Helper: Find equipped armor entity for an actor */
static ecs_entity_t find_equipped_armor(ecs_world_t *world, ecs_entity_t actor) {
    ecs_query_t *q = ecs_query(world, {
        .terms = {
            { .id = ecs_id(EquippedArmor) },
            { .first.id = ContainedBy, .second.id = actor }
        }
    });
    if (!q) return 0;

    ecs_entity_t armor = 0;
    ecs_iter_t it = ecs_query_iter(world, q);
    if (ecs_query_next(&it)) {
        armor = it.entities[0];
        ecs_iter_fini(&it);
    }
    ecs_query_fini(q);
    return armor;
}

/* Helper: Queue Attack — uses weapon delay if equipped, else unarmed default */
static void queue_attack(ecs_world_t *world, TurnState *ts,
                         ecs_entity_t actor, ecs_entity_t target, int speed) {
    int delay = 15; /* unarmed default */

    ecs_entity_t weapon = find_equipped_weapon(world, actor);
    if (weapon) {
        const WeaponStats *wpn = ecs_get(world, weapon, WeaponStats);
        if (wpn) delay = wpn->delay;
    } else {
        /* Fallback: speed-based delay for monsters without equipment */
        delay = calculate_attack_delay(speed);
    }

    QueuedAction qa = {
        .action_type = ACTION_ATTACK,
        .actor = actor,
        .target = target,
        .dx = 0,
        .dy = 0,
        .delay = delay,
        .priority = 100
    };
    rlc_action_queue_push(ts, qa);
}


/* Check if any entities have active tweens (animations in progress)
 * Returns true if any tween components exist in the world.
 * Tweens auto-remove on completion, so existence = active animation. */
static bool has_active_tweens(ecs_world_t *world) {
    /* Count entities with any tween component type */
    int tween_pos_count = ecs_count(world, TweenPosition);
    int tween_alpha_count = ecs_count(world, TweenAlpha);
    int tween_scale_count = ecs_count(world, TweenScale);
    int tween_offset_count = ecs_count(world, TweenOffset);

    int total_tweens = tween_pos_count + tween_alpha_count +
                       tween_scale_count + tween_offset_count;

    return total_tweens > 0;
}

/* System 1: Turn Initialization - Process delayed events */
static void TurnInitSystem(ecs_iter_t *it) {
    TurnState *ts = ecs_field(it, TurnState, 0);

    for (int i = 0; i < it->count; i++) {
        /* Process delayed event queue */
        rlc_event_queue_process(it->world, &ts[i]);
    }
}

/* System 2: Timeline System - The Heartbeat of the Grand Integer System
 * Advances time until an entity is ready to act. */
static void TimelineSystem(ecs_iter_t *it) {
    /* Stop if visual effects (tweens) are playing */
    if (has_active_tweens(it->world)) {
        return;
    }

    /* Stop if anyone currently has the MyTurn tag (waiting for them to act) */
    if (ecs_count(it->world, MyTurn) > 0) {
        return;
    }

    /* Get all entities with Energy */
    ecs_query_t *q_energy = ecs_query(it->world, {
        .terms = {
            { .id = ecs_id(Energy) },
            { .id = ecs_id(Actor) } /* Only consider actors */
        }
    });
    if (!q_energy) return;

    /* Max ticks to simulate per frame to prevent infinite loops if no one can act */
    int max_ticks = 1000; 
    int ticks = 0;

    while (ticks < max_ticks) {
        /* 1. Check if anyone has enough energy to act RIGHT NOW */
        int actors_ready = 0;
        
        ecs_iter_t energy_it = ecs_query_iter(it->world, q_energy);
        while (ecs_query_next(&energy_it)) {
            Energy *e = ecs_field(&energy_it, Energy, 0);
            for (int i = 0; i < energy_it.count; i++) {
                if (e[i].accumulator >= ACTION_COST) {
                    actors_ready++;
                    /* Mark them as "MyTurn" */
                    ecs_add(it->world, energy_it.entities[i], MyTurn);
                }
            }
        }

        /* 2. If we found someone, STOP. Let ECS systems run their logic. */
        if (actors_ready > 0) {
            ecs_query_fini(q_energy);
            return;
        }

        /* 3. If no one is ready, advance the clock (Tick) */
        /* This is the "Empty Time" where everyone regenerates */
        energy_it = ecs_query_iter(it->world, q_energy);
        while (ecs_query_next(&energy_it)) {
            Energy *e = ecs_field(&energy_it, Energy, 0);
            for (int i = 0; i < energy_it.count; i++) {
                 /* Apply interactions here! (e.g. status effects) */
                 int current_gain = e[i].gain_per_tick;
                 
                 /* Example: if (ecs_has(it->world, energy_it.entities[i], StatusFrozen)) current_gain /= 2; */
                 
                 e[i].accumulator += current_gain;
            }
        }
        ticks++;
    }
    
    ecs_query_fini(q_energy);
}

/* System 3: Player Path Follow - Auto-follow mouse path when no keyboard input */
static void PlayerPathFollowSystem(ecs_iter_t *it) {
    ActionIntent *intent = ecs_field(it, ActionIntent, 0);
    AStarPath *path = ecs_field(it, AStarPath, 1);
    PathStatus *status = ecs_field(it, PathStatus, 2);

    /* Only run if player has turn */
    if (!ecs_has(it->world, it->entities[0], MyTurn)) return;

    for (int i = 0; i < it->count; i++) {
        /* Skip if keyboard input is already pending */
        if (intent[i].pending) continue;

        /* Check if we have a valid path to follow */
        if (status[i].value != PATH_VALID || rlc_path_is_complete(path + i)) {
            continue;
        }

        /* Get next step from path */
        int dx, dy;
        if (rlc_path_get_next_step(path + i, &dx, &dy)) {
            /* Set intent to move along path */
            intent[i].action_type = ACTION_MOVE;
            intent[i].dx = dx;
            intent[i].dy = dy;
            intent[i].target = 0;
            intent[i].energy_cost = ACTION_COST_MOVE;
            intent[i].pending = true;

            /* Advance path for next turn */
            rlc_path_advance(path + i);

            /* Clear path if complete */
            if (rlc_path_is_complete(path + i)) {
                status[i].value = PATH_NONE;
            }

            // RLC_LOG_DEBUG("Player following path: (%d, %d)", dx, dy);
            ecs_modified(it->world, it->entities[i], ActionIntent);
        }
    }
}

/* System 4: Player Action System - Handle player input */
static void PlayerActionSystem(ecs_iter_t *it) {
    ActionIntent *intent = ecs_field(it, ActionIntent, 0);
    Energy *energy = ecs_field(it, Energy, 1);
    Actor *actor = ecs_field(it, Actor, 2);

    TurnState *ts = ecs_singleton_get_mut(it->world, TurnState);
    if (!ts) return;

    for (int i = 0; i < it->count; i++) {
        /* Ensure player has MyTurn tag */
        if (!ecs_has(it->world, it->entities[i], MyTurn)) continue;

        if (!intent[i].pending) continue;

        /* If moving, check for Bump Attack */
        if (intent[i].action_type == ACTION_MOVE) {
            GridPosition *pos = ecs_get_mut(it->world, it->entities[i], GridPosition);
            if (pos) {
                int target_x = pos->x + intent[i].dx;
                int target_y = pos->y + intent[i].dy;
                
                /* Check if blocked by entity */
                if (is_tile_occupied(it->world, target_x, target_y, it->entities[i])) {
                     /* Find WHO is blocking to see if we should attack */
                     ecs_query_t *q = ecs_query(it->world, {
                        .terms = {
                            { .id = ecs_id(GridPosition) },
                            { .id = ecs_id(Health) }, /* Only attack things with health */
                            { .id = ecs_id(Actor) }
                        }
                    });
                    if (q) {
                    ecs_iter_t qit = ecs_query_iter(it->world, q);
                    while (ecs_query_next(&qit)) {
                        GridPosition *gp = ecs_field(&qit, GridPosition, 0);
                        for (int k = 0; k < qit.count; k++) {
                            if (qit.entities[k] != it->entities[i] && gp[k].x == target_x && gp[k].y == target_y) {
                                /* Found target! Convert to Attack */
                                intent[i].action_type = ACTION_ATTACK;
                                intent[i].target = qit.entities[k];
                                ecs_iter_fini(&qit); /* Required for early exit via goto */
                                goto found_blocker;
                            }
                        }
                    }
                    found_blocker:
                    ecs_query_fini(q);
                    }
                }
            }
        }

        /* Execute Action */
        bool action_taken = false;
        
        if (intent[i].action_type == ACTION_MOVE) {
            /* Try to move */
            bool moved = execute_move_immediate(it->world, it->entities[i], intent[i].dx, intent[i].dy);
            if (moved) action_taken = true;
        }
        else if (intent[i].action_type == ACTION_ATTACK) {
            /* Schedule Attack */
            queue_attack(it->world, ts, it->entities[i], intent[i].target, actor[i].speed);
            action_taken = true;
        }
        else if (intent[i].action_type == ACTION_WAIT) {
            /* Explicit wait counts as taking a turn */
            action_taken = true;
        }
        else {
            /* Other actions (Use Item, etc) */
            QueuedAction qa = {
                .action_type = intent[i].action_type,
                .actor = it->entities[i],
                .target = intent[i].target,
                .item = intent[i].item,
                .dx = intent[i].dx,
                .dy = intent[i].dy,
                .delay = 0,
                .priority = 50
            };
            rlc_action_queue_push(ts, qa);
            action_taken = true;
        }

        if (action_taken) {
            /* Pay the cost */
            energy[i].accumulator -= ACTION_COST;
            
            /* Remove MyTurn tag - turn ends */
            ecs_remove(it->world, it->entities[i], MyTurn);
        }

        /* Clear intent */
        intent[i].pending = false;
    }
}

/* --- Helper: Find player entity and position --- */
static bool find_player(ecs_world_t *world, ecs_entity_t *out_player, int *out_x, int *out_y) {
    ecs_query_t *player_q = ecs_query(world, {
        .terms = {
            { .id = ecs_id(GridPosition) },
            { .id = ecs_id(Player) }
        }
    });
    if (!player_q) return false;
    ecs_iter_t player_it = ecs_query_iter(world, player_q);
    bool found = false;
    if (ecs_query_next(&player_it)) {
        *out_player = player_it.entities[0];
        const GridPosition *player_grid = ecs_field(&player_it, GridPosition, 0);
        if (player_grid) {
            *out_x = player_grid->x;
            *out_y = player_grid->y;
            found = true;
        }
        ecs_iter_fini(&player_it);  /* Must finalize iterator before query_fini */
    }
    ecs_query_fini(player_q);
    return found;
}

/* --- Helper: Check if any actor occupies a grid position --- */
static bool is_tile_occupied(ecs_world_t *world, int x, int y, ecs_entity_t ignore) {
    ecs_query_t *q = ecs_query(world, {
        .terms = {
            { .id = ecs_id(GridPosition) },
            { .id = ecs_id(Actor) }
        }
    });
    if (!q) return false;

    bool occupied = false;
    ecs_iter_t qit = ecs_query_iter(world, q);
    while (ecs_query_next(&qit)) {
        GridPosition *gp = ecs_field(&qit, GridPosition, 0);
        for (int i = 0; i < qit.count; i++) {
            if (qit.entities[i] != ignore && gp[i].x == x && gp[i].y == y) {
                occupied = true;
                break;
            }
        }
        if (occupied) {
            ecs_iter_fini(&qit); /* Required for early exit */
            break;
        }
    }
    ecs_query_fini(q);
    return occupied;
}

/* --- Helper: Check line-of-sight using Bresenham's line algorithm --- */
static bool has_line_of_sight(DungeonContext *dungeon, int x0, int y0, int x1, int y1) {
    if (!dungeon) return false;

    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    int sx = (x0 < x1) ? 1 : -1;
    int sy = (y0 < y1) ? 1 : -1;
    int err = dx - dy;

    int x = x0, y = y0;

    while (1) {
        /* Don't check the start or end positions - only tiles in between */
        if ((x != x0 || y != y0) && (x != x1 || y != y1)) {
            Tile t = dungeon_context_get_tile(dungeon, x, y);
            if (t == TILE_WALL || t == TILE_EMPTY) {
                return false; /* Blocked by wall or void */
            }
        }

        if (x == x1 && y == y1) break;

        int e2 = 2 * err;
        if (e2 > -dy) {
            err -= dy;
            x += sx;
        }
        if (e2 < dx) {
            err += dx;
            y += sy;
        }
    }

    return true;
}

/* --- Helper: Check if entity can see target (range + line-of-sight) --- */
static bool can_see_entity(ecs_world_t *world, ecs_entity_t viewer, ecs_entity_t target) {
    const GridPosition *viewer_pos = ecs_get(world, viewer, GridPosition);
    const GridPosition *target_pos = ecs_get(world, target, GridPosition);

    if (!viewer_pos || !target_pos) return false;

    /* First: range check (early out if too far) */
    const int MONSTER_PERCEPTION_RANGE = 8;
    int dx = target_pos->x - viewer_pos->x;
    int dy = target_pos->y - viewer_pos->y;
    int dist_sq = dx * dx + dy * dy;

    if (dist_sq > (MONSTER_PERCEPTION_RANGE * MONSTER_PERCEPTION_RANGE)) {
        return false;
    }

    /* Second: line-of-sight check (can't see through walls).
     * Use the world context DungeonContext* directly — it's fully populated
     * for all dungeon types (physics, BSP, loaded/imported, town, arena). */
    DungeonContext *ctx = (DungeonContext *)ecs_get_ctx(world);
    if (!ctx) {
        return false;
    }

    return has_line_of_sight(ctx,
                             viewer_pos->x, viewer_pos->y,
                             target_pos->x, target_pos->y);
}

/* System 5a: AI Perception System - Update memory based on what AI can see */
static void AiPerceptionSystem(ecs_iter_t *it) {
    Actor *actor = ecs_field(it, Actor, 0);
    AiMemory *memory = ecs_field(it, AiMemory, 1);
    GridPosition *grid = ecs_field(it, GridPosition, 2);
    (void)actor; 

    /* AI Systems now run whenever the AI entity has MyTurn */
    /* This system updates memory for *all* AIs though? */
    /* No, we should only update for AIs that are about to act or we update all per frame? */
    /* Better to update only for the active entity to save perf, or all? */
    /* Actually, perception is a passive thing. But in a strict turn system, */
    /* it matters when they act. Let's filter by MyTurn for now to keep it simple. */
    
    /* However, to catch "I see you", we usually check perception. */
    /* Let's iterate only MyTurn entities */
    
    // Note: The original system iterated all Actors. 
    // We only want to process the entity whose turn it is.
    // So we add MyTurn to the query in registration.

    ecs_entity_t player = 0;
    int player_x = 0, player_y = 0;
    if (!find_player(it->world, &player, &player_x, &player_y)) return;

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];
        bool sees_player = can_see_entity(it->world, monster, player);

        if (sees_player) {
            memory[i].last_seen_player_x = player_x;
            memory[i].last_seen_player_y = player_y;
            memory[i].turns_since_player_seen = 0;

            /* Update hunting state if present */
            AiStateHunting *hunting = ecs_get_mut(it->world, monster, AiStateHunting);
            if (hunting) {
                hunting->last_known_x = player_x;
                hunting->last_known_y = player_y;
                hunting->target_visible = true;
            }
        } else {
            memory[i].turns_since_player_seen++;

            /* Mark target not visible in hunting state */
            AiStateHunting *hunting = ecs_get_mut(it->world, monster, AiStateHunting);
            if (hunting) {
                hunting->target_visible = false;
            }
        }

        (void)grid;
    }
}

/* System 5b: AI State Transition System */
static void AiStateTransitionSystem(ecs_iter_t *it) {
    Actor *actor = ecs_field(it, Actor, 0);
    AiMemory *memory = ecs_field(it, AiMemory, 1);
    (void)actor;

    ecs_entity_t player = 0;
    int player_x = 0, player_y = 0;
    find_player(it->world, &player, &player_x, &player_y);

    ecs_defer_begin(it->world);

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];
        bool sees_player = (memory[i].turns_since_player_seen == 0);

        /* Check current state and decide transitions */
        bool is_idle = ecs_has(it->world, monster, AiStateIdle);
        bool is_wandering = ecs_has(it->world, monster, AiStateWandering);
        bool is_hunting = ecs_has(it->world, monster, AiStateHunting);
        bool is_investigating = ecs_has(it->world, monster, AiStateInvestigating);
        bool is_fleeing = ecs_has(it->world, monster, AiStateFleeing);

        /* Check health for fleeing decision */
        const Health *hp = ecs_get(it->world, monster, Health);
        bool low_health = hp && hp->max > 0 && (hp->current * 100 / hp->max) < 30;

        /* Idle -> Hunting: Monster awakens when it sees the player */
        if (is_idle) {
            if (sees_player && player) {
                ecs_remove(it->world, monster, AiStateIdle);
                ecs_set(it->world, monster, AiStateHunting, {
                    .target = player,
                    .last_known_x = memory[i].last_seen_player_x,
                    .last_known_y = memory[i].last_seen_player_y,
                    .target_visible = true,
                    .stuck_turns = 0
                });
                RLC_LOG_DEBUG("Monster %lu: Idle -> Hunting (awakened!)", (unsigned long)monster);
                continue;
            }
            continue;
        }

        if (is_wandering) {
            if (sees_player && player) {
                ecs_remove(it->world, monster, AiStateWandering);
                ecs_set(it->world, monster, AiStateHunting, {
                    .target = player,
                    .last_known_x = memory[i].last_seen_player_x,
                    .last_known_y = memory[i].last_seen_player_y,
                    .target_visible = true,
                    .stuck_turns = 0
                });
            }
        }
        else if (is_hunting) {
            AiStateHunting *hunting = ecs_get_mut(it->world, monster, AiStateHunting);

            if (low_health) {
                ecs_remove(it->world, monster, AiStateHunting);
                ecs_add(it->world, monster, AiStateFleeing);
            }
            else if (hunting && !hunting->target_visible && memory[i].turns_since_player_seen >= 2) {
                int target_x = hunting->last_known_x;
                int target_y = hunting->last_known_y;
                ecs_remove(it->world, monster, AiStateHunting);
                ecs_set(it->world, monster, AiStateInvestigating, {
                    .target_x = target_x,
                    .target_y = target_y,
                    .turns_remaining = 5
                });
            }
            else if (hunting && hunting->stuck_turns >= 3) {
                ecs_remove(it->world, monster, AiStateHunting);
                ecs_set(it->world, monster, AiStateInvestigating, {
                    .target_x = hunting->last_known_x,
                    .target_y = hunting->last_known_y,
                    .turns_remaining = 3
                });
            }
        }
        else if (is_investigating) {
            AiStateInvestigating *inv = ecs_get_mut(it->world, monster, AiStateInvestigating);

            if (sees_player && player) {
                ecs_remove(it->world, monster, AiStateInvestigating);
                ecs_set(it->world, monster, AiStateHunting, {
                    .target = player,
                    .last_known_x = memory[i].last_seen_player_x,
                    .last_known_y = memory[i].last_seen_player_y,
                    .target_visible = true,
                    .stuck_turns = 0
                });
            }
            else if (inv && inv->turns_remaining <= 0) {
                ecs_remove(it->world, monster, AiStateInvestigating);
                ecs_add(it->world, monster, AiStateWandering);
            }
        }
        else if (is_fleeing) {
            if (memory[i].turns_since_player_seen >= 5) {
                ecs_remove(it->world, monster, AiStateFleeing);
                ecs_add(it->world, monster, AiStateWandering);
            }
        }
        else {
            ecs_add(it->world, monster, AiStateWandering);
        }
    }

    ecs_defer_end(it->world);
}

/* System 5c: AI Action - Idle behavior */
static void AiActionIdleSystem(ecs_iter_t *it) {
    Energy *energy = ecs_field(it, Energy, 1);

    for (int i = 0; i < it->count; i++) {
        /* Idle monsters pass their turn */
        energy[i].accumulator -= ACTION_COST;
        ecs_remove(it->world, it->entities[i], MyTurn);
    }
}

/* System 5d: AI Action - Wandering behavior */
static void AiActionWanderingSystem(ecs_iter_t *it) {
    Energy *energy = ecs_field(it, Energy, 1);
    GridPosition *grid = ecs_field(it, GridPosition, 2);

    DungeonContext *dungeon = (DungeonContext *)ecs_get_ctx(it->world);
    if (!dungeon) return;

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];

        /* Pick random adjacent walkable tile */
        int dx = 0, dy = 0;
        int directions[8][2] = {{-1,-1},{0,-1},{1,-1},{-1,0},{1,0},{-1,1},{0,1},{1,1}};

        for (int tries = 0; tries < 8; tries++) {
            int dir_idx = rand() % 8;
            int try_dx = directions[dir_idx][0];
            int try_dy = directions[dir_idx][1];

            int new_x = grid[i].x + try_dx;
            int new_y = grid[i].y + try_dy;
            Tile t = dungeon_context_get_tile(dungeon, new_x, new_y);

            if (t == TILE_FLOOR) {
                dx = try_dx;
                dy = try_dy;
                break;
            }
        }

        if (dx != 0 || dy != 0) {
            execute_move_immediate(it->world, monster, dx, dy);
        }
        
        /* Pay cost */
        energy[i].accumulator -= ACTION_COST;
        ecs_remove(it->world, monster, MyTurn);
    }
}

/* System 5e: AI Action - Hunting behavior */
static void AiActionHuntingSystem(ecs_iter_t *it) {
    Energy *energy = ecs_field(it, Energy, 1);
    GridPosition *grid = ecs_field(it, GridPosition, 2);
    AiStateHunting *hunting = ecs_field(it, AiStateHunting, 3);
    Actor *actor = ecs_field(it, Actor, 4);

    TurnState *ts = ecs_singleton_get_mut(it->world, TurnState);
    if (!ts) return;

    DungeonContext *dungeon = (DungeonContext *)ecs_get_ctx(it->world);
    if (!dungeon) return;

    ecs_entity_t player = 0;
    int player_x = 0, player_y = 0;
    find_player(it->world, &player, &player_x, &player_y);

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];

        if (!ecs_is_alive(it->world, hunting[i].target)) {
            energy[i].accumulator -= ACTION_COST;
            ecs_remove(it->world, monster, MyTurn);
            continue;
        }

        int target_x = hunting[i].last_known_x;
        int target_y = hunting[i].last_known_y;

        int gx = grid[i].x;
        int gy = grid[i].y;
        int dist_x = player_x - gx;
        int dist_y = player_y - gy;
        bool adjacent = (abs(dist_x) <= 1 && abs(dist_y) <= 1 && (dist_x != 0 || dist_y != 0));

        bool acted = false;

        /* 1. Attack if adjacent */
        if (adjacent && hunting[i].target_visible) {
             queue_attack(it->world, ts, monster, player, actor[i].speed);
             acted = true;
        }
        else {
            /* 2. Move */
            AStarPath *path = ecs_get_mut(it->world, monster, AStarPath);
            PathStatus *status = ecs_get_mut(it->world, monster, PathStatus);

            int dx = 0, dy = 0;
            bool moved = false;
            
            if (path && status && status->value == PATH_VALID && !rlc_path_is_complete(path)) {
                int step_dx, step_dy;
                if (rlc_path_get_next_step(path, &step_dx, &step_dy)) {
                    int new_x = grid[i].x + step_dx;
                    int new_y = grid[i].y + step_dy;
                    Tile t = dungeon_context_get_tile(dungeon, new_x, new_y);

                    if (t == TILE_FLOOR) {
                        dx = step_dx;
                        dy = step_dy;
                        rlc_path_advance(path);
                        
                        moved = execute_move_immediate(it->world, monster, dx, dy);
                        if (moved) {
                            hunting[i].stuck_turns = 0;
                            acted = true;
                        }
                    }
                }
            }

            if (!moved) {
                int dist_sq = (target_x - grid[i].x) * (target_x - grid[i].x) +
                                (target_y - grid[i].y) * (target_y - grid[i].y);
                if (dist_sq <= 400) {
                    rlc_request_path_to_entity(it->world, monster, hunting[i].target);
                }
                hunting[i].stuck_turns++;
                
                /* Pass turn even if stuck to prevent freeze */
                acted = true;
            } else {
                /* 3. Attack after move (bump) logic if we want, but here we just moved */
            }
        }

        if (acted) {
            energy[i].accumulator -= ACTION_COST;
            ecs_remove(it->world, monster, MyTurn);
        }
    }
}

/* System 5f: AI Action - Fleeing behavior */
static void AiActionFleeingSystem(ecs_iter_t *it) {
    Energy *energy = ecs_field(it, Energy, 1);
    GridPosition *grid = ecs_field(it, GridPosition, 2);

    DungeonContext *dungeon = (DungeonContext *)ecs_get_ctx(it->world);
    if (!dungeon) return;

    ecs_entity_t player = 0;
    int player_x = 0, player_y = 0;
    find_player(it->world, &player, &player_x, &player_y);

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];

        int flee_dx = grid[i].x - player_x;
        int flee_dy = grid[i].y - player_y;

        int dx = (flee_dx > 0) ? 1 : ((flee_dx < 0) ? -1 : 0);
        int dy = (flee_dy > 0) ? 1 : ((flee_dy < 0) ? -1 : 0);

        int new_x = grid[i].x + dx;
        int new_y = grid[i].y + dy;
        Tile t = dungeon_context_get_tile(dungeon, new_x, new_y);

        if (t != TILE_FLOOR) {
            int alts[4][2] = {{dx, 0}, {0, dy}, {-dy, dx}, {dy, -dx}};
            dx = 0;
            dy = 0;
            for (int j = 0; j < 4; j++) {
                new_x = grid[i].x + alts[j][0];
                new_y = grid[i].y + alts[j][1];
                t = dungeon_context_get_tile(dungeon, new_x, new_y);
                if (t == TILE_FLOOR) {
                    dx = alts[j][0];
                    dy = alts[j][1];
                    break;
                }
            }
        }

        if (dx != 0 || dy != 0) {
            execute_move_immediate(it->world, monster, dx, dy);
        }
        
        energy[i].accumulator -= ACTION_COST;
        ecs_remove(it->world, monster, MyTurn);
    }
}

/* System 5g: AI Action - Investigating behavior */
static void AiActionInvestigatingSystem(ecs_iter_t *it) {
    Energy *energy = ecs_field(it, Energy, 1);
    GridPosition *grid = ecs_field(it, GridPosition, 2);
    AiStateInvestigating *inv = ecs_field(it, AiStateInvestigating, 3);

    DungeonContext *dungeon = (DungeonContext *)ecs_get_ctx(it->world);
    if (!dungeon) return;

    for (int i = 0; i < it->count; i++) {
        ecs_entity_t monster = it->entities[i];

        inv[i].turns_remaining--;

        int dx = 0, dy = 0;
        int to_x = inv[i].target_x - grid[i].x;
        int to_y = inv[i].target_y - grid[i].y;

        if (to_x != 0 || to_y != 0) {
            dx = (to_x > 0) ? 1 : ((to_x < 0) ? -1 : 0);
            dy = (to_y > 0) ? 1 : ((to_y < 0) ? -1 : 0);

            int new_x = grid[i].x + dx;
            int new_y = grid[i].y + dy;
            Tile t = dungeon_context_get_tile(dungeon, new_x, new_y);

            if (t != TILE_FLOOR) {
                if (abs(to_x) >= abs(to_y)) {
                    dy = 0;
                } else {
                    dx = 0;
                }
                new_x = grid[i].x + dx;
                new_y = grid[i].y + dy;
                t = dungeon_context_get_tile(dungeon, new_x, new_y);
                if (t != TILE_FLOOR) {
                    dx = 0;
                    dy = 0;
                }
            }
        }

        if (dx != 0 || dy != 0) {
             execute_move_immediate(it->world, monster, dx, dy);
        }
        
        energy[i].accumulator -= ACTION_COST;
        ecs_remove(it->world, monster, MyTurn);
    }
}

/* System 6: Action Execution System - Execute queued actions (Attacks, Items) */
static void ActionExecutionSystem(ecs_iter_t *it) {
    TurnState *ts = ecs_field(it, TurnState, 0);

    for (int i = 0; i < it->count; i++) {
        if (ts[i].action_count == 0) continue;

        rlc_action_queue_sort(&ts[i]);

        for (size_t j = 0; j < ts[i].action_count; j++) {
            QueuedAction *qa = &ts[i].action_queue[j];

            switch (qa->action_type) {
                case ACTION_ATTACK: {
                    if (!ecs_is_alive(it->world, qa->target)) break;

                    const GridPosition *attacker_pos = ecs_get(it->world, qa->actor, GridPosition);
                    const GridPosition *target_pos = ecs_get(it->world, qa->target, GridPosition);

                    if (!attacker_pos || !target_pos) break;

                    int dx = target_pos->x - attacker_pos->x;
                    int dy = target_pos->y - attacker_pos->y;
                    if (abs(dx) > 1 || abs(dy) > 1) break;

                    /* Attack lunge animation */
                    {
                        float lunge_x = (float)dx * 4.0f;
                        float lunge_y = (float)dy * 4.0f;

                        if (!ecs_has(it->world, qa->actor, RenderOffset)) {
                            ecs_set(it->world, qa->actor, RenderOffset, {0.0f, 0.0f});
                        }

                        TweenBuilder lunge = TWEEN(it->world, qa->actor);
                        rlc_tween_duration(&lunge, 0.15f);
                        rlc_tween_easing(&lunge, EASE_OUT_QUAD);
                        rlc_tween_repeat(&lunge, TWEEN_MODE_YOYO);
                        rlc_tween_offset(lunge, lunge_x, lunge_y);
                    }

                    /* ── Deterministic weapon-based damage ── */
                    int raw_damage = 1; /* unarmed fallback */
                    const WeaponStats *wpn = NULL;
                    ecs_entity_t weapon_ent = find_equipped_weapon(it->world, qa->actor);

                    if (weapon_ent) {
                        wpn = ecs_get(it->world, weapon_ent, WeaponStats);
                        if (wpn) raw_damage = wpn->base_damage;
                    } else {
                        /* Unarmed: 1 + str/3 */
                        const Attributes *attr = ecs_get(it->world, qa->actor, Attributes);
                        raw_damage = 1 + (attr ? attr->str / 3 : 0);
                    }

                    /* Flat defense from target's armor */
                    int defense = 0;
                    ecs_entity_t armor_ent = find_equipped_armor(it->world, qa->target);
                    if (armor_ent) {
                        const ArmorStats *arm = ecs_get(it->world, armor_ent, ArmorStats);
                        if (arm) defense += arm->defense;
                    }
                    /* Also check target's Attributes for innate defense (monsters) */
                    /* Monsters don't equip armor; their defense field IS their defense */
                    if (!armor_ent) {
                        const Attributes *tgt_attr = ecs_get(it->world, qa->target, Attributes);
                        /* con provides innate defense for unarmored entities */
                        if (tgt_attr) defense += tgt_attr->con / 3;
                    }

                    /* Final damage: deterministic, floor 1 */
                    int damage = raw_damage - defense;
                    if (damage < 1) damage = 1;

                    Health *target_hp = ecs_get_mut(it->world, qa->target, Health);
                    if (target_hp) {
                        target_hp->current -= damage;

                        /* Enhanced combat log with damage breakdown */
                        rlc_log_combat_detailed(it->world, qa->actor, qa->target,
                                                raw_damage, defense, damage, weapon_ent);

                        /* Stun mechanic: push target's queued attacks forward */
                        if (wpn && wpn->stun_delay > 0 && target_hp->current > 0) {
                            for (size_t k = 0; k < ts[i].action_count; k++) {
                                if (ts[i].action_queue[k].actor == qa->target) {
                                    ts[i].action_queue[k].delay += wpn->stun_delay;
                                }
                            }
                            rlc_action_queue_sort(&ts[i]);

                            const Name *tgt_name = ecs_get(it->world, qa->target, Name);
                            rlc_log_message(it->world, LOG_MSG_COMBAT_HIT,
                                "%s is stunned! (+%d delay)",
                                tgt_name ? tgt_name->str : "Target", wpn->stun_delay);
                        }

                        if (target_hp->current <= 0) {
                            bool is_target_player = ecs_has(it->world, qa->target, Player);
                            if (!is_target_player) {
                                ecs_delete(it->world, qa->target);
                            } else {
                                RLC_LOG_INFO("Player killed!");
                            }
                        }
                    } else {
                        rlc_log_combat_detailed(it->world, qa->actor, qa->target,
                                                0, 0, 0, 0);
                    }
                    break;
                }

                case ACTION_USE_ITEM: {
                    if (!ecs_is_alive(it->world, qa->item)) break;
                    const Consumable *cons = ecs_get(it->world, qa->item, Consumable);
                    if (!cons) break;

                    bool consumed = false;
                    switch (cons->effect) {
                        case CONSUMABLE_EFFECT_HEAL: {
                            Health *hp = ecs_get_mut(it->world, qa->actor, Health);
                            if (hp) {
                                int old_hp = hp->current;
                                hp->current += cons->power;
                                if (hp->current > hp->max) hp->current = hp->max;
                                ecs_modified(it->world, qa->actor, Health);
                                int healed = hp->current - old_hp;
                                rlc_log_message(it->world, LOG_MSG_ITEM_USE, "You use a healing potion and restore %d HP", healed);
                                consumed = true;
                            }
                            break;
                        }
                        case CONSUMABLE_EFFECT_TELEPORT: {
                            rlc_log_message(it->world, LOG_MSG_ITEM_USE, "You read a scroll of teleportation!");
                            consumed = true;
                            break;
                        }
                        case CONSUMABLE_EFFECT_AIZAWA: {
                            rlc_log_message(it->world, LOG_MSG_ITEM_USE,
                                            "You read the Scroll of Aizawa... reality unravels!");
                            ecs_add(it->world, qa->actor, AizawaPending);
                            consumed = true;
                            break;
                        }
                        default: break;
                    }
                    if (consumed) ecs_delete(it->world, qa->item);
                    break;
                }

                case ACTION_DROP_ITEM: {
                    if (!ecs_is_alive(it->world, qa->item)) break;
                    /* Remove all equipment tags */
                    ecs_remove(it->world, qa->item, EquippedWeapon);
                    ecs_remove(it->world, qa->item, EquippedArmor);
                    ecs_remove(it->world, qa->item, EquippedAccessory);
                    /* Remove inventory slot and containment */
                    ecs_remove(it->world, qa->item, InventorySlot);
                    ecs_remove_pair(it->world, qa->item, ContainedBy, qa->actor);
                    const GridPosition *pos = ecs_get(it->world, qa->actor, GridPosition);
                    if (pos) {
                        ecs_add(it->world, qa->item, WorldItem);
                        ecs_set(it->world, qa->item, GridPosition, {pos->x, pos->y});
                        ecs_set(it->world, qa->item, RenderPosition, {(float)pos->x, (float)pos->y});
                        const Name *item_name = ecs_get(it->world, qa->item, Name);
                        rlc_log_message(it->world, LOG_MSG_SYSTEM, "You drop %s", item_name ? item_name->str : "an item");
                    }
                    break;
                }

                case ACTION_EQUIP_ITEM: {
                    if (!ecs_is_alive(it->world, qa->item)) break;
                    const Name *item_name = ecs_get(it->world, qa->item, Name);

                    /* Check attribute requirements before equipping */
                    const Attributes *equip_attr = ecs_get(it->world, qa->actor, Attributes);
                    const WeaponStats *equip_wpn = ecs_get(it->world, qa->item, WeaponStats);
                    const ArmorStats *equip_arm = ecs_get(it->world, qa->item, ArmorStats);

                    if (equip_wpn && equip_attr) {
                        if (equip_attr->str < equip_wpn->req_str) {
                            rlc_log_message(it->world, LOG_MSG_SYSTEM,
                                "Need %d STR to wield %s (you have %d)",
                                equip_wpn->req_str, item_name ? item_name->str : "weapon", equip_attr->str);
                            break;
                        }
                        if (equip_attr->dex < equip_wpn->req_dex) {
                            rlc_log_message(it->world, LOG_MSG_SYSTEM,
                                "Need %d DEX to wield %s (you have %d)",
                                equip_wpn->req_dex, item_name ? item_name->str : "weapon", equip_attr->dex);
                            break;
                        }
                    }
                    if (equip_arm && equip_attr) {
                        if (equip_attr->str < equip_arm->req_str) {
                            rlc_log_message(it->world, LOG_MSG_SYSTEM,
                                "Need %d STR to wear %s (you have %d)",
                                equip_arm->req_str, item_name ? item_name->str : "armor", equip_attr->str);
                            break;
                        }
                    }

                    /* Check item type and toggle equipment tag */
                    if (ecs_has(it->world, qa->item, IsWeapon)) {
                        if (ecs_has(it->world, qa->item, EquippedWeapon)) {
                            ecs_remove(it->world, qa->item, EquippedWeapon);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You unequip %s", item_name ? item_name->str : "weapon");
                        } else {
                            unequip_slot_type(it->world, qa->actor, ecs_id(EquippedWeapon));
                            ecs_add(it->world, qa->item, EquippedWeapon);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You equip %s", item_name ? item_name->str : "weapon");
                        }
                    } else if (ecs_has(it->world, qa->item, IsArmor)) {
                        if (ecs_has(it->world, qa->item, EquippedArmor)) {
                            ecs_remove(it->world, qa->item, EquippedArmor);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You unequip %s", item_name ? item_name->str : "armor");
                        } else {
                            unequip_slot_type(it->world, qa->actor, ecs_id(EquippedArmor));
                            ecs_add(it->world, qa->item, EquippedArmor);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You equip %s", item_name ? item_name->str : "armor");
                        }
                    } else if (ecs_has(it->world, qa->item, IsAccessory)) {
                        if (ecs_has(it->world, qa->item, EquippedAccessory)) {
                            ecs_remove(it->world, qa->item, EquippedAccessory);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You unequip %s", item_name ? item_name->str : "accessory");
                        } else {
                            unequip_slot_type(it->world, qa->actor, ecs_id(EquippedAccessory));
                            ecs_add(it->world, qa->item, EquippedAccessory);
                            rlc_log_message(it->world, LOG_MSG_SYSTEM, "You equip %s", item_name ? item_name->str : "accessory");
                        }
                    }
                    break;
                }

                case ACTION_PICKUP_ITEM: {
                    if (!ecs_is_alive(it->world, qa->item)) break;
                    if (!ecs_has(it->world, qa->item, WorldItem)) break;

                    /* Validate item isn't already in an inventory */
                    if (ecs_has(it->world, qa->item, InventorySlot)) break;
                    if (ecs_has_pair(it->world, qa->item, ContainedBy, EcsWildcard)) break;

                    /* Find free inventory slot */
                    int slot = find_free_slot(it->world, qa->actor);
                    if (slot == -1) {
                        rlc_log_message(it->world, LOG_MSG_SYSTEM, "Inventory full!");
                        break;
                    }

                    /* Add to inventory */
                    ecs_remove(it->world, qa->item, WorldItem);
                    ecs_remove(it->world, qa->item, GridPosition);
                    ecs_remove(it->world, qa->item, RenderPosition);
                    ecs_add_pair(it->world, qa->item, ContainedBy, qa->actor);
                    ecs_set(it->world, qa->item, InventorySlot, {.slot_index = slot});

                    const Name *item_name = ecs_get(it->world, qa->item, Name);
                    rlc_log_message(it->world, LOG_MSG_ITEM_PICKUP, "You pick up %s", item_name ? item_name->str : "an item");
                    break;
                }

                default: break;
            }
        }

        rlc_action_queue_clear(&ts[i]);
    }
}

/* Register all turn systems */
void rlc_turn_systems_register(ecs_world_t *world) {
    /* 1. Turn Init */
    ecs_entity_t sys_turn_init = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "TurnInitSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {{ .id = ecs_id(TurnState), .inout = EcsInOut }},
        .callback = TurnInitSystem
    });

    /* 2. Timeline (Time Loop) */
    ecs_entity_t sys_timeline = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "TimelineSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        /* Reads energy to manage turns, adds MyTurn tag */
        .callback = TimelineSystem
    });
    ecs_add_pair(world, sys_timeline, EcsDependsOn, sys_turn_init);

    /* 3. Player Path (Requires MyTurn) */
    ecs_entity_t sys_player_path = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "PlayerPathFollowSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {
            { .id = ecs_id(ActionIntent), .inout = EcsInOut },
            { .id = ecs_id(AStarPath), .inout = EcsInOut },
            { .id = ecs_id(PathStatus), .inout = EcsInOut },
            { .id = ecs_id(Player), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = PlayerPathFollowSystem
    });
    ecs_add_pair(world, sys_player_path, EcsDependsOn, sys_timeline);

    /* 4. Player Action (Requires MyTurn) */
    ecs_entity_t sys_player_action = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "PlayerActionSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {
            { .id = ecs_id(ActionIntent), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = PlayerActionSystem
    });
    ecs_add_pair(world, sys_player_action, EcsDependsOn, sys_player_path);

    /* 5. AI Systems (Require MyTurn) */
    ecs_entity_t sys_ai_perception = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "AiPerceptionSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(AiMemory), .inout = EcsInOut },
            { .id = ecs_id(GridPosition), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiPerceptionSystem
    });
    ecs_add_pair(world, sys_ai_perception, EcsDependsOn, sys_timeline);

    ecs_entity_t sys_ai_state = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "AiStateTransitionSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(AiMemory), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiStateTransitionSystem
    });
    ecs_add_pair(world, sys_ai_state, EcsDependsOn, sys_ai_perception);

    ecs_entity_t sys_ai_wandering = ecs_system(world, {
        .entity = ecs_entity(world, { .name = "AiActionWanderingSystem", .add = ecs_ids(ecs_dependson(EcsOnUpdate)) }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(GridPosition), .inout = EcsIn },
            { .id = ecs_id(AiStateWandering), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiActionWanderingSystem
    });
    ecs_add_pair(world, sys_ai_wandering, EcsDependsOn, sys_ai_state);

    ecs_entity_t sys_ai_idle = ecs_system(world, {
        .entity = ecs_entity(world, { .name = "AiActionIdleSystem", .add = ecs_ids(ecs_dependson(EcsOnUpdate)) }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(AiStateIdle), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiActionIdleSystem
    });
    ecs_add_pair(world, sys_ai_idle, EcsDependsOn, sys_ai_state);

    ecs_entity_t sys_ai_hunting = ecs_system(world, {
        .entity = ecs_entity(world, { .name = "AiActionHuntingSystem", .add = ecs_ids(ecs_dependson(EcsOnUpdate)) }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(GridPosition), .inout = EcsIn },
            { .id = ecs_id(AiStateHunting), .inout = EcsInOut },
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiActionHuntingSystem
    });
    ecs_add_pair(world, sys_ai_hunting, EcsDependsOn, sys_ai_state);

    ecs_entity_t sys_ai_fleeing = ecs_system(world, {
        .entity = ecs_entity(world, { .name = "AiActionFleeingSystem", .add = ecs_ids(ecs_dependson(EcsOnUpdate)) }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(GridPosition), .inout = EcsIn },
            { .id = ecs_id(AiStateFleeing), .inout = EcsIn },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiActionFleeingSystem
    });
    ecs_add_pair(world, sys_ai_fleeing, EcsDependsOn, sys_ai_state);

    ecs_entity_t sys_ai_investigating = ecs_system(world, {
        .entity = ecs_entity(world, { .name = "AiActionInvestigatingSystem", .add = ecs_ids(ecs_dependson(EcsOnUpdate)) }),
        .query.terms = {
            { .id = ecs_id(Actor), .inout = EcsIn },
            { .id = ecs_id(Energy), .inout = EcsInOut },
            { .id = ecs_id(GridPosition), .inout = EcsIn },
            { .id = ecs_id(AiStateInvestigating), .inout = EcsInOut },
            { .id = ecs_id(MyTurn), .inout = EcsIn }
        },
        .callback = AiActionInvestigatingSystem
    });
    ecs_add_pair(world, sys_ai_investigating, EcsDependsOn, sys_ai_state);

    /* 6. Action Execution (Runs after everyone has had a chance to queue things) */
    ecs_entity_t sys_action_exec = ecs_system(world, {
        .entity = ecs_entity(world, {
            .name = "ActionExecutionSystem",
            .add = ecs_ids(ecs_dependson(EcsOnUpdate))
        }),
        .query.terms = {{ .id = ecs_id(TurnState), .inout = EcsInOut }},
        .callback = ActionExecutionSystem
    });
    /* Run after all AI and Player systems */
    ecs_add_pair(world, sys_action_exec, EcsDependsOn, sys_ai_investigating);
    ecs_add_pair(world, sys_action_exec, EcsDependsOn, sys_player_action);

    /* Register NPC AI systems (schedule, movement, alert, flee) */
    rlc_npc_ai_systems_register(world);
}
