extends Node

func _ready() -> void:
  EventBus.ability_use.connect(_on_ability_use)

func _on_ability_use(_player: Node, ability_index: int) -> void:
  if not PlayerData.alive:
    return
  var ability_set: AbilitySet = PlayerData.ability_set
  if not ability_set or ability_index >= ability_set.abilities.size():
    return
  var ability: Ability = ability_set.abilities[ability_index]
  if not ability or ability.type == Ability.Type.PASSIVE:
    return
  if PlayerData.cooldowns[ability_index] > 0:
    return
  if ability.uses_gcd and PlayerData.gcd > 0:
    return
  var success: bool = _execute_ability(ability)
  if not success:
    return
  var gcd: float = PlayerData.gcd_time if ability.uses_gcd else 0.0
  PlayerData.cooldowns[ability_index] = ability.cooldown
  PlayerData.max_cooldowns[ability_index] = max(ability.cooldown, gcd)
  if gcd > 0:
    PlayerData.gcd = gcd

func _execute_ability(ability: Ability) -> bool:
  var stat: String = "heal" if ability.is_heal else "damage"
  var dmg: float = _apply_passive(ability.damage, stat)
  var player: Node = get_tree().get_first_node_in_group("player")
  match ability.type:
    Ability.Type.SINGLE:
      return _execute_single(player, ability, dmg)
    Ability.Type.AOE:
      return _execute_aoe(player, ability, dmg)
    Ability.Type.UTILITY:
      return _execute_utility(ability)
    Ability.Type.ULT:
      return _execute_ult(player, ability, dmg)
  return false

func _apply_passive(base: float, stat: String) -> float:
  var mult: float = 1.0
  match stat:
    "damage": mult = PlayerData.buff_damage
    "heal": mult = PlayerData.buff_heal
  return base * mult * PlayerData.level_scale

func _in_range(ability: Ability) -> bool:
  if ability.ability_range <= 0 or ability.is_heal:
    return true
  if not is_instance_valid(PlayerData.target):
    return false
  var player: Node = get_tree().get_first_node_in_group("player")
  var dist: float = player.global_position.distance_to(PlayerData.target.global_position)
  return dist <= ability.ability_range

func _execute_single(player: Node, ability: Ability, dmg: float) -> bool:
  if ability.is_heal:
    EventBus.heal_requested.emit(player, player, dmg)
    EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
    return true
  if not _in_range(ability):
    return false
  if not is_instance_valid(PlayerData.target):
    return false
  EventBus.damage_requested.emit(player, PlayerData.target, dmg)
  if ability.element != 0:
    EventBus.element_damage_dealt.emit(player, PlayerData.target, dmg, ability.element)
  EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
  return true

func _execute_aoe(player: Node, ability: Ability, dmg: float) -> bool:
  if ability.is_heal:
    EventBus.heal_requested.emit(player, player, dmg)
    EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
    return true
  var hit := false
  var targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
  for target in targets:
    var dist: float = player.global_position.distance_to(target.global_position)
    if dist <= ability.ability_range:
      EventBus.damage_requested.emit(player, target, dmg)
      if ability.element != 0:
        EventBus.element_damage_dealt.emit(player, target, dmg, ability.element)
      hit = true
  if hit:
    EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
  return hit

func _execute_utility(ability: Ability) -> bool:
  if PlayerData.max_shield <= 0:
    return false
  var shield: float = PlayerData.shield
  if ability.damage > 0:
    shield = PlayerData.max_shield * (ability.damage / 100.0)
  else:
    if shield >= PlayerData.max_shield:
      return false
    shield = PlayerData.max_shield
  PlayerData.set_shield(shield)
  return true

func _execute_ult(player: Node, ability: Ability, dmg: float) -> bool:
  if ability.is_heal:
    EventBus.heal_requested.emit(player, player, dmg)
    var aoe_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range
    EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg)
    return true
  if not _in_range(ability):
    return false
  if not is_instance_valid(PlayerData.target):
    return false
  var target: Node3D = PlayerData.target
  EventBus.damage_requested.emit(player, target, dmg * 5.0)
  if ability.element != 0:
    EventBus.element_damage_dealt.emit(player, target, dmg * 5.0, ability.element)
  var splash_range: float = ability.aoe_radius if ability.aoe_radius > 0 else ability.ability_range
  var splash_targets: Array = get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals")
  for other in splash_targets:
    if other != target and is_instance_valid(other):
      var other_dist: float = target.global_position.distance_to(other.global_position)
      if other_dist <= splash_range:
        EventBus.damage_requested.emit(player, other, dmg * 2.0)
        if ability.element != 0:
          EventBus.element_damage_dealt.emit(player, other, dmg * 2.0, ability.element)
  EventBus.attack_executed.emit(player, player.global_position, -player.global_transform.basis.z, dmg * 5.0)
  return true
