extends Node

var _active: Dictionary = {}
var _passive_auras: Dictionary = {}

func _ready() -> void:
  EventBus.entity_died.connect(_on_died)
  EventBus.entity_deregistered.connect(_on_dereg)
  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 _heavy_accum: float = 0.0

func _physics_process(delta: float) -> void:
  if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
    return
  _tick_durations(delta)
  _heavy_accum += delta
  if _heavy_accum < 0.20:
    return
  _heavy_accum = 0.0
  _propagate_auras()
  _recompute_buffs()

func apply(target: Node, effect: Effect, source: Node = null) -> void:
  if not is_instance_valid(target):
    return
  if not target in _active:
    _active[target] = []
  for entry in _active[target]:
    if entry.effect.effect_name == effect.effect_name and entry.source == source:
      entry.remaining = effect.duration
      entry.tick_timer = effect.tick_interval
      EventBus.effect_applied.emit(target, effect, source)
      return
  _active[target].append({
    "effect": effect,
    "source": source,
    "remaining": effect.duration,
    "tick_timer": effect.tick_interval
  })
  EventBus.effect_applied.emit(target, effect, source)

func remove_by_source(target: Node, source: Node) -> void:
  if not target in _active:
    return
  var keep: Array = []
  for entry in _active[target]:
    if entry.source == source:
      EventBus.effect_expired.emit(target, entry.effect)
    else:
      keep.append(entry)
  _active[target] = keep

func apply_passive_aura(player: Node, ability: Ability) -> void:
  if ability == null or ability.passive_stat == &"":
    return
  _passive_auras[player] = ability
  for target in get_tree().get_nodes_in_group("player"):
    remove_by_source(target, player)

func _propagate_auras() -> void:
  for source in _passive_auras.keys():
    if not is_instance_valid(source):
      _passive_auras.erase(source)
      continue
    var ability: Ability = _passive_auras[source]
    var effect := Effect.new()
    effect.effect_name = StringName("aura_%s_%s" % [ability.passive_stat, source.name])
    effect.type = Effect.Type.AURA
    effect.stat = ability.passive_stat
    effect.value = ability.passive_value
    effect.is_multiplier = true
    effect.duration = 0.5
    effect.aura_radius = ability.passive_radius
    var src_pos: Vector3 = (source as Node3D).global_position
    for target in get_tree().get_nodes_in_group("player"):
      if not Stats.has(target):
        continue
      var d: float = (target as Node3D).global_position.distance_to(src_pos)
      if d <= ability.passive_radius:
        apply(target, effect, source)

func _tick_durations(delta: float) -> void:
  for target in _active.keys():
    if not is_instance_valid(target):
      _active.erase(target)
      continue
    var keep: Array = []
    for entry in _active[target]:
      if entry.effect.duration > 0.0:
        entry.remaining -= delta
        if entry.remaining <= 0.0:
          EventBus.effect_expired.emit(target, entry.effect)
          continue
      if entry.effect.tick_interval > 0.0:
        entry.tick_timer -= delta
        if entry.tick_timer <= 0.0:
          entry.tick_timer = entry.effect.tick_interval
          if entry.effect.type == Effect.Type.DOT:
            EventBus.damage_requested.emit(entry.source, target, entry.effect.value, entry.effect.element)
          elif entry.effect.type == Effect.Type.HOT:
            EventBus.heal_requested.emit(entry.source, target, entry.effect.value)
      keep.append(entry)
    _active[target] = keep

func _recompute_buffs() -> void:
  for target in _active.keys():
    if not is_instance_valid(target) or not Stats.has(target):
      continue
    var bonus: Dictionary = {}
    var mult: Dictionary = {}
    for entry in _active[target]:
      var e: Effect = entry.effect
      if e.stat == &"":
        continue
      if e.is_multiplier:
        mult[e.stat] = mult.get(e.stat, 0.0) + e.value
      else:
        bonus[e.stat] = bonus.get(e.stat, 0.0) + e.value
    for stat in [&"buff_damage", &"buff_heal", &"buff_shield"]:
      var base: float = 1.0
      var v: float = base + mult.get(stat, 0.0) + bonus.get(stat, 0.0)
      Stats.set_stat(target, stat, v)
      EventBus.buff_changed.emit(target, stat, v)

func _on_died(entity: Node) -> void:
  if entity in _active:
    for entry in _active[entity]:
      EventBus.effect_expired.emit(entity, entry.effect)
  _active.erase(entity)
  _passive_auras.erase(entity)

func _on_dereg(entity: Node) -> void:
  _active.erase(entity)
  _passive_auras.erase(entity)
