extends BaseEnemy
class_name ArmedEnemy

## An enemy that uses the player model, animations, and can equip weapons
## Drops equipped weapons on death using the existing world weapon spawn system

## Movement
@export var move_speed: float = 4.0
@export var chase_range: float = 20.0
@export var attack_range: float = 2.5

## Combat (unarmed fallback)
@export var unarmed_damage: float = 10.0
@export var unarmed_knockback: float = 5.0
@export var attack_cooldown: float = 1.0
@export_category("Unarmed Attack Timing")
@export var unarmed_startup: float = 0.15
@export var unarmed_active: float = 0.2

## Weapon seeking (when unarmed)
@export_category("Weapon Seeking")
@export var seek_weapons_when_unarmed: bool = true  ## If true, unarmed enemies will seek nearby weapons
@export var weapon_seek_range: float = 30.0  ## How far to look for weapons
@export var weapon_pickup_range: float = 1.5  ## How close to get before picking up

## Weapon system
@export_category("Weapons")
@export var starting_weapon: WeaponData = null  ## Weapon to equip on spawn
@export var starting_offhand: WeaponData = null  ## Off-hand weapon to equip on spawn

## Body reference (LilguyBody for animations)
@export var _body: Node3D = null
@export var _weapon_attachment: BoneAttachment3D = null
@export var _weapon_container: Node3D = null
@export var _offhand_attachment: BoneAttachment3D = null
@export var _offhand_container: Node3D = null

## Runtime weapon state
var equipped_weapon: BaseWeapon = null
var equipped_offhand: BaseWeapon = null

## AI State
var _attack_timer: float = 0.0
var _is_attacking: bool = false
var _unarmed_hitbox: HitBox = null

## Visual feedback
var _hit_flash_timer: float = 0.0
const HIT_FLASH_DURATION: float = 0.2

## Position sync (manual sync instead of MultiplayerSynchronizer for dynamic spawning)
var _sync_timer: float = 0.0
const SYNC_INTERVAL: float = 0.05  # 20 times per second

func _enter_tree():
	# Enemies are always server-authoritative
	set_multiplayer_authority(1)

func _ready():
	super._ready()

	# Armed gladiators are worth more than basic enemies (arena economy)
	gold_reward = 25

	# Gladiators die for good - respawning would make them a repeatable gold farm
	# (they also don't re-show their body on respawn, so this avoids invisible enemies)
	can_respawn = false

	# Auto-find body if not set
	if _body == null:
		if has_node("LilguyRigged/Armature"):
			_body = get_node("LilguyRigged/Armature")

	# Auto-find weapon attachments
	if _weapon_attachment == null:
		_weapon_attachment = get_node_or_null("LilguyRigged/Armature/Skeleton3D/WeaponPoint")

	if _weapon_container == null and _weapon_attachment:
		_weapon_container = _weapon_attachment.get_node_or_null("WeaponContainer")

	if _offhand_attachment == null:
		_offhand_attachment = get_node_or_null("LilguyRigged/Armature/Skeleton3D/OffhandPoint")

	if _offhand_container == null and _offhand_attachment:
		_offhand_container = _offhand_attachment.get_node_or_null("OffhandContainer")

	# Setup unarmed hitbox
	call_deferred("_setup_unarmed_hitbox")

	# Equip starting weapons
	# Server will equip and send RPC to sync; clients also equip directly to handle late-join
	call_deferred("_equip_starting_weapons_local")

