using Cirno.Scripts.Enums;
using Godot;
using Godot.Collections;

namespace Cirno.Scripts.Components.FSM.Enemy;

public partial class PlayerDetectionModule : Area2D
{
    [Signal]
    public delegate void PlayerInRangeEventHandler();
    
    [Signal]
    public delegate void PlayerOutOfRangeEventHandler();
    
    [Export(PropertyHint.Layers2DPhysics)]
    public uint ObstaclesCollisionMask { get; private set; }
    
    public Vector2? LastKnownPlayerPosition { get; set; }
    public Vector2? LastKnowPlayerVelocity { get; set; }
    
    //public bool PlayerInActiveArea { get; private set; }
    private CollisionShape2D _collisionShape2D;
    
    private bool _initialized = false;
    public override void _Ready()
    {
        CallDeferred(MethodName.Initialize);
    }

    private void Initialize()
    {
        _initialized = true;
    }
    
    public void SetRange(float range)
    {
        _collisionShape2D ??= this.GetNode<CollisionShape2D>("CollisionShape2D");
        if (_collisionShape2D.Shape is CircleShape2D shape2D)
        {
            shape2D.Radius = range;
        }
    }
    
    public bool IsPlayerInRange(float range)
    {
        if (!_initialized) return false;
        if (GameManager.Instance is null) return false;
        
        if (!GameManager.Instance.PlayerPosition.HasValue)
        {
            return false;
        }
        
        return this.GlobalPosition.DistanceTo(GameManager.Instance.PlayerPosition.Value) <= range;
    }
    
    public bool IsPlayerInSight()
    {
        if (!_initialized) return false;
        if (GameManager.Instance is null) return false;
        //if (_cachedPlayer == null) return false;
        if (!GameManager.Instance.PlayerPosition.HasValue) return false;
        
        var found = HasLineOfSight(this.GlobalPosition, GameManager.Instance.PlayerPosition.Value);
        
        // var spaceState = GetWorld2D().DirectSpaceState;
        //
        // // It needs to use its own collision mask because it's detecting obstacles rather than the player
        // var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, GameManager.Instance.PlayerPosition.Value, ObstaclesCollisionMask,
        //     [GetRid()]);
        // //query.CollideWithBodies = true;
        // //query.CollideWithAreas = true;
        // // var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
        // var result = spaceState.IntersectRay(query);
        //
        // // If count is 0 then the player is in sight, otherwise there is level geometry in the way
        // var found = result.Count == 0;
        if (found)
        {
            LastKnownPlayerPosition = GameManager.Instance.PlayerPosition;
            LastKnowPlayerVelocity = GameManager.Instance.PlayerVelocity;
        }
        return found;
    }
    
    private void _on_area_entered(Area2D area)
    {
        if (area is not InteractionController player) return;
        EmitSignal(SignalName.PlayerInRange);
        //PlayerInActiveArea = true;
    }

    private void _on_area_exited(Area2D area)
    {
        if (area is not InteractionController player) return;
        EmitSignal(SignalName.PlayerOutOfRange);
        //PlayerInActiveArea = false;
    }

    public bool HasLineOfSight(Vector2 startPos, Vector2 endPos)
    {
        var spaceState = GetWorld2D().DirectSpaceState;
        
        // It needs to use its own collision mask because it's detecting obstacles rather than the player
        var query = PhysicsRayQueryParameters2D.Create(startPos, endPos, ObstaclesCollisionMask,
            [GetRid()]);
        //query.CollideWithBodies = true;
        //query.CollideWithAreas = true;
        // var query = PhysicsRayQueryParameters2D.Create(this.GlobalPosition, _cachedPlayer.GlobalPosition, CollisionMask, new Array<Rid> { GetRid() });
        var result = spaceState.IntersectRay(query);

        // If count is 0 then the player is in sight, otherwise there is level geometry in the way
        var found = result.Count == 0;
        
        return found;
    }
}