using UnityEngine;
using UnityEngine.AI;
using System.Collections.Generic;

/// <summary>
/// CashSystemAI — Goal-oriented AI decision layer for Cash System mode.
///
/// ARCHITECTURE: Single-authority utility AI with direct NavMeshAgent control.
///
/// This is the SOLE owner of the NavMeshAgent in Cash System mode.
/// It drives navigation directly (no middleman), handles its own combat,
/// and disables competing controllers (TeamAIController, EnemyAIController)
/// to prevent NavMeshAgent conflicts.
///
/// Each AI evaluates all possible actions every decision tick, scores them
/// using weighted utility functions, and executes the highest-scoring action.
/// Roles bias the weights but any AI can take any action if the score is high enough.
///
/// ROLES:
///   Defender (1 per team) — Biases toward vault defense, enemy interception
///   Collector (2 per team) — Biases toward cash collection, deposit, opportunistic combat
///
/// COORDINATION:
///   Static team registries prevent clustering — AI won't target a bag another
///   teammate is already after. Defenders "claim" the vault area.
///
/// ENVIRONMENT SENSOR:
///   Periodically scans for nearby cash bags, enemies, and vaults using
///   EntityRegistry and TeamMember queries. Cached sorted lists provide
///   fast access for decision-making without per-frame allocations.
/// </summary>
public class CashSystemAI : MonoBehaviour
{
    // ─── Enums ───────────────────────────────────────────────

    public enum AIState
    {
        Idle,
        CollectCash,
        DepositCash,
        AttackEnemy,
        StealFromVault,
        DefendVault,
        Retreat,
        Patrol,
        // ─── Extraction Mode States ──────────────────────────
        SeekVault,          // Navigate to a sealed ExtractionVault
        OpenVault,          // Channel to open a sealed vault ($1,000 reward)
        CarryVault,         // Carrying an opened vault to CashoutStation (encumbered)
        DepositVault,       // Depositing vault at CashoutStation
        DefendCashout,      // Defending an active cashout timer
        AttackCashout,      // Attacking/hacking enemy cashout
        InterceptCarrier    // Hunting an enemy carrying a vault
    }

    public enum AIRole
    {
        Collector,
        Defender
    }

    // ─── Inspector Fields ────────────────────────────────────

    [Header("Role")]
    [SerializeField] private AIRole role = AIRole.Collector;

    [Header("State (read-only)")]
    [SerializeField] private AIState currentState = AIState.Idle;

    [Header("Detection Ranges")]
    [SerializeField] private float bagScanRadius = 500f;     // Full-map awareness (was 50 — too small for large maps)
    [SerializeField] private float enemyDetectRange = 40f;   // COMBAT FIX: Was 15 — too small, AI ignored nearby enemies
    [SerializeField] private float vaultDefenseRadius = 12f;
    [SerializeField] private float depositRange = 3.5f;
    [SerializeField] private float bagPickupRange = 2.5f;

    [Header("Thresholds")]
    [SerializeField] private float depositCashThreshold = 200f;  // Collect 2+ bags before depositing
    [SerializeField] private float retreatHealthThreshold = 25f;
    [SerializeField] private float maxChaseDistance = 20f;

    [Header("Timing")]
    [SerializeField] private float decisionInterval = 0.3f;
    [SerializeField] private float stateMinDuration = 0.6f;
    [SerializeField] private float sensorInterval = 0.5f;

    [Header("Combat")]
    [SerializeField] private float attackRange = 2.5f;
    [SerializeField] private float attackCooldown = 1.2f;
    [SerializeField] private float combatApproachSpeed = 1f;
    [SerializeField] private float aggressionLevel = 0.8f;   // 0-1, how eager AI is to fight (0.8 = very aggressive)

    [Header("Patrol")]
    [SerializeField] private float patrolRadius = 15f;
    [SerializeField] private float patrolPauseMin = 0.5f;
    [SerializeField] private float patrolPauseMax = 2f;

    [Header("Extraction Mode")]
    [SerializeField] private float vaultInteractRange = 4f;
    [SerializeField] private float cashoutStationRange = 5f;
    [SerializeField] private float carrierInterceptRange = 40f;

    [Header("Difficulty")]
    [SerializeField] private int aiLevel = 1;

    [Header("Debug")]
    [SerializeField] private bool showDebugGizmos = true;
    [SerializeField] private string debugLastDecision = "";

    // ─── Cached Components ───────────────────────────────────

    private CashCarrier cashCarrier;
    private HealthNew health;
    private CharacterAIController aiController;
    private NavMeshAgent navAgent;
    private TeamMember teamMember;
    private CharacterAttacks characterAttacks;
    private Animator animator;

    // ─── Runtime State ───────────────────────────────────────

    private float lastDecisionTime;
    private float stateStartTime;
    private Transform currentTarget;
    private TeamVault myVault;
    private TeamVault enemyVault;
    private Vector3 myVaultGroundPos;      // Vault position projected to NavMesh level
    private Vector3 enemyVaultGroundPos;   // Vault position projected to NavMesh level
    private string myTeamTag;
    private float lastAttackTime;
    private float lastHeartbeatTime;       // For periodic diagnostic logging

    // Patrol
    private Vector3 patrolDestination;
    private bool hasPatrolDestination;
    private float patrolPauseEndTime;

    // Coordination — which bag this AI is targeting (prevents clustering)
    private CashBag claimedBag;

    // ─── Extraction Mode State ───────────────────────────────
    private CashoutStation myStation;
    private CashoutStation enemyStation;
    private Vector3 myStationGroundPos;
    private Vector3 enemyStationGroundPos;
    private ExtractionVault targetVault;     // Vault we're currently going after
    private bool isCarryingVault;            // Are we carrying an ExtractionVault?
    private GameObject lastDamageAttacker;   // Who last hit us (for reactive fight-back)
    private float lastDamageTime;            // When we were last hit

    // ─── Environment Sensor Cache ────────────────────────────

    private float lastSensorTime;
    private readonly List<SensedBag> sensedBags = new List<SensedBag>(16);
    private readonly List<SensedEnemy> sensedEnemies = new List<SensedEnemy>(8);

    private struct SensedBag
    {
        public CashBag bag;
        public float distance;
        public float score;
    }

    private struct SensedEnemy
    {
        public GameObject enemy;
        public float distance;
        public float carriedCash;
        public float healthPercent;
    }

    // ─── Static Team Coordination ────────────────────────────

    // Track which bags are claimed by which AI (per team) to prevent clustering
    private static readonly Dictionary<CashBag, CashSystemAI> _claimedBags = new Dictionary<CashBag, CashSystemAI>();

    // Track all active CashSystemAI instances per team for role assignment
    private static readonly List<CashSystemAI> _allAIs = new List<CashSystemAI>(8);

    // ─── Properties ──────────────────────────────────────────

    public AIState CurrentState => currentState;
    public AIRole CurrentRole => role;
    public Transform CurrentTarget => currentTarget;

    // ─── Lifecycle ───────────────────────────────────────────

    private void Start()
    {
        if (SelectionOptions.Instance == null || !SelectionOptions.Instance.isCashSystemSelected)
        {
            enabled = false;
            return;
        }

        CacheComponents();
        FindVaults();
        ApplyDifficultyScaling();
        DisableCompetingControllers();

        _allAIs.Add(this);

        // Initial sensor scan so we have data immediately
        ScanEnvironment();

        // Subscribe to damage events for reactive fight-back
        GameEvents.OnDamageDealt += OnDamageTaken;

        DevLog.Log($"[CashSystemAI] Initialized on {gameObject.name}, Team: {myTeamTag}, " +
                   $"NavAgent: {navAgent != null}, CharAttacks: {characterAttacks != null}");
    }

    /// <summary>
    /// SINGLE-AUTHORITY: Disable TeamAIController and EnemyAIController so they
    /// cannot interfere with NavMeshAgent. CashSystemAI is the SOLE movement authority.
    /// </summary>
    private void DisableCompetingControllers()
    {
        var teamAI = GetComponent<TeamAIController>();
        if (teamAI != null)
        {
            teamAI.enabled = false;
            DevLog.Log($"[CashSystemAI] Disabled TeamAIController on {gameObject.name}");
        }

        var enemyAI = GetComponent<EnemyAIController>();
        if (enemyAI != null)
        {
            enemyAI.enabled = false;
            DevLog.Log($"[CashSystemAI] Disabled EnemyAIController on {gameObject.name}");
        }
    }

    private void OnDestroy()
    {
        _allAIs.Remove(this);
        UnclaimBag();
        GameEvents.OnDamageDealt -= OnDamageTaken;
    }

