using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// AIPerceptionSystem — Unified vision/detection system for AI.
/// Uses Physics.OverlapSphereNonAlloc for zero-allocation detection.
/// Populates the AIBlackboard with perception results.
/// 
/// Call UpdatePerception() on the AI tick (not every frame).
/// </summary>
public class AIPerceptionSystem : MonoBehaviour
{
    [Header("Vision Settings")]
    [SerializeField] private float detectionRadius = 18f;
    [SerializeField] private float fovAngle = 160f;         // Forward cone
    [SerializeField] private float closeRangeRadius = 5f;   // Always detect within this range (no FOV check)

    [Header("Bag Detection")]
    [SerializeField] private float bagDetectionRadius = 25f;

    [Header("Line of Sight")]
    [SerializeField] private bool requireLineOfSight = true;
    [SerializeField] private LayerMask obstructionLayers;
    [SerializeField] private float losHeightOffset = 1.2f;  // Eye height

    [Header("Memory")]
    [SerializeField] private float memoryDuration = 5f;     // Remember entities for this long after losing sight

    // ─── Detection Buffer (zero-alloc) ───────────────────────
    private static readonly Collider[] _overlapBuffer = new Collider[32];

    // ─── Threat Memory ───────────────────────────────────────
    private readonly Dictionary<Transform, float> _lastSeenTime = new Dictionary<Transform, float>(16);
    private readonly Dictionary<Transform, Vector3> _lastKnownPositions = new Dictionary<Transform, Vector3>(16);

    // ─── Component Cache ─────────────────────────────────────
    private Transform _transform;
    private string _teamTag;

    private void Awake()
    {
        _transform = transform;
    }

    public void Initialize(string teamTag)
    {
        _teamTag = teamTag;
    }

    /// <summary>
    /// Update perception and populate blackboard.
    /// Call this on the AI decision tick, NOT every frame.
    /// </summary>
    public void UpdatePerception(AIBlackboard blackboard)
    {
        blackboard.ClearPerception();

        Vector3 myPos = _transform.position;
        Vector3 eyePos = myPos + Vector3.up * losHeightOffset;

        // ─── Detect Characters ───────────────────────────────
        var allMembers = TeamMember.AllMembers;
        for (int i = 0; i < allMembers.Count; i++)
        {
            TeamMember member = allMembers[i];
            if (member == null || member.gameObject == gameObject) continue;

            Transform target = member.transform;
            float sqrDist = (target.position - myPos).sqrMagnitude;
            float dist = Mathf.Sqrt(sqrDist);

            // Range check
            if (dist > detectionRadius) continue;

            // FOV check (skip for close range)
            bool inFOV = dist <= closeRangeRadius || IsInFOV(target.position);

            // LOS check
            bool hasLOS = !requireLineOfSight || dist <= closeRangeRadius || CheckLineOfSight(eyePos, target.position + Vector3.up * losHeightOffset);

            bool isVisible = inFOV && hasLOS;

            // Update memory
            if (isVisible)
            {
                _lastSeenTime[target] = Time.time;
                _lastKnownPositions[target] = target.position;
            }

            // Check memory (even if not currently visible)
            bool isRemembered = false;
            float timeSinceLastSeen = float.MaxValue;
            Vector3 lastKnownPos = target.position;

            if (_lastSeenTime.TryGetValue(target, out float lastSeen))
            {
                timeSinceLastSeen = Time.time - lastSeen;
                isRemembered = timeSinceLastSeen < memoryDuration;
                if (_lastKnownPositions.TryGetValue(target, out Vector3 lkp))
                    lastKnownPos = lkp;
            }

            if (!isVisible && !isRemembered) continue;

            // Build perceived entity
            HealthNew health = member.GetComponent<HealthNew>();
            CashCarrier carrier = member.GetComponent<CashCarrier>();

            PerceivedEntity entity = new PerceivedEntity
            {
                transform = target,
                gameObject = member.gameObject,
                distance = dist,
                sqrDistance = sqrDist,
                healthPercent = health != null ? (health.currentHealth / health.maxHealth) : 1f,
                isCarryingBag = carrier != null && carrier.IsCarrying,
                carriedBagCount = carrier != null ? carrier.BagCount : 0,
                isVisible = isVisible,
                lastKnownPosition = lastKnownPos,
                timeSinceLastSeen = timeSinceLastSeen
            };

            bool isEnemy = member.gameObject.tag != _teamTag;
            if (isEnemy)
            {
                blackboard.perceivedEnemies.Add(entity);
                blackboard.nearbyEnemyCount++;

                if (dist < blackboard.nearestEnemyDistance)
                    blackboard.nearestEnemyDistance = dist;

                // Track nearest enemy carrier
                if (entity.isCarryingBag && carrier != null)
                {
                    if (blackboard.nearestEnemyCarrier == null ||
                        dist < Vector3.Distance(myPos, blackboard.nearestEnemyCarrier.transform.position))
                    {
                        blackboard.nearestEnemyCarrier = carrier;
                    }
                }
            }
            else
            {
                blackboard.perceivedAllies.Add(entity);
                blackboard.nearbyAllyCount++;

                // Track nearest ally carrier (for escort)
                if (entity.isCarryingBag && carrier != null)
                {
                    if (blackboard.nearestAllyCarrier == null ||
                        dist < Vector3.Distance(myPos, blackboard.nearestAllyCarrier.transform.position))
                    {
                        blackboard.nearestAllyCarrier = carrier;
                    }
                }
            }
        }

        // ─── Detect Cash Bags ─────────────────────────────────
        var allBags = EntityRegistry<CashBag>.All;
        for (int i = 0; i < allBags.Count; i++)
        {
            CashBag bag = allBags[i];
            if (bag == null || bag.IsCarried) continue;

            float dist = Vector3.Distance(myPos, bag.transform.position);
            if (dist > bagDetectionRadius) continue;

            if (dist < blackboard.nearestBagDistance)
                blackboard.nearestBagDistance = dist;

            if (bag.IsAvailable)
                blackboard.nearbyAvailableBags.Add(bag);
            else if (bag.IsDropped)
                blackboard.nearbyDroppedBags.Add(bag);
        }
    }