## Equip starting weapons - server uses RPC to sync, clients equip directly
func _equip_starting_weapons_local():
	# Wait a frame to ensure everything is ready
	await get_tree().process_frame

	# Check if multiplayer peer is assigned
	if multiplayer.multiplayer_peer == null:
		# No multiplayer yet, just equip locally
		if starting_weapon:
			_equip_weapon(starting_weapon, false)
		if starting_offhand:
			_equip_weapon(starting_offhand, true)
		return

	if multiplayer.is_server():
		# Server equips via RPC to sync to all clients
		if starting_weapon:
			print("[ArmedEnemy ", name, "] Server equipping starting weapon: ", starting_weapon.resource_path)
			rpc("_equip_weapon_sync", starting_weapon.resource_path, false)
		if starting_offhand:
			print("[ArmedEnemy ", name, "] Server equipping starting offhand: ", starting_offhand.resource_path)
			rpc("_equip_weapon_sync", starting_offhand.resource_path, true)
	else:
		# Client equips directly (for late-join clients who won't receive server's initial RPC)
		# Skip if already equipped (from server RPC)
		if starting_weapon and not equipped_weapon:
			print("[ArmedEnemy ", name, "] Client equipping starting weapon directly")
			_equip_weapon(starting_weapon, false)
		if starting_offhand and not equipped_offhand:
			print("[ArmedEnemy ", name, "] Client equipping starting offhand directly")
			_equip_weapon(starting_offhand, true)

## Equip weapon on all clients
@rpc("any_peer", "call_local", "reliable")
func _equip_weapon_sync(weapon_data_path: String, is_offhand: bool):
	print("[ArmedEnemy ", name, "] _equip_weapon_sync called on peer ", multiplayer.get_unique_id(), " path: ", weapon_data_path)
	if weapon_data_path == "":
		push_error("[ArmedEnemy] Empty weapon path!")
		return
	var data = load(weapon_data_path) as WeaponData
	if data:
		_equip_weapon(data, is_offhand)
	else:
		push_error("[ArmedEnemy] Failed to load weapon data from: ", weapon_data_path)

func _equip_weapon(data: WeaponData, is_offhand: bool = false):
	# Unequip current weapon in that hand first
	if is_offhand:
		if equipped_offhand:
			_unequip_weapon(true)
	else:
		if equipped_weapon:
			_unequip_weapon(false)

	# Determine attachment point
	var attach_point: Node3D
	if is_offhand:
		attach_point = _offhand_container if _offhand_container else _offhand_attachment
	else:
		attach_point = _weapon_container if _weapon_container else _weapon_attachment

	if not attach_point:
		push_error("[ArmedEnemy] No weapon attachment point found")
		return

	# Create weapon instance
	var weapon = BaseWeapon.new()
	weapon.weapon_data = data
	weapon.name = "EquippedOffHand" if is_offhand else "EquippedWeapon"

	# Add to scene first (so _ready is called and hitbox is set up)
	attach_point.add_child(weapon)

	# Set owner for damage routing (must be after add_child so hitbox exists)
	weapon.set_owner_character(self)

	# Store reference
	if is_offhand:
		equipped_offhand = weapon
	else:
		equipped_weapon = weapon

	print("[ArmedEnemy ", name, "] Equipped: ", data.weapon_name)

func _unequip_weapon(is_offhand: bool = false):
	if is_offhand:
		if equipped_offhand:
			equipped_offhand.queue_free()
			equipped_offhand = null
	else:
		if equipped_weapon:
			equipped_weapon.queue_free()
			equipped_weapon = null

func _process(delta):
	# Countdown timers
	if _attack_timer > 0:
		_attack_timer -= delta

	# Handle hit flash
	if _hit_flash_timer > 0:
		_hit_flash_timer -= delta
		if _hit_flash_timer <= 0:
			_reset_material()

