extends CharacterBody3D

const GRAVITY: float = 18.0

@export var stats_resource: EnemyStats
@export var is_boss: bool = false

@onready var nav: NavigationAgent3D = $NavAgent
@onready var mesh_holder: Node3D = $MeshHolder
@onready var collision: CollisionShape3D = $Collision
@onready var detection: Area3D = $DetectionArea
@onready var sync: MultiplayerSynchronizer = $Synchronizer
@onready var healthbar: MeshInstance3D = $Healthbar
@onready var name_label: Label3D = $NameLabel

var origin: Vector3 = Vector3.ZERO
var attack_cd: float = 0.0
var dead: bool = false
var invasion_target: Node = null

@export var sync_position: Vector3 = Vector3.ZERO
@export var sync_yaw: float = 0.0

func _enter_tree() -> void:
  set_multiplayer_authority(1)

func _ready() -> void:
  if is_boss:
    add_to_group("boss")
  add_to_group("enemies")
  if stats_resource == null:
    stats_resource = EnemyStats.new()
  Stats.register(self, stats_resource)
  origin = global_position
  detection.body_entered.connect(_on_body_entered)
  EventBus.health_changed.connect(_on_health_changed)
  EventBus.entity_died.connect(_on_entity_died)
  name_label.text = "Boss" if is_boss else "Enemy"
  if is_boss:
    name_label.text = "Boss"
    var mesh: MeshInstance3D = mesh_holder.get_node("Mesh")
    mesh.scale = Vector3(1.5, 1.5, 1.5)
    var mat: StandardMaterial3D = StandardMaterial3D.new()
    mat.albedo_color = Color(0.6, 0.2, 0.8)
    mesh.material_override = mat

func _exit_tree() -> void:
  Stats.deregister(self)

func _physics_process(delta: float) -> void:
  if not is_multiplayer_authority():
    global_position = global_position.lerp(sync_position, clamp(delta * 20.0, 0.0, 1.0))
    rotation.y = lerp_angle(rotation.y, sync_yaw, clamp(delta * 20.0, 0.0, 1.0))
    return
  if dead:
    return
  if not is_on_floor():
    velocity.y -= GRAVITY * delta
  attack_cd = max(0.0, attack_cd - delta)
  var target: Node = _get_target()
  if target == null:
    _return_to_origin(delta)
  else:
    _chase_or_attack(target, delta)
  move_and_slide()
  sync_position = global_position
  sync_yaw = rotation.y

func _get_target() -> Node:
  var aggro: Node = get_node_or_null("/root/World/Systems/AggroSystem")
  if aggro == null:
    aggro = get_node_or_null("/root/Dungeon/Systems/AggroSystem")
  if aggro and aggro.has_method("target_for"):
    var t: Node = aggro.target_for(self)
    if t:
      return t
  if invasion_target and is_instance_valid(invasion_target):
    return invasion_target
  return null

func _chase_or_attack(target: Node, delta: float) -> void:
  var t_pos: Vector3 = (target as Node3D).global_position
  var d: float = global_position.distance_to(t_pos)
  var attack_range: float = float(Stats.get_stat(self, "attack_range", 2.0))
  if d <= attack_range:
    velocity.x = move_toward(velocity.x, 0.0, 20.0 * delta)
    velocity.z = move_toward(velocity.z, 0.0, 20.0 * delta)
    var look := Vector3(t_pos.x - global_position.x, 0.0, t_pos.z - global_position.z)
    if look.length() > 0.01:
      rotation.y = atan2(look.x, look.z)
    if attack_cd <= 0.0:
      attack_cd = float(Stats.get_stat(self, "attack_cooldown", 1.5))
      var dmg: float = float(Stats.get_stat(self, "attack_damage", 5.0))
      EventBus.damage_requested.emit(self, target, dmg, Element.NONE)
  else:
    var dir := Vector3(t_pos.x - global_position.x, 0.0, t_pos.z - global_position.z).normalized()
    var speed: float = float(Stats.get_stat(self, "speed", 3.0))
    velocity.x = dir.x * speed
    velocity.z = dir.z * speed
    if dir.length() > 0.01:
      rotation.y = atan2(dir.x, dir.z)

func _return_to_origin(delta: float) -> void:
  var d: float = global_position.distance_to(origin)
  if d < 0.5:
    velocity.x = move_toward(velocity.x, 0.0, 20.0 * delta)
    velocity.z = move_toward(velocity.z, 0.0, 20.0 * delta)
    var max_hp: float = float(Stats.get_stat(self, "max_health", 100.0))
    var hp: float = float(Stats.get_stat(self, "health", 0.0))
    if hp > 0.0 and hp < max_hp:
      Stats.set_stat(self, "health", min(max_hp, hp + max_hp * 0.20 * delta))
      EventBus.health_changed.emit(self, Stats.get_stat(self, "health"), max_hp)
  else:
    var dir: Vector3 = Vector3(origin.x - global_position.x, 0.0, origin.z - global_position.z).normalized()
    var speed: float = float(Stats.get_stat(self, "speed", 3.0)) * 1.5
    velocity.x = dir.x * speed
    velocity.z = dir.z * speed
    if dir.length() > 0.01:
      rotation.y = atan2(dir.x, dir.z)

func _on_body_entered(body: Node) -> void:
  if not multiplayer.is_server() and multiplayer.multiplayer_peer != null:
    return
  if body.is_in_group("player") and not dead:
    EventBus.enemy_detected.emit(self, body)

func _on_health_changed(entity: Node, current: float, max: float) -> void:
  if entity != self:
    return
  var ratio: float = clamp(current / max if max > 0 else 0.0, 0.0, 1.0)
  if healthbar:
    healthbar.scale.x = max(0.01, ratio)

func _on_entity_died(entity: Node) -> void:
  if entity != self or dead:
    return
  dead = true
  if multiplayer.is_server() or multiplayer.multiplayer_peer == null:
    _on_death.rpc()
    var loot: Node = get_node_or_null("/root/World/Systems/LootSystem")
    if loot == null:
      loot = get_node_or_null("/root/Dungeon/Systems/LootSystem")
    if loot and loot.has_method("drop_loot_for"):
      loot.drop_loot_for(self, global_position)
    var xp_sys: Node = get_node_or_null("/root/World/Systems/XpSystem")
    if xp_sys == null:
      xp_sys = get_node_or_null("/root/Dungeon/Systems/XpSystem")
    if xp_sys and xp_sys.has_method("award_for_enemy"):
      xp_sys.award_for_enemy(self)
    if is_boss:
      EventBus.boss_defeated.emit(self)
    var t := get_tree().create_timer(2.0)
    t.timeout.connect(func():
      if is_instance_valid(self):
        queue_free())

@rpc("any_peer", "reliable", "call_local")
func _on_death() -> void:
  if multiplayer.multiplayer_peer != null and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer) and multiplayer.get_remote_sender_id() != 0 and multiplayer.get_remote_sender_id() != 1:
    return
  dead = true
  collision.disabled = true
  modulate_alpha(0.4)

func modulate_alpha(a: float) -> void:
  for child in mesh_holder.get_children():
    if child is MeshInstance3D and child.material_override:
      var c: Color = child.material_override.albedo_color
      c.a = a
      child.material_override.albedo_color = c