    // ─── Vision Helpers ──────────────────────────────────────

    private bool IsInFOV(Vector3 targetPos)
    {
        Vector3 dirToTarget = (targetPos - _transform.position).normalized;
        dirToTarget.y = 0; // Horizontal only
        float angle = Vector3.Angle(_transform.forward, dirToTarget);
        return angle <= fovAngle * 0.5f;
    }

    private bool CheckLineOfSight(Vector3 from, Vector3 to)
    {
        Vector3 dir = to - from;
        float dist = dir.magnitude;

        if (dist < 0.1f) return true;

        return !Physics.Raycast(from, dir.normalized, dist, obstructionLayers);
    }

    // ─── Memory Management ───────────────────────────────────

    /// <summary>Clear all threat memory (call on state reset).</summary>
    public void ClearMemory()
    {
        _lastSeenTime.Clear();
        _lastKnownPositions.Clear();
    }

    /// <summary>Get last known position of a target (for pursuit).</summary>
    public bool TryGetLastKnownPosition(Transform target, out Vector3 position)
    {
        return _lastKnownPositions.TryGetValue(target, out position);
    }

    // ─── Debug ───────────────────────────────────────────────

#if UNITY_EDITOR
    private void OnDrawGizmosSelected()
    {
        if (_transform == null) _transform = transform;

        // Detection radius
        Gizmos.color = new Color(1f, 1f, 0f, 0.1f);
        Gizmos.DrawWireSphere(_transform.position, detectionRadius);

        // Close range
        Gizmos.color = new Color(1f, 0.5f, 0f, 0.15f);
        Gizmos.DrawWireSphere(_transform.position, closeRangeRadius);

        // FOV cone
        Gizmos.color = new Color(0f, 1f, 0f, 0.3f);
        Vector3 fovLeft = Quaternion.Euler(0, -fovAngle * 0.5f, 0) * _transform.forward * detectionRadius;
        Vector3 fovRight = Quaternion.Euler(0, fovAngle * 0.5f, 0) * _transform.forward * detectionRadius;
        Gizmos.DrawLine(_transform.position, _transform.position + fovLeft);
        Gizmos.DrawLine(_transform.position, _transform.position + fovRight);

        // Bag detection radius
        Gizmos.color = new Color(1f, 0.84f, 0f, 0.05f);
        Gizmos.DrawWireSphere(_transform.position, bagDetectionRadius);
    }
#endif
}