    /// <summary>
    /// Called by CashSystemManager after all AI are spawned.
    /// Deterministically assigns 1 Defender + 2 Collectors per team.
    /// </summary>
    public static void AssignTeamRoles()
    {
        // Group AIs by team
        var playerTeam = new List<CashSystemAI>();
        var enemyTeam = new List<CashSystemAI>();

        for (int i = 0; i < _allAIs.Count; i++)
        {
            if (_allAIs[i] == null) continue;
            if (_allAIs[i].myTeamTag == "Player")
                playerTeam.Add(_allAIs[i]);
            else
                enemyTeam.Add(_allAIs[i]);
        }

        AssignRolesForTeam(playerTeam);
        AssignRolesForTeam(enemyTeam);
    }

    private static void AssignRolesForTeam(List<CashSystemAI> team)
    {
        if (team.Count == 0) return;

        // First AI = Defender, rest = Collectors
        // Pick the AI closest to the vault as Defender
        CashSystemAI bestDefender = null;
        float bestDist = float.MaxValue;

        for (int i = 0; i < team.Count; i++)
        {
            if (team[i].myVault == null) continue;
            float d = Vector3.Distance(team[i].transform.position, team[i].myVaultGroundPos);
            if (d < bestDist)
            {
                bestDist = d;
                bestDefender = team[i];
            }
        }

        for (int i = 0; i < team.Count; i++)
        {
            if (team[i] == bestDefender)
            {
                team[i].role = AIRole.Defender;
                DevLog.Log($"[CashSystemAI] {team[i].gameObject.name} assigned DEFENDER");
            }
            else
            {
                team[i].role = AIRole.Collector;
                DevLog.Log($"[CashSystemAI] {team[i].gameObject.name} assigned COLLECTOR");
            }
        }
    }

    /// <summary>Clear all static state (call from GameEvents.ClearAll or scene unload).</summary>
    public static void ClearAll()
    {
        _claimedBags.Clear();
        _allAIs.Clear();
    }

    private void CacheComponents()
    {
        cashCarrier = GetComponent<CashCarrier>();
        health = GetComponent<HealthNew>();
        aiController = GetComponent<CharacterAIController>();
        navAgent = GetComponent<NavMeshAgent>();
        teamMember = GetComponent<TeamMember>();
        characterAttacks = GetComponent<CharacterAttacks>();
        animator = GetComponent<Animator>();
        myTeamTag = gameObject.tag;
    }

    private void FindVaults()
    {
        var vaults = EntityRegistry<TeamVault>.GetAll();
        for (int i = 0; i < vaults.Count; i++)
        {
            if (vaults[i] == null) continue;
            if (vaults[i].TeamTag == myTeamTag) myVault = vaults[i];
            else enemyVault = vaults[i];
        }

        // Fallback
        if (myVault == null || enemyVault == null)
        {
            TeamVault[] fallback = FindObjectsOfType<TeamVault>();
            foreach (var v in fallback)
            {
                if (v.TeamTag == myTeamTag) myVault = v;
                else enemyVault = v;
            }
        }

        // Project vault positions to NavMesh ground level.
        // Vaults can be floating high above the NavMesh (Y=15+).
        // NavMeshAgent can't path to those positions, so we cache
        // the nearest NavMesh point at the vault's XZ location.
        if (myVault != null)
            myVaultGroundPos = ProjectToNavMesh(myVault.transform.position);
        if (enemyVault != null)
            enemyVaultGroundPos = ProjectToNavMesh(enemyVault.transform.position);

        // Find CashoutStations for extraction mode
        var stations = EntityRegistry<CashoutStation>.GetAll();
        for (int i = 0; i < stations.Count; i++)
        {
            if (stations[i] == null) continue;
            if (stations[i].TeamTag == myTeamTag) myStation = stations[i];
            else enemyStation = stations[i];
        }
        if (myStation != null)
            myStationGroundPos = ProjectToNavMesh(myStation.transform.position);
        if (enemyStation != null)
            enemyStationGroundPos = ProjectToNavMesh(enemyStation.transform.position);

        DevLog.Log($"[CashSystemAI] {gameObject.name} vaults: my={myVault != null} at {myVaultGroundPos}, " +
                   $"enemy={enemyVault != null} at {enemyVaultGroundPos}, " +
                   $"myStation={myStation != null}, enemyStation={enemyStation != null}");
    }

    /// <summary>
    /// Project a world position down to the nearest NavMesh surface.
    /// Essential for vault positions which may be 15+ meters above the NavMesh.
    /// </summary>
    private Vector3 ProjectToNavMesh(Vector3 worldPos)
    {
        // Try at character's Y level first (most likely NavMesh surface)
        Vector3 probePos = new Vector3(worldPos.x, transform.position.y, worldPos.z);
        NavMeshHit hit;
        if (NavMesh.SamplePosition(probePos, out hit, 30f, NavMesh.AllAreas))
            return hit.position;

        // Fallback: try from the original position with large radius
        if (NavMesh.SamplePosition(worldPos, out hit, 50f, NavMesh.AllAreas))
            return hit.position;

        // Last resort: return XZ of vault at character Y
        return probePos;
    }

    private void ApplyDifficultyScaling()
    {
        if (aiLevel > 1)
        {
            float scale = 1f + (aiLevel - 1) * 0.15f;
            decisionInterval = Mathf.Max(0.15f, decisionInterval / (1f + (aiLevel - 1) * 0.2f));
            if (navAgent != null)
                navAgent.speed *= 1f + (aiLevel - 1) * 0.1f;
        }
    }

    // ─── Main Update Loop ────────────────────────────────────

    private void Update()
    {
        // ── Environment Sensor: periodic scan ──
        if (Time.time - lastSensorTime >= sensorInterval)
        {
            lastSensorTime = Time.time;
            ScanEnvironment();
        }

        // ── Sync walk animation from NavMeshAgent velocity ──
        SyncAnimation();

        // ── Decision tick ──
        if (Time.time - lastDecisionTime < decisionInterval)
            return;

        lastDecisionTime = Time.time + Random.Range(-0.05f, 0.05f); // Stagger

        // Lazy vault re-find
        if (myVault == null || enemyVault == null)
            FindVaults();

        // Periodic diagnostic heartbeat (every 5s)
        if (Time.time - lastHeartbeatTime > 5f)
        {
            lastHeartbeatTime = Time.time;
            DevLog.Log($"[CashSystemAI] ♥ {gameObject.name}: state={currentState}, role={role}, " +
                       $"pos={transform.position}, navOnMesh={navAgent != null && navAgent.isOnNavMesh}, " +
                       $"navSpeed={navAgent?.speed:F1}, vel={navAgent?.velocity.magnitude:F1}, " +
                       $"bags={sensedBags.Count}, enemies={sensedEnemies.Count}");
        }

        // ALWAYS check retreat first (survival override)
        if (ShouldRetreat())
        {
            SetState(AIState.Retreat);
            ExecuteCurrentState();
            return;
        }

        // Don't switch state too fast (but always re-execute for movement)
        if (Time.time - stateStartTime < stateMinDuration && currentState != AIState.Idle)
        {
            ExecuteCurrentState();
            return;
        }

        // Evaluate all possible actions and pick the best
        EvaluateAndDecide();
        ExecuteCurrentState();
    }

    /// <summary>
    /// Sync walk/run/idle animation from NavMeshAgent velocity.
    /// Runs every frame for smooth blending.
    /// </summary>
    private void SyncAnimation()
    {
        if (animator == null) return;

        if (navAgent != null && navAgent.isActiveAndEnabled && !navAgent.isStopped && navAgent.hasPath)
        {
            float spd = navAgent.velocity.magnitude / Mathf.Max(navAgent.speed, 0.01f);
            animator.SetFloat("Speed", spd > 0.05f ? Mathf.Clamp01(spd) : 0f);
        }
        else
        {
            animator.SetFloat("Speed", 0f);
        }
    }

    // ─── Decision System (Utility AI) ────────────────────────

