extends Node

const COMBAT_TIMEOUT: float = 5.0
const TANK_MULT: float = 2.0
const HEAL_MULT: float = 0.5
const SPREAD: float = 0.5
const DECAY_PER_SEC: float = 1.0

var aggro: Dictionary = {}
var combat_timers: Dictionary = {}

func _ready() -> void:
  EventBus.damage_dealt.connect(_on_damage_dealt)
  EventBus.heal_requested.connect(_on_heal_requested)
  EventBus.entity_died.connect(_on_died)
  EventBus.entity_deregistered.connect(_on_dereg)
  EventBus.enemy_detected.connect(_on_enemy_detected)
  if multiplayer.multiplayer_peer != null and not multiplayer.is_server() and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer):
    set_physics_process(false)
  else:
    set_physics_process(true)

var _accum: float = 0.0

func _physics_process(delta: float) -> void:
  if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
    return
  _accum += delta
  if _accum < 0.20:
    return
  var dt: float = _accum
  _accum = 0.0
  for enemy in aggro.keys():
    if not is_instance_valid(enemy):
      continue
    var t: float = combat_timers.get(enemy, 0.0)
    if t > 0.0:
      combat_timers[enemy] = max(0.0, t - dt)
    else:
      var table: Dictionary = aggro[enemy]
      var to_remove: Array = []
      for player in table.keys():
        table[player] = max(0.0, table[player] - DECAY_PER_SEC * dt)
        if table[player] <= 0.0:
          to_remove.append(player)
      for p in to_remove:
        table.erase(p)
    if not aggro[enemy].is_empty():
      EventBus.enemy_engaged.emit(enemy, _top_target(enemy))

func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void:
  if not is_instance_valid(target) or not is_instance_valid(attacker):
    return
  if target.is_in_group("enemies"):
    var role: int = int(Stats.get_stat(attacker, "role", GameState.ROLE_DAMAGE))
    var mult: float = TANK_MULT if role == GameState.ROLE_TANK else 1.0
    _add(target, attacker, amount * mult)
    _spread(target, attacker, amount * SPREAD)
    combat_timers[target] = COMBAT_TIMEOUT

func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
  if not is_instance_valid(healer):
    return
  for enemy in aggro.keys():
    if not is_instance_valid(enemy):
      continue
    if healer in aggro[enemy]:
      _add(enemy, healer, amount * HEAL_MULT)

func _on_enemy_detected(enemy: Node, player: Node) -> void:
  _add(enemy, player, 1.0)
  combat_timers[enemy] = COMBAT_TIMEOUT

func _on_died(entity: Node) -> void:
  aggro.erase(entity)
  combat_timers.erase(entity)
  for enemy in aggro.keys():
    if entity in aggro[enemy]:
      aggro[enemy].erase(entity)

func _on_dereg(entity: Node) -> void:
  _on_died(entity)

func _add(enemy: Node, player: Node, amount: float) -> void:
  if not enemy in aggro:
    aggro[enemy] = {}
  aggro[enemy][player] = aggro[enemy].get(player, 0.0) + amount

func _spread(enemy: Node, player: Node, amount: float) -> void:
  for other in aggro.keys():
    if other == enemy or not is_instance_valid(other):
      continue
    if (other as Node3D).global_position.distance_to((enemy as Node3D).global_position) <= 10.0:
      _add(other, player, amount)

func _top_target(enemy: Node) -> Node:
  if not enemy in aggro:
    return null
  var best: Node = null
  var best_v: float = -1.0
  for p in aggro[enemy].keys():
    if not is_instance_valid(p):
      continue
    if aggro[enemy][p] > best_v:
      best_v = aggro[enemy][p]
      best = p
  return best

func target_for(enemy: Node) -> Node:
  return _top_target(enemy)
