using Godot;
using Mr.BrickAdventures.scripts.Resources;

namespace Mr.BrickAdventures.scripts.components;

[GlobalClass]
public partial class DamageComponent : Node
{
    [Export] public float Damage { get; set; } = 0.25f;
    [Export] public Area2D Area { get; set; }
    [Export] public StatusEffectDataResource StatusEffectData { get; set; }
    [Export] public Timer DamageTimer { get; set; }
    
    private readonly System.Collections.Generic.HashSet<Node> _currentTargets = new();
    private KnockbackComponent _knockbackComponent = null;
    
    [Signal] public delegate void EffectInflictedEventHandler(Node2D target, StatusEffectDataResource effect);

    public override void _Ready()
    {
        _knockbackComponent = Owner.GetNodeOrNull<KnockbackComponent>("KnockbackComponent");
        
        if (Area != null)
        {
            Area.BodyEntered += OnAreaBodyEntered;
            Area.BodyExited += OnAreaBodyExited;
            Area.AreaEntered += OnAreaAreaEntered;
        }
        
        if (DamageTimer != null)
        {
            DamageTimer.Timeout += OnDamageTimerTimeout;
        }
    }

    public override void _Process(double delta)
    {
        if (_currentTargets.Count == 0) return;
        if (DamageTimer != null) return;

        foreach (var target in _currentTargets)
            ProcessEntityAndApplyDamage(target as Node2D);
    }

    public void DealDamage(HealthComponent target) => target.DecreaseHealth(Damage);

    private void OnAreaAreaEntered(Area2D area)
    {
        if (!CheckIfProcessingIsOn())
            return;
        if (area == Area) return;

        var parent = area.GetParent();
        if (parent.HasNode("DamageComponent"))
            ProcessEntityAndApplyDamage(parent as Node2D);
    }

    private void OnAreaBodyExited(Node2D body)
    {
        _currentTargets.Remove(body);
        if (_currentTargets.Count == 0)
            DamageTimer?.Stop();
    }

    private void OnAreaBodyEntered(Node2D body)
    {
        _currentTargets.Add(body);

        if (!CheckIfProcessingIsOn())
            return;

        if (_currentTargets.Count == 1)
            DamageTimer?.Start();

        ProcessEntityAndApplyDamage(body);
    }

    private void OnDamageTimerTimeout()
    {
        foreach (var target in _currentTargets)
            ProcessEntityAndApplyDamage(target as Node2D);
    }
    
    private void ProcessEntityAndApplyDamage(Node2D body)
    {
        if (body == null) return;

        if (!body.HasNode("HealthComponent")) return;
        var health = body.GetNode<HealthComponent>("HealthComponent");
        var inv = body.GetNodeOrNull<InvulnerabilityComponent>("InvulnerabilityComponent");

        if (inv != null && inv.IsInvulnerable())
            return;

        if (StatusEffectData != null && StatusEffectData.Type != StatusEffectType.None)
            EmitSignalEffectInflicted(body, StatusEffectData);

        DealDamage(health);

        if (_knockbackComponent != null && body is CharacterBody2D characterBody && Owner is Node2D source)
        {
            _knockbackComponent.ApplyKnockback(characterBody, source);
        }

        inv?.Activate();
    }
    
    private bool CheckIfProcessingIsOn()
    {
        return ProcessMode is ProcessModeEnum.Inherit or ProcessModeEnum.Always;
    }
}