func _physics_process(delta):
	super._physics_process(delta)

	# Only server runs AI and movement (check peer is assigned first)
	if multiplayer.multiplayer_peer == null or not multiplayer.is_server():
		return

	if is_dead:
		return

	# Apply gravity
	if not is_on_floor():
		velocity.y -= ProjectSettings.get_setting("physics/3d/default_gravity") * delta

	# AI behavior - prioritize weapon seeking when unarmed
	var seeking_weapon = false
	if seek_weapons_when_unarmed and equipped_weapon == null:
		seeking_weapon = _ai_seek_weapon(delta)

	# If not seeking a weapon (or armed), do normal combat
	if not seeking_weapon:
		if current_target and is_instance_valid(current_target):
			_ai_combat(delta)
		else:
			# Stop moving if no target
			velocity.x = 0
			velocity.z = 0

	move_and_slide()

	# Rotate body to face movement direction (like player does)
	var body_rotation_y: float = 0.0
	if _body and _body.has_method("apply_rotation") and velocity.length() > 0.1:
		_body.apply_rotation(velocity)
	if _body:
		body_rotation_y = _body.rotation.y

	# Animate body
	if _body and _body.has_method("animate"):
		_body.animate(velocity)

	# Sync position, rotation, and animation to clients periodically
	_sync_timer -= delta
	if _sync_timer <= 0:
		_sync_timer = SYNC_INTERVAL
		var current_anim = ""
		if _body:
			var anim_player = _body.get_node_or_null("../AnimationPlayer") as AnimationPlayer
			if anim_player:
				current_anim = anim_player.current_animation
		rpc("_sync_transform", global_position, body_rotation_y, current_anim)

## Sync position, body rotation, and animation from server to clients
@rpc("authority", "call_remote", "unreliable")
func _sync_transform(pos: Vector3, body_rot_y: float, anim_name: String = ""):
	# Only apply on clients (server is authoritative)
	if multiplayer.is_server():
		return

	global_position = pos
	if _body:
		_body.rotation.y = body_rot_y

		# Sync animation
		if anim_name != "":
			var anim_player = _body.get_node_or_null("../AnimationPlayer") as AnimationPlayer
			if anim_player and anim_player.has_animation(anim_name):
				if anim_player.current_animation != anim_name:
					anim_player.play(anim_name)

## Override to find nearest player
func get_nearest_player() -> Node:
	var players = get_players_in_range(1000.0)  # Essentially unlimited

	if players.is_empty():
		return null

	var nearest_player = null
	var nearest_distance = INF

	for player in players:
		var distance = global_position.distance_to(player.global_position)
		if distance < nearest_distance:
			nearest_distance = distance
			nearest_player = player

	return nearest_player

## Find the nearest WorldWeapon within range
func get_nearest_world_weapon() -> WorldWeapon:
	var level = get_tree().get_current_scene()
	if not level:
		return null

	var weapons_container = level.get_node_or_null("WeaponsContainer")
	if not weapons_container:
		return null

	var nearest_weapon: WorldWeapon = null
	var nearest_distance = weapon_seek_range

	for child in weapons_container.get_children():
		if child is WorldWeapon:
			var distance = global_position.distance_to(child.global_position)
			if distance < nearest_distance:
				nearest_distance = distance
				nearest_weapon = child

	return nearest_weapon

## Update target
func _update_target():
	if not is_aggressive:
		return

	var nearest = get_nearest_player()

	if nearest:
		if current_target != nearest:
			current_target = nearest
			target_changed.emit(nearest)
	else:
		if current_target != null:
			current_target = null
			target_changed.emit(null)

## Combat AI
func _ai_combat(delta):
	if not current_target or not is_instance_valid(current_target):
		return

	var target_pos = current_target.global_position
	var direction = (target_pos - global_position).normalized()
	var distance = global_position.distance_to(target_pos)

	# Get attack range - use a close fixed range to ensure hits connect
	# The hitbox is on the weapon in the enemy's hand, so we need to be close
	var current_attack_range = 2.0  # Fixed close range for melee

	# If in attack range, attack
	if distance <= current_attack_range:
		velocity.x = 0
		velocity.z = 0

		# Face target while attacking (use body rotation like player does)
		if _body and _body.has_method("apply_rotation"):
			var face_dir = Vector3(direction.x, 0, direction.z) * move_speed
			_body.apply_rotation(face_dir)

		if _attack_timer <= 0 and not _is_attacking:
			_perform_attack()
	else:
		# Chase target - velocity direction will be used for body rotation
		velocity.x = direction.x * move_speed
		velocity.z = direction.z * move_speed