    private void EvaluateAndDecide()
    {
        float bestScore = -1f;
        AIState bestState = AIState.Patrol; // Never idle — patrol as minimum
        Transform bestTarget = null;
        string bestReason = "default patrol";

        bool isCarrying = cashCarrier != null && cashCarrier.CarriedCash >= 0.01f;
        bool combatEnabled = IsCombatEnabled();
        float myHealth = health != null ? health.currentHealth : 100f;

        // ── Check if extraction vaults exist — if so, use extraction logic ──
        bool hasExtractionVaults = EntityRegistry<ExtractionVault>.GetAll().Count > 0 || myStation != null;
        isCarryingVault = IsCarryingExtractionVault();

        if (hasExtractionVaults)
        {
            EvaluateExtractionActions(ref bestScore, ref bestState, ref bestTarget, ref bestReason,
                                      combatEnabled, myHealth);
        }
        else
        {
            // ── Legacy bag-collection mode ──
            EvaluateBagCollectionActions(ref bestScore, ref bestState, ref bestTarget, ref bestReason,
                                         isCarrying, combatEnabled, myHealth);
        }

        // ── ALWAYS: Attack enemies in range (applies to both modes) ──
        if (combatEnabled && !isCarryingVault) // Can't attack while carrying vault
        {
            GameObject enemy = FindBestAttackTarget();
            if (enemy != null)
            {
                float dist = Vector3.Distance(transform.position, enemy.transform.position);
                if (dist < enemyDetectRange)
                {
                    // BOOSTED: Base score now 50 (was 0) — combat is a REAL option
                    float score = 50f;
                    // BOOSTED: Proximity bonus now 40 (was 20) — close enemies are priority
                    score += 40f * (1f - dist / enemyDetectRange);

                    CashCarrier enemyCarrier = enemy.GetComponent<CashCarrier>();
                    if (enemyCarrier != null && enemyCarrier.CarriedCash > 0)
                        score += 40f + enemyCarrier.CarriedCash * 0.2f;

                    HealthNew enemyHP = enemy.GetComponent<HealthNew>();
                    if (enemyHP != null && enemyHP.currentHealth < 40)
                        score += 30f;

                    if (role == AIRole.Defender && myVault != null)
                    {
                        float enemyToVault = Vector3.Distance(enemy.transform.position, myVaultGroundPos);
                        if (enemyToVault < vaultDefenseRadius)
                            score += 80f;
                    }

                    if (role == AIRole.Collector && isCarrying)
                        score *= 0.4f;

                    // In extraction mode, boost priority for intercepting vault carriers
                    if (hasExtractionVaults && IsEnemyCarryingVault(enemy))
                        score += 150f; // Very high — they have THE vault

                    // REACTIVE: Massive boost if we were recently attacked
                    if (lastDamageAttacker != null && Time.time - lastDamageTime < 5f)
                        score += 60f; // Being hit = FIGHT BACK

                    // Apply aggression multiplier (inspector-tunable)
                    score *= aggressionLevel;

                    if (score > bestScore)
                    {
                        bestScore = score;
                        bestState = isCarryingVault ? AIState.CarryVault : AIState.AttackEnemy;
                        bestTarget = enemy.transform;
                        bestReason = $"attack {enemy.name} (score {score:F0})";
                    }
                }
            }
        }

        // ── ALWAYS: Defender patrol (applies to both modes) ──
        if (role == AIRole.Defender && !isCarryingVault)
        {
            Vector3 defenseCenter = myStation != null ? myStationGroundPos :
                                    (myVault != null ? myVaultGroundPos : transform.position);
            float distToBase = Vector3.Distance(transform.position, defenseCenter);
            if (distToBase > vaultDefenseRadius && bestState == AIState.Patrol)
            {
                bestScore = 25f;
                bestState = AIState.DefendVault;
                bestTarget = myStation != null ? myStation.transform : (myVault != null ? myVault.transform : null);
                bestReason = "return to base";
            }
        }

        currentTarget = bestTarget;
        debugLastDecision = bestReason;
        SetState(bestState);
    }

    /// <summary>Extraction-mode decision evaluation.</summary>
    private void EvaluateExtractionActions(ref float bestScore, ref AIState bestState,
        ref Transform bestTarget, ref string bestReason, bool combatEnabled, float myHealth)
    {
        // ── 1. CARRY VAULT TO CASHOUT STATION (highest priority if carrying) ──
        if (isCarryingVault && myStation != null)
        {
            float score = 300f; // Always go deposit when carrying
            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.CarryVault;
                bestTarget = myStation.transform;
                bestReason = "carrying vault to cashout station";
            }
            return; // Don't evaluate other actions while carrying
        }