## Weapon seeking AI - returns true if actively seeking a weapon
func _ai_seek_weapon(delta) -> bool:
	var nearest_weapon = get_nearest_world_weapon()
	if not nearest_weapon or not is_instance_valid(nearest_weapon):
		return false

	var weapon_pos = nearest_weapon.global_position
	var direction = (weapon_pos - global_position).normalized()
	var distance = global_position.distance_to(weapon_pos)

	# If close enough, pick up the weapon
	if distance <= weapon_pickup_range:
		velocity.x = 0
		velocity.z = 0
		_pickup_world_weapon(nearest_weapon)
		return true

	# Move towards the weapon
	velocity.x = direction.x * move_speed
	velocity.z = direction.z * move_speed
	return true

## Pick up a world weapon (server only)
func _pickup_world_weapon(world_weapon: WorldWeapon):
	if not multiplayer.is_server():
		return

	if not world_weapon or not is_instance_valid(world_weapon):
		return

	var weapon_data = world_weapon.weapon_data
	if not weapon_data:
		return

	var resource_path = weapon_data.resource_path
	if resource_path == "":
		push_error("[ArmedEnemy] WorldWeapon has no resource path!")
		return

	var weapon_id = world_weapon.weapon_id
	if weapon_id == -1:
		push_error("[ArmedEnemy] WorldWeapon has invalid weapon_id!")
		return

	print("[ArmedEnemy ", name, "] Picking up weapon: ", weapon_data.weapon_name)

	# Equip the weapon on all clients
	rpc("_equip_weapon_sync", resource_path, false)

	# Remove the world weapon from all clients using level's system
	var level = get_tree().get_current_scene()
	if level and level.has_method("remove_world_weapon"):
		level.remove_world_weapon(weapon_id)
	else:
		push_error("[ArmedEnemy] Level doesn't have remove_world_weapon method!")

## Perform attack
func _perform_attack():
	if _is_attacking or not multiplayer.is_server():
		return

	# Use weapon if equipped
	if equipped_weapon and equipped_weapon.weapon_data:
		_perform_weapon_attack()
	else:
		_perform_unarmed_attack()

func _perform_weapon_attack():
	var weapon = equipped_weapon
	var data = weapon.weapon_data

	var total_duration = data.startup_time + data.active_time
	var cooldown = max(data.attack_cooldown, total_duration)

	_attack_timer = cooldown
	_is_attacking = true

	# Play animation on all clients
	var anim_name = data.attack_animation if data.attack_animation else "Attack_OneHand"
	rpc("_sync_attack_animation", anim_name)

	# Use weapon's built-in attack activation
	_activate_weapon_hitbox_direct(weapon)

func _perform_unarmed_attack():
	var total_duration = unarmed_startup + unarmed_active
	var cooldown = max(attack_cooldown, total_duration)

	_attack_timer = cooldown
	_is_attacking = true

	# Play animation
	rpc("_sync_attack_animation", "Attack_OneHand")

	# Activate unarmed hitbox
	_activate_unarmed_hitbox()

## Activate weapon hitbox for attack (direct access to weapon's internal hitbox)
func _activate_weapon_hitbox_direct(weapon: BaseWeapon):
	if not weapon or not multiplayer.is_server():
		_is_attacking = false
		return

	var data = weapon.weapon_data

	# Access weapon's internal hitbox
	var hitbox = weapon._hitbox

	if not hitbox:
		print("[ArmedEnemy] No hitbox found on weapon, trying to find it")
		hitbox = weapon.get_node_or_null("HitBox") as HitBox

	if not hitbox:
		push_error("[ArmedEnemy] Cannot find hitbox on weapon!")
		_is_attacking = false
		return

	# Make sure hitbox has correct owner
	hitbox.owner_entity = self

	# STARTUP PHASE
	if data.startup_time > 0:
		await get_tree().create_timer(data.startup_time).timeout

	if not is_instance_valid(hitbox) or is_dead:
		_is_attacking = false
		return

	# ACTIVE PHASE
	hitbox.activate()

	await get_tree().create_timer(data.active_time).timeout

	# RECOVERY PHASE
	if hitbox and is_instance_valid(hitbox):
		hitbox.deactivate()

	_is_attacking = false

## Setup unarmed hitbox
func _setup_unarmed_hitbox():
	_unarmed_hitbox = HitBox.new()
	_unarmed_hitbox.name = "UnarmedHitBox"
	_unarmed_hitbox.owner_entity = self
	_unarmed_hitbox.set_stats(unarmed_damage, unarmed_knockback)

	# Add collision shape BEFORE adding hitbox to tree (so _ready can find it)
	var collision = CollisionShape3D.new()
	var sphere = SphereShape3D.new()
	sphere.radius = attack_range
	collision.shape = sphere
	collision.position = Vector3(0, 0.8, -attack_range * 0.75)
	_unarmed_hitbox.add_child(collision)

	# Now attach the fully configured hitbox to body
	if _body:
		_body.add_child(_unarmed_hitbox)
	else:
		add_child(_unarmed_hitbox)

	# Connect hit signal
	_unarmed_hitbox.hit_landed.connect(_on_hitbox_hit)

func _activate_unarmed_hitbox():
	if not _unarmed_hitbox or not multiplayer.is_server():
		_is_attacking = false
		return

	# STARTUP PHASE
	if unarmed_startup > 0:
		await get_tree().create_timer(unarmed_startup).timeout

	if not _unarmed_hitbox or not is_instance_valid(_unarmed_hitbox) or is_dead:
		_is_attacking = false
		return

	# ACTIVE PHASE
	_unarmed_hitbox.activate()

	await get_tree().create_timer(unarmed_active).timeout

	# RECOVERY PHASE
	if _unarmed_hitbox and is_instance_valid(_unarmed_hitbox):
		_unarmed_hitbox.deactivate()

	_is_attacking = false

## Called when hitbox hits something
func _on_hitbox_hit(target: Node, damage_amount: float, knockback_amount: float, attacker_pos: Vector3):
	if not target or not multiplayer.is_server():
		return

	# Flash target's hurtbox
	if target is Node:
		var hurtbox = target.find_child("HurtBox", true, false)
		if hurtbox and hurtbox.has_method("flash_hit"):
			hurtbox.flash_hit()

	# Apply damage directly (we're server)
	if target is BaseUnit:
		target.take_damage(damage_amount, 1, knockback_amount, global_position)

## Sync attack animation
@rpc("any_peer", "call_local", "reliable")
func _sync_attack_animation(anim_name: String):
	if _body and _body.has_method("play_attack"):
		_body.play_attack(anim_name)

## Override hurt animation
@rpc("any_peer", "call_local", "reliable")
func _play_hurt_animation():
	_flash_red()

## Flash red when hit
func _flash_red():
	_hit_flash_timer = HIT_FLASH_DURATION

	# Flash all mesh instances in body
	if _body:
		var meshes = _find_mesh_instances(_body)
		for mesh in meshes:
			_apply_red_flash(mesh)

func _find_mesh_instances(node: Node) -> Array[MeshInstance3D]:
	var meshes: Array[MeshInstance3D] = []
	if node is MeshInstance3D:
		meshes.append(node)
	for child in node.get_children():
		meshes.append_array(_find_mesh_instances(child))
	return meshes

func _apply_red_flash(mesh: MeshInstance3D):
	if not mesh:
		return

	for i in range(mesh.get_surface_override_material_count()):
		var material = mesh.get_surface_override_material(i)
		if not material:
			material = mesh.mesh.surface_get_material(i)
			if material:
				material = material.duplicate()
				mesh.set_surface_override_material(i, material)

		if material and material is StandardMaterial3D:
			material.albedo_color = Color(1.5, 0.3, 0.3)

func _reset_material():
	# Reset to a neutral color after flash
	if _body:
		var meshes = _find_mesh_instances(_body)
		for mesh in meshes:
			_reset_mesh_material(mesh)