        // ── 2. DEFEND ACTIVE CASHOUT (our station is cashing out) ──
        if (myStation != null && myStation.IsCashingOut)
        {
            float score = 150f;
            if (role == AIRole.Defender) score += 100f;

            // More valuable cashouts = more worth defending
            score += myStation.CashoutRemainingValue * 0.01f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.DefendCashout;
                bestTarget = myStation.transform;
                bestReason = $"defend cashout ({myStation.CashoutProgress:P0})";
            }
        }

        // ── 3. ATTACK/HACK ENEMY CASHOUT (enemy station is cashing out) ──
        if (combatEnabled && enemyStation != null && enemyStation.IsCashingOut)
        {
            float dist = Vector3.Distance(transform.position, enemyStationGroundPos);
            float score = 100f;

            // Undefended = go for it
            int enemies = CountEnemiesNear(enemyStationGroundPos, 15f);
            if (enemies == 0) score += 80f;
            else if (enemies == 1 && myHealth > 60f) score += 30f;
            else score -= 40f;

            // Closer = more attractive
            score += 30f * Mathf.Clamp01(1f - dist / 100f);

            // Collectors more aggressive at hacking
            if (role == AIRole.Collector) score += 20f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.AttackCashout;
                bestTarget = enemyStation.transform;
                bestReason = $"hack enemy cashout (defenders:{enemies})";
            }
        }

        // ── 4. INTERCEPT ENEMY VAULT CARRIER ──
        if (combatEnabled)
        {
            var enemyCarriedVault = FindEnemyCarryingVaultObj();
            if (enemyCarriedVault != null)
            {
                float dist = Vector3.Distance(transform.position, enemyCarriedVault.transform.position);
                if (dist < carrierInterceptRange)
                {
                    float score = 120f;
                    score += 40f * Mathf.Clamp01(1f - dist / carrierInterceptRange);
                    if (role == AIRole.Defender) score += 30f;

                    if (score > bestScore)
                    {
                        bestScore = score;
                        bestState = AIState.InterceptCarrier;
                        bestTarget = enemyCarriedVault.transform;
                        bestReason = $"intercept vault carrier ({dist:F0}m)";
                    }
                }
            }
        }

        // ── 5. PICK UP AVAILABLE VAULT (opened/dropped, not carried) ──
        ExtractionVault avail = ExtractionVault.FindNearestAvailable(transform.position);
        if (avail != null)
        {
            float dist = Vector3.Distance(transform.position, avail.transform.position);
            float score = 90f;
            score += 30f * Mathf.Clamp01(1f - dist / 100f);

            // Dropped vaults are urgent (they despawn)
            if (avail.IsDropped) score += 25f;

            if (role == AIRole.Collector) score += 20f;
            if (role == AIRole.Defender) score *= 0.4f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.SeekVault;
                bestTarget = avail.transform;
                bestReason = $"pick up vault ({dist:F0}m)";
                targetVault = avail;
            }
        }

        // ── 6. SEEK SEALED VAULT (go open one) ──
        ExtractionVault sealedVault = ExtractionVault.FindNearestSealed(transform.position);
        if (sealedVault != null)
        {
            float dist = Vector3.Distance(transform.position, sealedVault.transform.position);
            float score = 70f;
            score += 20f * Mathf.Clamp01(1f - dist / 100f);

            if (role == AIRole.Collector) score += 15f;
            if (role == AIRole.Defender) score *= 0.3f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.SeekVault;
                bestTarget = sealedVault.transform;
                bestReason = $"open sealed vault ({dist:F0}m)";
                targetVault = sealedVault;
            }
        }

        // ── 7. LEGACY: STEAL FROM ENEMY VAULT (fallback) ──
        if (combatEnabled && enemyVault != null && enemyVault.StoredCash > 50f)
        {
            float dist = Vector3.Distance(transform.position, enemyVaultGroundPos);
            int enemies = CountEnemiesNear(enemyVaultGroundPos, vaultDefenseRadius);
            float score = 10f + (enemyVault.StoredCash / 500f) * 15f;
            if (enemies == 0) score += 30f;
            else score -= 30f;
            if (role == AIRole.Defender) score *= 0.1f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.StealFromVault;
                bestTarget = enemyVault.transform;
                bestReason = $"steal from vault (defenders:{enemies})";
            }
        }
    }

    /// <summary>Legacy bag-collection decision evaluation (pre-extraction mode).</summary>
    private void EvaluateBagCollectionActions(ref float bestScore, ref AIState bestState,
        ref Transform bestTarget, ref string bestReason, bool isCarrying, bool combatEnabled, float myHealth)
    {
        if (myVault != null && myVault.IsBeingStolen)
        {
            float score = 200f; // Critical priority
            if (role == AIRole.Defender) score += 100f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.DefendVault;
                bestTarget = myVault.transform;
                bestReason = "vault under attack!";
            }
        }

        // ── 2. DEPOSIT CASH (if carrying) ──
        if (isCarrying && myVault != null)
        {
            float carried = cashCarrier.CarriedCash;
            int bags = cashCarrier.BagCount;
            float score = 20f + (carried / depositCashThreshold) * 50f;

            // Only 1 bag and more available? Low urgency to deposit
            if (bags == 1 && cashCarrier.CanPickUp())
            {
                CashBag extraBag = FindBestUnclaimedBag();
                if (extraBag != null)
                    score *= 0.3f; // Heavily suppress — go grab another bag first
            }

            // Full bags = desperate to deposit
            if (bags >= 3) score += 80f;

            // Carrying + enemies nearby = deposit urgently
            GameObject nearEnemy = FindNearestEnemy();
            if (nearEnemy != null)
            {
                float enemyDist = Vector3.Distance(transform.position, nearEnemy.transform.position);
                if (enemyDist < enemyDetectRange)
                    score += 40f * (1f - enemyDist / enemyDetectRange);
            }

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.DepositCash;
                bestTarget = myVault.transform;
                bestReason = $"deposit ${carried:F0}";
            }
        }

        // ── 3. ATTACK ENEMY (context-dependent) ──
        if (combatEnabled)
        {
            GameObject enemy = FindBestAttackTarget();
            if (enemy != null)
            {
                float dist = Vector3.Distance(transform.position, enemy.transform.position);
                if (dist < enemyDetectRange)
                {
                    // BOOSTED: Base score now 50 (was 0) — AI actually considers fighting
                    float score = 50f;

                    // BOOSTED: Proximity bonus now 40 (was 20)
                    score += 40f * (1f - dist / enemyDetectRange);

                    // Enemy carrying cash = juicy target
                    CashCarrier enemyCarrier = enemy.GetComponent<CashCarrier>();
                    if (enemyCarrier != null && enemyCarrier.CarriedCash > 0)
                        score += 40f + enemyCarrier.CarriedCash * 0.2f;

                    // Enemy is weak = finish them
                    HealthNew enemyHP = enemy.GetComponent<HealthNew>();
                    if (enemyHP != null && enemyHP.currentHealth < 40)
                        score += 30f;

                    // Defender: boost if enemy near vault
                    if (role == AIRole.Defender && myVault != null)
                    {
                        float enemyToVault = Vector3.Distance(enemy.transform.position, myVaultGroundPos);
                        if (enemyToVault < vaultDefenseRadius)
                            score += 80f; // Very high — they're in our territory
                    }

                    // Collector carrying cash = less eager to fight
                    if (role == AIRole.Collector && isCarrying)
                        score *= 0.4f;

                    // REACTIVE: Massive boost if recently attacked
                    if (lastDamageAttacker != null && Time.time - lastDamageTime < 5f)
                        score += 60f;

                    // Apply aggression multiplier
                    score *= aggressionLevel;

                    if (score > bestScore)
                    {
                        bestScore = score;
                        bestState = AIState.AttackEnemy;
                        bestTarget = enemy.transform;
                        bestReason = $"attack {enemy.name} (score {score:F0})";
                    }
                }
            }
        }

        // ── 4. COLLECT CASH (Collector bread-and-butter) ──
        if (cashCarrier != null && cashCarrier.CanPickUp() && !isCarrying)
        {
            CashBag bag = FindBestUnclaimedBag();
            if (bag != null)
            {
                float dist = Vector3.Distance(transform.position, bag.transform.position);
                float score = 60f;

                // Closer bags score higher (soft curve over 120m instead of bagScanRadius)
                score += 30f * Mathf.Clamp01(1f - dist / 120f);

                // Higher value bags
                score += bag.CashValue * 0.1f;

                // Hot drops are extra valuable
                if (bag.IsHotDrop) score += 25f;

                // Dropped bags from killed enemies
                if (bag.IsDropped) score += 15f;

                // Defender: less interested in collection
                if (role == AIRole.Defender)
                    score *= 0.3f;

                if (score > bestScore)
                {
                    bestScore = score;
                    bestState = AIState.CollectCash;
                    bestTarget = bag.transform;
                    bestReason = $"collect bag (${bag.CashValue}, {dist:F0}m away)";
                }
            }
        }
        // Also check if already carrying but can pick up more
        else if (cashCarrier != null && cashCarrier.CanPickUp() && isCarrying && cashCarrier.BagCount < 3)
        {
            CashBag bag = FindBestUnclaimedBag();
            if (bag != null)
            {
                float dist = Vector3.Distance(transform.position, bag.transform.position);
                // Grab nearby bags when already carrying (expanded range from 10 to 30m)
                if (dist < 30f)
                {
                    float score = 50f + 25f * Mathf.Clamp01(1f - dist / 30f);
                    if (bag.IsDropped) score += 15f;

                    if (role == AIRole.Defender) score *= 0.2f;

                    if (score > bestScore)
                    {
                        bestScore = score;
                        bestState = AIState.CollectCash;
                        bestTarget = bag.transform;
                        bestReason = $"grab extra bag (${bag.CashValue}, {dist:F0}m)";
                    }
                }
            }
        }

        // ── 5. STEAL FROM ENEMY VAULT (opportunistic Collector action) ──
        if (combatEnabled && enemyVault != null && enemyVault.StoredCash > 50f && !isCarrying)
        {
            float dist = Vector3.Distance(transform.position, enemyVaultGroundPos);
            int enemiesNearVault = CountEnemiesNear(enemyVaultGroundPos, vaultDefenseRadius);

            float score = 10f + (enemyVault.StoredCash / 500f) * 20f;

            // Undefended vault = go for it
            if (enemiesNearVault == 0) score += 40f;
            else if (enemiesNearVault == 1 && myHealth > 70f) score += 10f;
            else score -= 30f; // Too risky

            // Defender: rarely steals
            if (role == AIRole.Defender) score *= 0.15f;

            // Distance penalty
            score -= dist * 0.5f;

            if (score > bestScore)
            {
                bestScore = score;
                bestState = AIState.StealFromVault;
                bestTarget = enemyVault.transform;
                bestReason = $"steal (defenders: {enemiesNearVault})";
            }
        }

        // ── 6. DEFENDER PATROL (stay near vault) ──
        if (role == AIRole.Defender && myVault != null)
        {
            float distToVault = Vector3.Distance(transform.position, myVaultGroundPos);
            if (distToVault > vaultDefenseRadius && bestState == AIState.Patrol)
            {
                // Return to vault area
                bestScore = 25f;
                bestState = AIState.DefendVault;
                bestTarget = myVault.transform;
                bestReason = "return to vault";
            }
        }
    }

    private bool ShouldRetreat()
    {
        if (health == null) return false;
        if (health.currentHealth > retreatHealthThreshold) return false;

        // Only retreat if there's actually danger nearby
        for (int i = 0; i < sensedEnemies.Count; i++)
        {
            if (sensedEnemies[i].distance < enemyDetectRange)
                return true;
        }
        return false;
    }

    private bool IsCombatEnabled()
    {
        if (aiController != null) return aiController.attack;
        return true;
    }

    // ─── Environment Sensor ──────────────────────────────────

    /// <summary>
    /// Scans the environment for cash bags, enemies, and vaults.
    /// Results are cached in sorted lists for fast access by the decision system.
    /// This is the "sensor" system — AI becomes aware of its environment through this.
    /// </summary>
    private void ScanEnvironment()
    {
        ScanBags();
        ScanEnemies();
    }

    private void ScanBags()
    {
        sensedBags.Clear();
        var bags = EntityRegistry<CashBag>.GetAll();

        for (int i = 0; i < bags.Count; i++)
        {
            CashBag bag = bags[i];
            if (bag == null) continue;
            if (!bag.IsAvailable && !bag.IsDropped) continue;

            float dist = Vector3.Distance(transform.position, bag.transform.position);
            // No hard distance cutoff — AI sees ALL bags on the map.
            // Distance is used for scoring only, not filtering.

            float score = 50f;
            // Closer bags score higher, but use a soft curve (not a hard wall)
            // Bags within 30m get full proximity bonus, farther bags get diminishing bonus
            float proximityBonus = 40f * Mathf.Clamp01(1f - dist / 120f);
            score += proximityBonus;
            score += bag.CashValue * 0.1f;
            if (bag.IsDropped) score += 20f;
            if (bag.IsHotDrop) score += 30f;

            sensedBags.Add(new SensedBag { bag = bag, distance = dist, score = score });
        }

        // Sort by score descending (best bags first)
        sensedBags.Sort((a, b) => b.score.CompareTo(a.score));
    }

    private void ScanEnemies()
    {
        sensedEnemies.Clear();

        foreach (TeamMember member in TeamMember.AllMembers)
        {
            if (member == null || member.gameObject == gameObject) continue;
            if (member.gameObject.CompareTag(myTeamTag)) continue;

            HealthNew hp = member.GetComponent<HealthNew>();
            if (hp != null && hp.isDead) continue;

            float dist = Vector3.Distance(transform.position, member.transform.position);
            if (dist > Mathf.Max(enemyDetectRange, bagScanRadius)) continue;

            CashCarrier ec = member.GetComponent<CashCarrier>();
            float carried = ec != null ? ec.CarriedCash : 0f;
            float healthPct = hp != null ? ((float)hp.currentHealth / Mathf.Max(hp.maxHealth, 1)) : 1f;

            sensedEnemies.Add(new SensedEnemy
            {
                enemy = member.gameObject,
                distance = dist,
                carriedCash = carried,
                healthPercent = healthPct
            });
        }

        // Sort by distance ascending (closest first)
        sensedEnemies.Sort((a, b) => a.distance.CompareTo(b.distance));
    }

    // ─── State Execution ─────────────────────────────────────

    private void SetState(AIState newState)
    {
        if (newState != currentState)
        {
            // Clean up old state
            if (currentState == AIState.CollectCash)
                UnclaimBag();

            currentState = newState;
            stateStartTime = Time.time;
            hasPatrolDestination = false;
            DevLog.Log($"[CashSystemAI] {gameObject.name} → {newState} ({debugLastDecision})");
        }
    }

    private void ExecuteCurrentState()
    {
        switch (currentState)
        {
            case AIState.Idle: ExecuteIdle(); break;
            case AIState.CollectCash: ExecuteCollect(); break;
            case AIState.DepositCash: ExecuteDeposit(); break;
            case AIState.AttackEnemy: ExecuteAttack(); break;
            case AIState.StealFromVault: ExecuteSteal(); break;
            case AIState.DefendVault: ExecuteDefend(); break;
            case AIState.Retreat: ExecuteRetreat(); break;
            case AIState.Patrol: ExecutePatrol(); break;
            // Extraction mode states
            case AIState.SeekVault: ExecuteSeekVault(); break;
            case AIState.OpenVault: ExecuteOpenVault(); break;
            case AIState.CarryVault: ExecuteCarryVault(); break;
            case AIState.DepositVault: ExecuteDepositVault(); break;
            case AIState.DefendCashout: ExecuteDefendCashout(); break;
            case AIState.AttackCashout: ExecuteAttackCashout(); break;
            case AIState.InterceptCarrier: ExecuteInterceptCarrier(); break;
        }
    }

    // ─── State Implementations ───────────────────────────────

    private void ExecuteIdle()
    {
        // Idle should never persist — immediately transition
        SetState(AIState.Patrol);
    }

    private void ExecuteCollect()
    {
        // PROACTIVE: If an enemy is very close while collecting, engage them
        if (IsCombatEnabled() && !isCarryingVault)
        {
            GameObject enemy = FindBestAttackTarget();
            if (enemy != null)
            {
                float enemyDist = Vector3.Distance(transform.position, enemy.transform.position);
                // Fight enemies within close range (60% of detect range) while collecting
                if (enemyDist < enemyDetectRange * 0.6f)
                {
                    currentTarget = enemy.transform;
                    SetState(AIState.AttackEnemy);
                    debugLastDecision = $"ENGAGE {enemy.name} near bag ({enemyDist:F0}m)";
                    return;
                }
            }
        }

        if (currentTarget == null)
        {
            SetState(AIState.Patrol);
            return;
        }

        CashBag bag = currentTarget.GetComponent<CashBag>();
        if (bag == null || (!bag.IsAvailable && !bag.IsDropped))
        {
            // Bag taken or destroyed — find another
            UnclaimBag();
            CashBag newBag = FindBestUnclaimedBag();
            if (newBag != null)
            {
                ClaimBag(newBag);
                currentTarget = newBag.transform;
            }
            else
            {
                SetState(AIState.Patrol);
                return;
            }
        }

        float dist = Vector3.Distance(transform.position, currentTarget.position);

        // Close enough to pick up
        if (dist <= bagPickupRange)
        {
            if (cashCarrier != null && cashCarrier.CanPickUp() && bag != null)
            {
                bag.PickUp(cashCarrier);
                cashCarrier.OnBagPickedUp(bag);
                DevLog.Log($"[CashSystemAI] {gameObject.name} picked up bag (${bag.CashValue}), total ${cashCarrier.CarriedCash:F0}");

                UnclaimBag();

                // Decide next: deposit or grab another?
                if (cashCarrier.BagCount >= 2 || cashCarrier.CarriedCash >= depositCashThreshold)
                {
                    SetState(AIState.DepositCash);
                    currentTarget = myVault != null ? myVault.transform : null;
                }
                else
                {
                    // Try to find another nearby bag
                    CashBag next = FindBestUnclaimedBag();
                    if (next != null && Vector3.Distance(transform.position, next.transform.position) < 15f)
                    {
                        ClaimBag(next);
                        currentTarget = next.transform;
                    }
                    else
                    {
                        SetState(AIState.DepositCash);
                        currentTarget = myVault != null ? myVault.transform : null;
                    }
                }
            }
            return;
        }

        // Move toward bag
        MoveToward(currentTarget.position);
    }

    private void ExecuteDeposit()
    {
        if (myVault == null)
        {
            FindVaults();
            if (myVault == null) { SetState(AIState.Patrol); return; }
        }

        // Check we still have cash
        if (cashCarrier == null || cashCarrier.CarriedCash < 0.01f)
        {
            DevLog.Log($"[CashSystemAI] {gameObject.name} no cash to deposit, back to work");
            SetState(AIState.Idle);
            return;
        }

        float dist = Vector3.Distance(transform.position, myVaultGroundPos);

        // Close enough — deposit
        if (dist <= depositRange)
        {
            float deposited = cashCarrier.DepositToVault(myVault);
            DevLog.Log($"[CashSystemAI] {gameObject.name} deposited ${deposited:F0}!");
            SetState(AIState.Idle);
            return;
        }

        MoveToward(myVaultGroundPos);
    }

    private void ExecuteAttack()
    {
        if (currentTarget == null || !IsCombatEnabled())
        {
            SetState(AIState.Idle);
            return;
        }

        // Check target still alive
        HealthNew targetHP = currentTarget.GetComponent<HealthNew>();
        if (targetHP != null && targetHP.isDead)
        {
            DevLog.Log($"[CashSystemAI] {gameObject.name} target died, scanning");
            SetState(AIState.Idle);
            return;
        }

        float dist = Vector3.Distance(transform.position, currentTarget.position);

        // Don't chase too far (especially Defenders)
        float maxChase = role == AIRole.Defender ? vaultDefenseRadius * 1.5f : maxChaseDistance;
        if (dist > maxChase)
        {
            DevLog.Log($"[CashSystemAI] {gameObject.name} target too far, disengaging");
            SetState(AIState.Idle);
            return;
        }

        // ── DIRECT COMBAT (no delegation to CharacterAIController) ──

        // Always face the target when in combat range
        if (dist < attackRange * 3f)
            FaceTarget(currentTarget.position);

        if (dist <= attackRange)
        {
            // In attack range — stop and punch
            StopNavAgent();
            PerformDirectAttack();
        }
        else
        {
            // Approach the target
            MoveToward(currentTarget.position);
        }
    }

    private void ExecuteSteal()
    {
        if (enemyVault == null)
        {
            SetState(AIState.Idle);
            return;
        }

        // Abort if defenders showed up
        int enemies = CountEnemiesNear(enemyVaultGroundPos, vaultDefenseRadius);
        if (enemies >= 2)
        {
            DevLog.Log($"[CashSystemAI] {gameObject.name} aborting steal — too many defenders ({enemies})");
            SetState(AIState.Retreat);
            return;
        }

        MoveToward(enemyVaultGroundPos);
    }

    private void ExecuteDefend()
    {
        if (myVault == null) { SetState(AIState.Patrol); return; }

        // Priority: attack enemies near vault — DIRECT combat
        GameObject intruder = FindEnemyNearVault();
        if (intruder != null)
        {
            currentTarget = intruder.transform;
            float dist = Vector3.Distance(transform.position, intruder.transform.position);

            FaceTarget(intruder.transform.position);

            if (dist <= attackRange)
            {
                StopNavAgent();
                PerformDirectAttack();
            }
            else
            {
                MoveToward(intruder.transform.position);
            }
            return;
        }

        // No intruders — patrol around vault
        float distToVault = Vector3.Distance(transform.position, myVaultGroundPos);

        if (distToVault > vaultDefenseRadius)
        {
            // Too far from vault, return
            MoveToward(myVaultGroundPos);
            return;
        }

        // Patrol within vault radius
        if (Time.time < patrolPauseEndTime)
            return;

        if (!hasPatrolDestination || HasReachedPosition(patrolDestination, 2f))
        {
            if (hasPatrolDestination)
            {
                patrolPauseEndTime = Time.time + Random.Range(patrolPauseMin, patrolPauseMax);
                hasPatrolDestination = false;
                return;
            }

            // Pick point within vault defense radius — use GROUNDED vault position
            Vector3 randomDir = Random.insideUnitSphere * vaultDefenseRadius * 0.8f;
            randomDir.y = 0;
            Vector3 point = myVaultGroundPos + randomDir;

            NavMeshHit hit;
            if (NavMesh.SamplePosition(point, out hit, vaultDefenseRadius, NavMesh.AllAreas))
            {
                patrolDestination = hit.position;
                hasPatrolDestination = true;
                MoveToward(patrolDestination);
            }
        }
        else
        {
            MoveToward(patrolDestination);
        }
    }

    private void ExecuteRetreat()
    {
        if (myVault == null)
        {
            FindVaults();
            if (myVault == null) { SetState(AIState.Patrol); return; }
        }

        // Deposit cash while retreating
        float distToVault = Vector3.Distance(transform.position, myVaultGroundPos);
        if (distToVault < depositRange)
        {
            if (cashCarrier != null && cashCarrier.CarriedCash > 0)
                cashCarrier.DepositToVault(myVault);
        }

        // Head toward vault (safe haven)
        MoveToward(myVaultGroundPos);

        // Recover check — exit retreat when health recovered AND safe
        if (health != null && health.currentHealth > retreatHealthThreshold * 2f)
        {
            GameObject nearEnemy = FindNearestEnemy();
            if (nearEnemy == null || Vector3.Distance(transform.position, nearEnemy.transform.position) > enemyDetectRange)
            {
                SetState(AIState.Idle);
            }
        }
    }

    private void ExecutePatrol()
    {
        if (Time.time < patrolPauseEndTime)
            return;

        // PROACTIVE COMBAT: While patrolling, actively hunt nearby enemies
        if (IsCombatEnabled() && !isCarryingVault)
        {
            GameObject enemy = FindBestAttackTarget();
            if (enemy != null)
            {
                float dist = Vector3.Distance(transform.position, enemy.transform.position);
                // Engage enemies within detect range based on aggression level
                // At aggressionLevel 0.8, engage within 32m (80% of 40m range)
                if (dist < enemyDetectRange * aggressionLevel)
                {
                    currentTarget = enemy.transform;
                    SetState(AIState.AttackEnemy);
                    debugLastDecision = $"HUNT {enemy.name} while patrolling ({dist:F0}m)";
                    DevLog.Log($"[CashSystemAI] {gameObject.name} hunting {enemy.name} during patrol!");
                    return;
                }
            }
        }

        // While patrolling, check for opportunities
        CashBag bag = FindBestUnclaimedBag();
        if (bag != null)
        {
            ClaimBag(bag);
            currentTarget = bag.transform;
            SetState(AIState.CollectCash);
            return;
        }

        // Defender: always return to vault area when nothing to do
        if (role == AIRole.Defender && myVault != null)
        {
            float distToVault = Vector3.Distance(transform.position, myVaultGroundPos);
            if (distToVault > vaultDefenseRadius * 0.5f)
            {
                SetState(AIState.DefendVault);
                return;
            }
        }

        // Pick new random patrol point
        if (!hasPatrolDestination || HasReachedPosition(patrolDestination, 2f))
        {
            if (hasPatrolDestination)
            {
                patrolPauseEndTime = Time.time + Random.Range(patrolPauseMin, patrolPauseMax);
                hasPatrolDestination = false;
                return;
            }

            // Collectors patrol far (explore), Defenders near vault
            float radius = role == AIRole.Defender ? vaultDefenseRadius : patrolRadius;
            Vector3 center = role == AIRole.Defender && myVault != null
                ? myVaultGroundPos
                : transform.position;

            Vector3 randomPoint = center + Random.insideUnitSphere * radius;
            randomPoint.y = transform.position.y;

            NavMeshHit hit;
            if (NavMesh.SamplePosition(randomPoint, out hit, radius, NavMesh.AllAreas))
            {
                patrolDestination = hit.position;
                hasPatrolDestination = true;
                MoveToward(patrolDestination);
            }
        }
        else
        {
            MoveToward(patrolDestination);
        }
    }

    // ─── Extraction Mode State Implementations ──────────────

    private void ExecuteSeekVault()
    {
        if (targetVault == null)
        {
            // Try to find any vault
            targetVault = ExtractionVault.FindNearestSealed(transform.position);
            if (targetVault == null)
                targetVault = ExtractionVault.FindNearestAvailable(transform.position);
            if (targetVault == null)
            {
                SetState(AIState.Patrol);
                return;
            }
            currentTarget = targetVault.transform;
        }

        // Check if vault state changed (someone else grabbed it)
        if (targetVault.IsCarried || targetVault.State == ExtractionVault.VaultState.Deposited)
        {
            targetVault = null;
            SetState(AIState.Idle);
            return;
        }

        float dist = Vector3.Distance(transform.position, targetVault.transform.position);

        if (dist <= vaultInteractRange)
        {
            if (targetVault.IsSealed)
            {
                // Start opening
                targetVault.StartOpening(gameObject);
                SetState(AIState.OpenVault);
                return;
            }
            else if (targetVault.IsAvailableForPickup)
            {
                // Pick it up
                targetVault.PickUp(gameObject);
                isCarryingVault = true;
                SetState(AIState.CarryVault);
                currentTarget = myStation != null ? myStation.transform : (myVault != null ? myVault.transform : null);
                return;
            }
        }

        MoveToward(targetVault.transform.position);
    }

    private void ExecuteOpenVault()
    {
        if (targetVault == null || targetVault.State == ExtractionVault.VaultState.Deposited)
        {
            SetState(AIState.Idle);
            return;
        }

        // Stay still while opening (channel)
        StopNavAgent();

        if (targetVault.IsAvailableForPickup)
        {
            // Opening complete, pick it up
            targetVault.PickUp(gameObject);
            isCarryingVault = true;
            SetState(AIState.CarryVault);
            currentTarget = myStation != null ? myStation.transform : (myVault != null ? myVault.transform : null);
            return;
        }

        // If vault is no longer being opened by us (cancelled due to damage etc.)
        if (targetVault.IsSealed && targetVault.OpenProgress < 0.01f)
        {
            // Retry opening
            float dist = Vector3.Distance(transform.position, targetVault.transform.position);
            if (dist <= vaultInteractRange)
                targetVault.StartOpening(gameObject);
            else
                MoveToward(targetVault.transform.position);
        }
    }

    private void ExecuteCarryVault()
    {
        // Navigate to cashout station (encumbered — speed already reduced by ExtractionVault)
        Vector3 depositPos = myStation != null ? myStationGroundPos :
                            (myVault != null ? myVaultGroundPos : transform.position);

        float dist = Vector3.Distance(transform.position, depositPos);

        if (dist <= cashoutStationRange)
        {
            // We're at the station — deposit handled by trigger
            // But in case trigger didn't fire, force deposit
            if (myStation != null && targetVault != null && targetVault.IsCarried)
            {
                myStation.DepositVault(targetVault, gameObject);
                isCarryingVault = false;
                targetVault = null;
                SetState(AIState.DefendCashout);
                currentTarget = myStation.transform;
                return;
            }
        }

        // Check if we dropped the vault (got hit, died, etc.)
        if (!IsCarryingExtractionVault())
        {
            isCarryingVault = false;
            targetVault = null;
            SetState(AIState.Idle);
            return;
        }

        MoveToward(depositPos);
    }

    private void ExecuteDepositVault()
    {
        // Same as CarryVault in practice — navigate to station
        ExecuteCarryVault();
    }

    private void ExecuteDefendCashout()
    {
        if (myStation == null || !myStation.IsCashingOut)
        {
            SetState(AIState.Idle);
            return;
        }

        // Attack enemies near the cashout station
        GameObject intruder = FindEnemyNearPosition(myStationGroundPos, myStation.DefenseRadius);
        if (intruder != null)
        {
            currentTarget = intruder.transform;
            float dist = Vector3.Distance(transform.position, intruder.transform.position);

            FaceTarget(intruder.transform.position);

            if (dist <= attackRange)
            {
                StopNavAgent();
                PerformDirectAttack();
            }
            else
            {
                MoveToward(intruder.transform.position);
            }
            return;
        }

        // No intruders — patrol around station
        float distToStation = Vector3.Distance(transform.position, myStationGroundPos);
        if (distToStation > myStation.DefenseRadius)
        {
            MoveToward(myStationGroundPos);
            return;
        }

        // Patrol within defense radius
        if (Time.time < patrolPauseEndTime) return;

        if (!hasPatrolDestination || HasReachedPosition(patrolDestination, 2f))
        {
            if (hasPatrolDestination)
            {
                patrolPauseEndTime = Time.time + Random.Range(patrolPauseMin, patrolPauseMax);
                hasPatrolDestination = false;
                return;
            }

            Vector3 randomDir = Random.insideUnitSphere * myStation.DefenseRadius * 0.7f;
            randomDir.y = 0;
            Vector3 point = myStationGroundPos + randomDir;
            NavMeshHit hit;
            if (NavMesh.SamplePosition(point, out hit, myStation.DefenseRadius, NavMesh.AllAreas))
            {
                patrolDestination = hit.position;
                hasPatrolDestination = true;
                MoveToward(patrolDestination);
            }
        }
        else
        {
            MoveToward(patrolDestination);
        }
    }

    private void ExecuteAttackCashout()
    {
        if (enemyStation == null || !enemyStation.IsCashingOut)
        {
            SetState(AIState.Idle);
            return;
        }

        float dist = Vector3.Distance(transform.position, enemyStationGroundPos);

        // Abort if too many defenders
        int defenders = CountEnemiesNear(enemyStationGroundPos, 15f);
        if (defenders >= 3)
        {
            SetState(AIState.Retreat);
            return;
        }

        // Fight defenders first
        GameObject defender = FindEnemyNearPosition(enemyStationGroundPos, 15f);
        if (defender != null)
        {
            float defDist = Vector3.Distance(transform.position, defender.transform.position);
            if (defDist <= attackRange)
            {
                FaceTarget(defender.transform.position);
                StopNavAgent();
                PerformDirectAttack();
                return;
            }
            else if (defDist < enemyDetectRange)
            {
                MoveToward(defender.transform.position);
                return;
            }
        }

        // Move toward enemy cashout station to hack it (handled by trigger/proximity)
        if (dist > cashoutStationRange)
        {
            MoveToward(enemyStationGroundPos);
        }
        else
        {
            // In range — station's trigger system handles the hack
            StopNavAgent();
        }
    }

    private void ExecuteInterceptCarrier()
    {
        if (currentTarget == null)
        {
            SetState(AIState.Idle);
            return;
        }

        // Check if enemy still has the vault
        HealthNew targetHP = currentTarget.GetComponent<HealthNew>();
        if (targetHP != null && targetHP.isDead)
        {
            SetState(AIState.Idle);
            return;
        }

        if (!IsEnemyCarryingVault(currentTarget.gameObject))
        {
            // They dropped it or deposited — look for the vault
            SetState(AIState.Idle);
            return;
        }

        float dist = Vector3.Distance(transform.position, currentTarget.position);

        if (dist <= attackRange)
        {
            FaceTarget(currentTarget.position);
            StopNavAgent();
            PerformDirectAttack();
        }
        else
        {
            MoveToward(currentTarget.position);
        }
    }

    // ─── Extraction Helpers ──────────────────────────────────

    /// <summary>Check if this AI is currently carrying an ExtractionVault.</summary>
    private bool IsCarryingExtractionVault()
    {
        var vaults = EntityRegistry<ExtractionVault>.GetAll();
        for (int i = 0; i < vaults.Count; i++)
        {
            if (vaults[i] != null && vaults[i].IsCarried && vaults[i].Carrier == gameObject)
                return true;
        }
        return false;
    }

    /// <summary>Check if a specific enemy is carrying an ExtractionVault.</summary>
    private bool IsEnemyCarryingVault(GameObject enemy)
    {
        var vaults = EntityRegistry<ExtractionVault>.GetAll();
        for (int i = 0; i < vaults.Count; i++)
        {
            if (vaults[i] != null && vaults[i].IsCarried && vaults[i].Carrier == enemy)
                return true;
        }
        return false;
    }

    /// <summary>Find the enemy currently carrying a vault.</summary>
    private GameObject FindEnemyCarryingVaultObj()
    {
        var vaults = EntityRegistry<ExtractionVault>.GetAll();
        for (int i = 0; i < vaults.Count; i++)
        {
            if (vaults[i] == null || !vaults[i].IsCarried) continue;
            if (vaults[i].Carrier != null && !vaults[i].Carrier.CompareTag(myTeamTag))
                return vaults[i].Carrier;
        }
        return null;
    }

    /// <summary>Find nearest enemy within a radius of a specific position.</summary>
    private GameObject FindEnemyNearPosition(Vector3 position, float radius)
    {
        GameObject best = null;
        float bestDist = float.MaxValue;

        foreach (TeamMember member in TeamMember.AllMembers)
        {
            if (member == null || member.gameObject == gameObject) continue;
            if (member.gameObject.CompareTag(myTeamTag)) continue;

            HealthNew hp = member.GetComponent<HealthNew>();
            if (hp != null && hp.isDead) continue;

            float dist = Vector3.Distance(member.transform.position, position);
            if (dist < radius && dist < bestDist)
            {
                bestDist = dist;
                best = member.gameObject;
            }
        }
        return best;
    }

    // ─── Target Finders (sensor-backed) ────────────────────

    /// <summary>Find the best enemy to attack — uses sensor cache for efficiency.</summary>
    private GameObject FindBestAttackTarget()
    {
        GameObject best = null;
        float bestScore = 0f;

        for (int i = 0; i < sensedEnemies.Count; i++)
        {
            var se = sensedEnemies[i];
            if (se.enemy == null || se.distance > enemyDetectRange) continue;

            float score = 10f;

            // Closer = better
            score += 20f * (1f - se.distance / enemyDetectRange);

            // Carrying cash = priority target
            if (se.carriedCash > 0)
                score += 30f + se.carriedCash * 0.3f;

            // Low health = easy kill
            score += (1f - se.healthPercent) * 30f;

            // Near our vault = high threat (Defender boost)
            if (role == AIRole.Defender && myVault != null)
            {
                float eDist = Vector3.Distance(se.enemy.transform.position, myVaultGroundPos);
                if (eDist < vaultDefenseRadius)
                    score += 60f;
            }

            // Stealing from our vault = MAXIMUM priority
            if (myVault != null && myVault.IsBeingStolen)
            {
                float eDist = Vector3.Distance(se.enemy.transform.position, myVaultGroundPos);
                if (eDist < vaultDefenseRadius)
                    score += 200f;
            }

            if (score > bestScore)
            {
                bestScore = score;
                best = se.enemy;
            }
        }

        return best;
    }

    /// <summary>Find nearest enemy — uses sensor cache (already sorted by distance).</summary>
    private GameObject FindNearestEnemy()
    {
        for (int i = 0; i < sensedEnemies.Count; i++)
        {
            if (sensedEnemies[i].enemy != null)
                return sensedEnemies[i].enemy;
        }
        return null;
    }

    /// <summary>Find enemy near our vault (for Defender).</summary>
    private GameObject FindEnemyNearVault()
    {
        if (myVault == null) return null;

        GameObject best = null;
        float bestDist = float.MaxValue;

        foreach (TeamMember member in TeamMember.AllMembers)
        {
            if (member == null || member.gameObject == gameObject) continue;
            if (member.gameObject.CompareTag(myTeamTag)) continue;

            HealthNew hp = member.GetComponent<HealthNew>();
            if (hp != null && hp.isDead) continue;

            float dist = Vector3.Distance(member.transform.position, myVaultGroundPos);
            if (dist < vaultDefenseRadius && dist < bestDist)
            {
                bestDist = dist;
                best = member.gameObject;
            }
        }

        return best;
    }

    /// <summary>Find the best cash bag not claimed by a teammate — uses sensor cache.</summary>
    private CashBag FindBestUnclaimedBag()
    {
        // Build a shortlist of the top-N valid bags, then pick randomly
        // to spread AI across the map instead of all chasing the same bag.
        const int TOP_N = 3;
        CashBag[] candidates = new CashBag[TOP_N];
        float[] candidateScores = new float[TOP_N];
        int found = 0;

        bool isCarrying = cashCarrier != null && cashCarrier.CarriedCash >= 0.01f;

        // sensedBags is already sorted by score descending
        for (int i = 0; i < sensedBags.Count && found < TOP_N; i++)
        {
            CashBag bag = sensedBags[i].bag;
            if (bag == null) continue;
            if (!bag.IsAvailable && !bag.IsDropped) continue;

            // Skip if claimed by a teammate (coordination)
            if (IsBagClaimedByTeammate(bag)) continue;

            // Penalize bags near enemies when we're carrying cash
            if (isCarrying)
            {
                int enemiesNear = CountEnemiesNearSensor(bag.transform.position, 8f);
                if (enemiesNear > 1) continue; // Skip dangerous bags when carrying
            }

            candidates[found] = bag;
            candidateScores[found] = sensedBags[i].score;
            found++;
        }

        if (found == 0) return null;
        if (found == 1) return candidates[0];

        // Weighted random selection from top-N candidates
        // Higher-scoring bags are more likely but not guaranteed
        float totalScore = 0f;
        for (int i = 0; i < found; i++) totalScore += candidateScores[i];

        float roll = Random.Range(0f, totalScore);
        float cumulative = 0f;
        for (int i = 0; i < found; i++)
        {
            cumulative += candidateScores[i];
            if (roll <= cumulative) return candidates[i];
        }

        return candidates[0]; // Fallback
    }

    // ─── Coordination Helpers ────────────────────────────────

    private void ClaimBag(CashBag bag)
    {
        UnclaimBag();
        if (bag != null)
        {
            claimedBag = bag;
            _claimedBags[bag] = this;
        }
    }

    private void UnclaimBag()
    {
        if (claimedBag != null)
        {
            if (_claimedBags.ContainsKey(claimedBag) && _claimedBags[claimedBag] == this)
                _claimedBags.Remove(claimedBag);
            claimedBag = null;
        }
    }

    private bool IsBagClaimedByTeammate(CashBag bag)
    {
        if (!_claimedBags.TryGetValue(bag, out CashSystemAI claimer)) return false;
        if (claimer == null || claimer == this) return false;
        // Only count same-team claims
        return claimer.myTeamTag == myTeamTag;
    }

    private int CountEnemiesNear(Vector3 position, float radius)
    {
        int count = 0;
        foreach (TeamMember member in TeamMember.AllMembers)
        {
            if (member == null || member.gameObject == gameObject) continue;
            if (member.gameObject.CompareTag(myTeamTag)) continue;

            HealthNew hp = member.GetComponent<HealthNew>();
            if (hp != null && hp.isDead) continue;

            if (Vector3.Distance(member.transform.position, position) < radius)
                count++;
        }
        return count;
    }

    /// <summary>Count enemies near a position using sensor cache (faster).</summary>
    private int CountEnemiesNearSensor(Vector3 position, float radius)
    {
        int count = 0;
        for (int i = 0; i < sensedEnemies.Count; i++)
        {
            if (sensedEnemies[i].enemy == null) continue;
            if (Vector3.Distance(sensedEnemies[i].enemy.transform.position, position) < radius)
                count++;
        }
        return count;
    }

    // ─── Movement (DIRECT NavMeshAgent — single authority) ──

    /// <summary>
    /// Move toward a position using NavMeshAgent DIRECTLY.
    /// No delegation to CharacterAIController — eliminates interference.
    /// </summary>
    private void MoveToward(Vector3 position)
    {
        if (navAgent == null || !navAgent.isActiveAndEnabled)
            return;

        // Re-warp if off NavMesh
        if (!navAgent.isOnNavMesh)
        {
            NavMeshHit hit;
            if (NavMesh.SamplePosition(transform.position, out hit, 20f, NavMesh.AllAreas))
                navAgent.Warp(hit.position);
            else
                return; // Can't recover
        }

        navAgent.isStopped = false;
        navAgent.SetDestination(position);
    }

    /// <summary>Stop the NavMeshAgent directly.</summary>
    private void StopNavAgent()
    {
        if (navAgent != null && navAgent.isActiveAndEnabled && navAgent.isOnNavMesh)
        {
            navAgent.isStopped = true;
            navAgent.velocity = Vector3.zero;
        }
    }

    /// <summary>Rotate to face a target position.</summary>
    private void FaceTarget(Vector3 targetPos)
    {
        Vector3 dir = (targetPos - transform.position);
        dir.y = 0;
        if (dir.sqrMagnitude > 0.01f)
        {
            Quaternion lookRot = Quaternion.LookRotation(dir.normalized);
            transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRot, 360f * Time.deltaTime);
        }
    }

    // ─── Reactive Combat (Damage Response) ─────────────────

    /// <summary>
    /// Called when ANY character takes damage. We check if WE are the victim
    /// and immediately set the attacker as our combat target.
    /// This is the critical "fight back" mechanic that was completely missing.
    /// </summary>
    private void OnDamageTaken(GameObject attacker, GameObject victim, float damage)
    {
        // Only react if WE are the victim
        if (victim != gameObject) return;
        if (attacker == null) return;
        if (health != null && health.isDead) return;

        // Don't react to friendly fire (shouldn't happen but safety check)
        if (attacker.CompareTag(myTeamTag)) return;

        lastDamageAttacker = attacker;
        lastDamageTime = Time.time;

        // CRITICAL: Immediately switch to attack if not already in a high-priority state
        if (currentState != AIState.CarryVault && currentState != AIState.Retreat)
        {
            // Force immediate re-evaluation — being attacked is highest priority
            currentTarget = attacker.transform;
            debugLastDecision = $"FIGHT BACK against {attacker.name}!";

            if (!isCarryingVault)
            {
                SetState(AIState.AttackEnemy);
                DevLog.Log($"[CashSystemAI] {gameObject.name} FIGHTING BACK against {attacker.name}!");
            }
        }
    }

    // ─── Direct Combat ───────────────────────────────────────

    /// <summary>
    /// Perform an attack directly using CharacterAttacks component.
    /// No delegation to CharacterAIController — CashSystemAI owns combat too.
    /// </summary>
    private void PerformDirectAttack()
    {
        if (Time.time - lastAttackTime < attackCooldown) return;

        if (characterAttacks != null)
        {
            // Pick a random attack from light/medium/heavy pool
            string[] lightAttacks = { AnimationNames.Jab, AnimationNames.LowKick, AnimationNames.Chop };
            string[] mediumAttacks = { AnimationNames.Haymaker, AnimationNames.SpinningBackfist, AnimationNames.BasicKick };
            string[] heavyAttacks = { AnimationNames.ElbowSmash, AnimationNames.ChargedPunch, AnimationNames.AirPunch };

            // Weighted random: 50% light, 35% medium, 15% heavy
            float roll = Random.value;
            string[] pool;
            if (roll < 0.5f) pool = lightAttacks;
            else if (roll < 0.85f) pool = mediumAttacks;
            else pool = heavyAttacks;

            string attack = pool[Random.Range(0, pool.Length)];
            characterAttacks.PunchAttacksDirectly(attack);
            lastAttackTime = Time.time;
        }
        else if (animator != null)
        {
            animator.SetTrigger("attack");
            lastAttackTime = Time.time;
        }
    }

    private bool HasReachedPosition(Vector3 pos, float threshold)
    {
        return Vector3.Distance(transform.position, pos) < threshold;
    }

    // ─── Debug Visualization ─────────────────────────────────

#if UNITY_EDITOR
    private void OnDrawGizmos()
    {
        if (!showDebugGizmos || !Application.isPlaying) return;

        // Role indicator sphere above head
        Vector3 headPos = transform.position + Vector3.up * 3f;
        Gizmos.color = role == AIRole.Defender ? Color.blue : Color.green;
        Gizmos.DrawSphere(headPos, 0.3f);

        // State color
        Color stateColor = GetStateColor();
        Gizmos.color = stateColor;

        // Line to current target
        if (currentTarget != null)
        {
            Gizmos.DrawLine(transform.position + Vector3.up, currentTarget.position + Vector3.up * 0.5f);
        }

        // Detection range
        Gizmos.color = new Color(1f, 0f, 0f, 0.15f);
        Gizmos.DrawWireSphere(transform.position, enemyDetectRange);

        // Vault defense radius (Defender only)
        if (role == AIRole.Defender && myVault != null)
        {
            Gizmos.color = new Color(0f, 0f, 1f, 0.2f);
            Gizmos.DrawWireSphere(myVaultGroundPos, vaultDefenseRadius);
        }

        // State label
        UnityEditor.Handles.Label(
            headPos + Vector3.up * 0.5f,
            $"{gameObject.name}\n[{role}] {currentState}\n{debugLastDecision}" +
            (cashCarrier != null ? $"\nCash: ${cashCarrier.CarriedCash:F0}" : "")
        );
    }
#endif

    private Color GetStateColor()
    {
        switch (currentState)
        {
            case AIState.CollectCash: return Color.yellow;
            case AIState.DepositCash: return Color.green;
            case AIState.AttackEnemy: return Color.red;
            case AIState.DefendVault: return Color.blue;
            case AIState.StealFromVault: return new Color(1f, 0.5f, 0f); // Orange
            case AIState.Retreat: return Color.magenta;
            case AIState.Patrol: return Color.cyan;
            case AIState.SeekVault: return new Color(0.8f, 0.8f, 0f);    // Dark yellow
            case AIState.OpenVault: return new Color(1f, 0.8f, 0.2f);    // Gold
            case AIState.CarryVault: return new Color(0f, 1f, 0.5f);     // Mint
            case AIState.DepositVault: return new Color(0f, 0.8f, 0.3f); // Emerald
            case AIState.DefendCashout: return new Color(0.2f, 0.4f, 1f);// Royal blue
            case AIState.AttackCashout: return new Color(1f, 0.2f, 0.2f);// Bright red
            case AIState.InterceptCarrier: return new Color(0.8f, 0f, 0.4f); // Crimson
            default: return Color.gray;
        }
    }
}