func _reset_mesh_material(mesh: MeshInstance3D):
	if not mesh:
		return

	for i in range(mesh.get_surface_override_material_count()):
		var material = mesh.get_surface_override_material(i)
		if material and material is StandardMaterial3D:
			# Reset to a default enemy color (reddish)
			material.albedo_color = Color(0.8, 0.3, 0.3)

## Death callback - drop weapons
func _on_enemy_died(killer_id: int):
	super._on_enemy_died(killer_id)

	# Hide body
	if _body:
		_body.visible = false

	# Disable collision so players can walk through
	var collision_shape = get_node_or_null("CollisionShape3D")
	if collision_shape:
		collision_shape.disabled = true

	# Disable hurtbox
	var hurtbox = get_node_or_null("HurtBox")
	if hurtbox:
		hurtbox.monitoring = false
		hurtbox.monitorable = false

	# Deactivate hitboxes
	if _unarmed_hitbox:
		_unarmed_hitbox.deactivate()

	# Drop equipped weapons (server only)
	if multiplayer.is_server():
		_drop_all_weapons()

	print("[ArmedEnemy ", name, "] killed by ", killer_id)

## Drop all equipped weapons
func _drop_all_weapons():
	if not multiplayer.is_server():
		return

	# Drop main hand weapon
	if equipped_weapon and equipped_weapon.weapon_data:
		_spawn_dropped_weapon(equipped_weapon.weapon_data, false)

	# Drop off-hand weapon
	if equipped_offhand and equipped_offhand.weapon_data:
		_spawn_dropped_weapon(equipped_offhand.weapon_data, true)

	# Clear equipped weapons on all clients
	rpc("_clear_equipped_weapons")

## Spawn a dropped weapon in the world
func _spawn_dropped_weapon(data: WeaponData, is_offhand: bool):
	if not multiplayer.is_server():
		return

	var resource_path = data.resource_path
	if resource_path == "":
		push_error("[ArmedEnemy] WeaponData has no resource path!")
		return

	# Calculate spawn position with slight offset and upward velocity
	var offset = Vector3.ZERO
	if is_offhand:
		offset = transform.basis.x * -0.5  # Left side
	else:
		offset = transform.basis.x * 0.5  # Right side

	var spawn_pos = global_position + offset
	spawn_pos.y += 1.5  # Spawn above death position

	# Random velocity to scatter weapons
	var velocity = Vector3(
		randf_range(-2.0, 2.0),
		randf_range(3.0, 5.0),  # Upward
		randf_range(-2.0, 2.0)
	)

	# Use level's weapon spawning system
	var level = get_tree().get_current_scene()
	if level and level.has_method("spawn_world_weapon"):
		level._weapon_spawn_counter += 1
		level.rpc("spawn_world_weapon", resource_path, spawn_pos, velocity, level._weapon_spawn_counter)
		print("[ArmedEnemy ", name, "] Dropped weapon: ", data.weapon_name)

## Clear equipped weapons on all clients
@rpc("any_peer", "call_local", "reliable")
func _clear_equipped_weapons():
	_unequip_weapon(false)
	_unequip_weapon(true)

## Respawn callback
func _on_enemy_respawned():
	super._on_enemy_respawned()

	# Show body
	if _body:
		_body.visible = true
		_reset_material()

	# Re-enable collision
	var collision_shape = get_node_or_null("CollisionShape3D")
	if collision_shape:
		collision_shape.disabled = false

	# Re-enable hurtbox
	var hurtbox = get_node_or_null("HurtBox")
	if hurtbox:
		hurtbox.monitoring = false  # Hurtbox doesn't monitor, it's monitored
		hurtbox.monitorable = true

	# Reset state
	_attack_timer = 0.0
	_is_attacking = false

	# Re-equip starting weapons
	call_deferred("_equip_starting_weapons_local")

	print("[ArmedEnemy ", name, "] respawned")

## Set enemy color (hue-based like player)
func set_enemy_color(hue: float):
	if _body and _body.has_method("set_character_color"):
		_body.set_character_color(hue)
