extends Node2D

# Enemy Spawner - Spawns enemies at this position

@export var enemy_scenes: Array[PackedScene] = [] # List of enemy scenes to randomly choose from
@export var spawn_on_ready: bool = true
@export var respawn_time: float = 10.0 # Time to respawn after enemy dies
@export var max_enemies: int = 1 # Maximum number of enemies this spawner can have alive

var spawned_enemies: Array = []
var respawn_timer: float = 0.0
var smoke_puff_scene = preload("res://scenes/smoke_puff.tscn")
var has_ever_spawned: bool = false  # Track if this spawner has ever spawned an enemy

func _ready():
	print("========== EnemySpawner READY ==========")
	print("  Position: ", global_position)
	print("  Is server: ", multiplayer.is_server())
	print("  Has multiplayer peer: ", multiplayer.has_multiplayer_peer())
	print("  Is authority: ", str(is_multiplayer_authority()) if multiplayer.has_multiplayer_peer() else "N/A")
	print("  spawn_on_ready: ", spawn_on_ready)
	print("  max_enemies: ", max_enemies)
	print("  enemy_scenes.size(): ", enemy_scenes.size())
	print("  Parent: ", get_parent())
	
	# Verify enemy_scenes is set
	if enemy_scenes.size() == 0:
		push_error("EnemySpawner: ERROR - enemy_scenes array is EMPTY! Spawner will not be able to spawn enemies!")
	
	# Spawn on server, or in single player (no multiplayer peer)
	var should_spawn = spawn_on_ready and (multiplayer.is_server() or not multiplayer.has_multiplayer_peer())
	print("  Should spawn? ", should_spawn)
	
	if should_spawn:
		print("  Calling spawn_enemy()...")
		call_deferred("spawn_enemy") # Use call_deferred to ensure scene is ready
	else:
		print("  NOT spawning - conditions not met (spawn_on_ready=", spawn_on_ready, ", will spawn when player enters room)")
	print("========================================")

func _process(delta):
	# Only server spawns, or single player
	if multiplayer.has_multiplayer_peer() and not multiplayer.is_server():
		return
	
	# Clean up dead enemies from list
	spawned_enemies = spawned_enemies.filter(func(e): return is_instance_valid(e) and not e.is_dead)
	
	# Check if we need to respawn (only if respawn_time > 0)
	# Puzzle spawners have respawn_time = 0.0, so they won't respawn
	if respawn_time > 0.0 and spawned_enemies.size() < max_enemies:
		respawn_timer += delta
		if respawn_timer >= respawn_time:
			spawn_enemy()
			respawn_timer = 0.0

func spawn_enemy():
	print(">>> spawn_enemy() CALLED <<<")
	print("  Spawner: ", name, " at ", global_position)
	print("  enemy_scenes.size(): ", enemy_scenes.size())
	print("  spawned_enemies.size(): ", spawned_enemies.size(), " / max_enemies: ", max_enemies)
	print("  spawn_on_ready: ", spawn_on_ready)
	
	# CRITICAL: Check if we can spawn (don't spawn if already at max)
	# Clean up dead enemies first
	spawned_enemies = spawned_enemies.filter(func(e): return is_instance_valid(e) and not e.is_dead)
	
	if spawned_enemies.size() >= max_enemies:
		print("  ERROR: Cannot spawn - already at max enemies (", spawned_enemies.size(), " >= ", max_enemies, ")")
		return
	
	# Only spawn on server (authority)
	if multiplayer.has_multiplayer_peer() and not is_multiplayer_authority():
		print("  ERROR: Cannot spawn - not multiplayer authority!")
		return
	
	# Choose enemy scene to spawn
	var scene_to_spawn: PackedScene = null
	if enemy_scenes.size() > 0:
		# Use random scene from list
		scene_to_spawn = enemy_scenes[randi() % enemy_scenes.size()]
		print("  Selected enemy scene from list: ", scene_to_spawn)
	else:
		push_error("ERROR: enemy_scenes array is EMPTY! Spawner has no enemy scenes to spawn!")
		return
	
	if not scene_to_spawn:
		push_error("ERROR: Failed to select enemy scene!")
		return
	
	print("  Spawning enemy at ", global_position)
	
	# CRITICAL: Spawn 3-4 smoke puffs first, wait for them to finish, THEN spawn enemy
	var num_puffs = randi_range(3, 4)  # 3 or 4 smoke puffs
	print("  Spawning ", num_puffs, " smoke puffs before enemy...")
	
	# Spawn multiple smoke puffs at slightly different positions
	var smoke_puffs = []
	var puff_spawn_radius = 8.0  # Pixels - spawn puffs in a small area around spawner
	var puff_positions = []  # Store positions for syncing to clients
	
	# Calculate puff positions first
	for i in range(num_puffs):
		var puff_offset = Vector2(
			randf_range(-puff_spawn_radius, puff_spawn_radius),
			randf_range(-puff_spawn_radius, puff_spawn_radius)
		)
		puff_positions.append(global_position + puff_offset)
	
	# Spawn smoke puffs on server
	for puff_pos in puff_positions:
		var puff = _spawn_smoke_puff_at_position(puff_pos)
		if puff:
			smoke_puffs.append(puff)
	
	# Sync smoke puffs to all clients
	if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
		var game_world = get_tree().get_first_node_in_group("game_world")
		if not game_world:
			# Fallback: traverse up the tree to find GameWorld
			var node = get_parent()
			while node:
				if node.has_method("_sync_smoke_puffs"):
					game_world = node
					break
				node = node.get_parent()
		
		if game_world and game_world.has_method("_sync_smoke_puffs"):
			game_world._rpc_to_ready_peers("_sync_smoke_puffs", [name, puff_positions])
	
	# Wait for smoke puffs to finish animating before spawning enemy
	# Reduced duration for faster spawning: 4 frames * (1/10.0) seconds per frame = 0.4s, plus move_duration 1.0s, plus fade_duration 0.3s = ~1.7s total
	var smoke_animation_duration = (4.0 / 10.0) + 1.0 + 0.3  # Reduced from 2.4s to 1.7s
	await get_tree().create_timer(smoke_animation_duration).timeout
	
	print("  Smoke puffs finished - now spawning enemy...")
	
	print("  Instantiating enemy scene...")
	var enemy = scene_to_spawn.instantiate()
	
	if not enemy:
		push_error("ERROR: Failed to instantiate enemy!")
		return
	
	print("  Enemy instantiated: ", enemy)
	enemy.global_position = global_position
	# Set spawn position for deterministic appearance seed (before adding to scene)
	if "spawn_position" in enemy:
		enemy.spawn_position = global_position
	print("  Set enemy position to: ", global_position)
	
	# If it's a humanoid enemy, randomize the humanoid_type
	var humanoid_type = null
	if scene_to_spawn.resource_path.ends_with("enemy_humanoid.tscn"):
		# Random humanoid type: 0=CYCLOPS, 1=DEMON, 2=HUMANOID, 3=NIGHTELF, 4=GOBLIN, 5=ORC, 6=SKELETON
		# Weight towards common types (goblins, humans, orcs) - 40% goblin, 30% humanoid, 20% orc, 10% other
		var rand_val = randf()
		var type_value = 2 # Default to HUMANOID
		if rand_val < 0.4:
			type_value = 4 # GOBLIN (40%)
		elif rand_val < 0.7:
			type_value = 2 # HUMANOID (30%)
		elif rand_val < 0.9:
			type_value = 5 # ORC (20%)
		else:
			# 10% for other types (distributed evenly)
			var other_types = [0, 1, 3, 6] # CYCLOPS, DEMON, NIGHTELF, SKELETON
			type_value = other_types[randi() % other_types.size()]
		enemy.humanoid_type = type_value
		humanoid_type = type_value
		print("  Randomized humanoid_type: ", type_value)
	
	# CRITICAL: Mark this enemy as spawned from a spawner (for door puzzle tracking)
	enemy.set_meta("spawned_from_spawner", true)
	enemy.set_meta("spawner_name", name)
	
	# CRITICAL: Set collision mask BEFORE adding to scene to ensure enemies collide with walls (layer 7 = bit 6 = 64)
	# This overrides any collision_mask set in the scene file
	enemy.collision_mask = 1 | 2 | 64  # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
	
	# Set multiplayer authority BEFORE adding to scene tree (CRITICAL for RPC to work!)
	if multiplayer.has_multiplayer_peer():
		enemy.set_multiplayer_authority(1)
	
	# Add to YSort node for automatic Y-sorting
	var ysort = get_parent().get_node_or_null("Entities")
	var parent = ysort if ysort else get_parent()
	print("  Parent node: ", parent)
	
	if not parent:
		push_error("ERROR: No parent node!")
		return
	
	print("  Adding enemy as child...")
	parent.add_child(enemy)
	
	# CRITICAL: Re-verify collision_mask AFTER adding to scene (in case _ready() or scene file overrides it)
	# Use call_deferred to ensure _ready() has completed first, then set the entire mask
	call_deferred("_verify_enemy_collision_mask", enemy)
	
	# Determine which scene index was used (for syncing to clients)
	var scene_index = -1
	if enemy_scenes.size() > 0:
		# Find which scene was used
		for i in range(enemy_scenes.size()):
			if enemy_scenes[i] == scene_to_spawn:
				scene_index = i
				break
	
	# Store scene_index as metadata on the enemy (for syncing existing enemies to new clients)
	enemy.set_meta("spawn_scene_index", scene_index)
	
	spawned_enemies.append(enemy)
	has_ever_spawned = true  # Mark that this spawner has spawned at least once
	
	print("  ✓ Successfully spawned enemy: ", enemy.name, " at ", global_position, " scene_index: ", scene_index)
	print("  Total spawned enemies: ", spawned_enemies.size())
	
	# If this spawner is marked for one-time spawn, destroy it after spawning
	if has_meta("spawn_once") and get_meta("spawn_once"):
		print("  Spawner marked for one-time spawn - destroying after spawn")
		call_deferred("queue_free")  # Destroy spawner after spawning once
	
	# Sync spawn to all clients via GameWorld
	if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
		# Get GameWorld by traversing up the tree (spawner is child of Entities, which is child of GameWorld)
		var game_world = get_tree().get_first_node_in_group("game_world")
		if not game_world:
			# Fallback: traverse up the tree to find GameWorld
			var node = get_parent()
			while node:
				if node.has_method("_sync_enemy_spawn"):
					game_world = node
					break
				node = node.get_parent()
		
		if game_world and game_world.has_method("_sync_enemy_spawn"):
			# Use spawner name for identification
			# Pass humanoid_type if it's a humanoid enemy (for syncing to clients)
			var sync_humanoid_type = humanoid_type if humanoid_type != null else -1
			print("  DEBUG: Calling _sync_enemy_spawn.rpc with name=", name, " pos=", global_position, " scene_index=", scene_index, " humanoid_type=", sync_humanoid_type)
			game_world._rpc_to_ready_peers("_sync_enemy_spawn", [name, global_position, scene_index, sync_humanoid_type])
			print("  Sent RPC to sync enemy spawn to clients: spawner=", name, " pos=", global_position, " scene_index=", scene_index, " humanoid_type=", sync_humanoid_type)
		else:
			var has_method_str = str(game_world.has_method("_sync_enemy_spawn")) if game_world else "N/A"
			push_error("ERROR: Could not find GameWorld or _sync_enemy_spawn method! game_world=", game_world, " has_method=", has_method_str)

func spawn_enemy_at_position(spawn_pos: Vector2, scene_index: int = -1, humanoid_type: int = -1):
	# This method is called by GameWorld RPC to spawn enemies on clients
	# scene_index tells us which scene from enemy_scenes array was used on the server
	# humanoid_type tells us the humanoid type if it's a humanoid enemy (for syncing from server)
	var scene_to_spawn: PackedScene = null
	if scene_index >= 0 and scene_index < enemy_scenes.size():
		# Use the scene index that was synced from server
		scene_to_spawn = enemy_scenes[scene_index]
		print("Client: Using enemy scene at index ", scene_index, ": ", scene_to_spawn)
	elif enemy_scenes.size() > 0:
		# Fallback: use first scene if index is invalid
		scene_to_spawn = enemy_scenes[0]
		print("Client: Invalid scene_index, using first enemy scene: ", scene_to_spawn)
	
	if not scene_to_spawn:
		push_error("ERROR: Spawner has no enemy scenes set! Add scenes to enemy_scenes array.")
		return
	
	print("Client: spawn_enemy_at_position called at ", spawn_pos, " humanoid_type: ", humanoid_type)
	
	# NOTE: Smoke puffs are synced via RPC (_sync_smoke_puffs) from server
	# so we don't spawn them here - they're already spawned by the RPC handler
	
	# Instantiate and add enemy
	var enemy = scene_to_spawn.instantiate()
	if not enemy:
		push_error("ERROR: Failed to instantiate enemy on client!")
		return
	
	enemy.global_position = spawn_pos
	# Set spawn position for deterministic appearance seed (before adding to scene)
	if "spawn_position" in enemy:
		enemy.spawn_position = spawn_pos
	
	# If it's a humanoid enemy, set the humanoid_type from server
	if humanoid_type >= 0 and scene_to_spawn.resource_path.ends_with("enemy_humanoid.tscn"):
		enemy.humanoid_type = humanoid_type
		print("Client: Set humanoid_type to ", humanoid_type)
	
	# CRITICAL: Set collision mask BEFORE adding to scene to ensure enemies collide with walls (layer 7 = bit 6 = 64)
	# This overrides any collision_mask set in the scene file
	enemy.collision_mask = 1 | 2 | 64  # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
	
	# Set multiplayer authority BEFORE adding to scene tree (CRITICAL!)
	if multiplayer.has_multiplayer_peer():
		enemy.set_multiplayer_authority(1)
	
	# Add to YSort node for automatic Y-sorting
	var ysort = get_parent().get_node_or_null("Entities")
	var parent = ysort if ysort else get_parent()
	if not parent:
		push_error("ERROR: No parent node on client!")
		return
	
	parent.add_child(enemy)
	
	# CRITICAL: Re-verify collision_mask AFTER adding to scene (in case _ready() or scene file overrides it)
	# Use call_deferred to ensure _ready() has completed first, then set the entire mask
	call_deferred("_verify_enemy_collision_mask", enemy)
	
	print("  ✓ Client spawned enemy: ", enemy.name, " at ", spawn_pos)

func get_spawned_enemy_positions() -> Array:
	# Return array of dictionaries with position, scene_index, and humanoid_type for all currently spawned enemies
	var enemy_data = []
	for enemy in spawned_enemies:
		if is_instance_valid(enemy) and not enemy.is_dead:
			var scene_index = -1
			if enemy.has_meta("spawn_scene_index"):
				scene_index = enemy.get_meta("spawn_scene_index")
			var data = {"position": enemy.global_position, "scene_index": scene_index}
			# Include humanoid_type if it's a humanoid enemy
			if "humanoid_type" in enemy:
				data["humanoid_type"] = enemy.humanoid_type
			else:
				data["humanoid_type"] = -1
			enemy_data.append(data)
	return enemy_data

func _verify_enemy_collision_mask(enemy: Node):
	# Verify and correct enemy collision_mask after _ready() has completed
	# This ensures enemies always collide with walls (layer 7 = bit 6 = 64), not layer 3 or 4
	if not is_instance_valid(enemy):
		return
	
	var expected_mask = 1 | 2 | 64  # Collide with players (layer 1), objects (layer 2), and walls (layer 7 = bit 6 = 64)
	if enemy.collision_mask != expected_mask:
		print("EnemySpawner: WARNING - Enemy ", enemy.name, " collision_mask was ", enemy.collision_mask, ", expected ", expected_mask, "! Correcting...")
		enemy.collision_mask = expected_mask
		
		# Double-check by setting individual layers to be absolutely sure
		enemy.set_collision_mask_value(1, true)   # Players
		enemy.set_collision_mask_value(2, true)   # Objects
		enemy.set_collision_mask_value(7, true)   # Walls (layer 7)
		enemy.set_collision_mask_value(3, false)  # Ensure layer 3 is NOT set
		enemy.set_collision_mask_value(4, false)  # Ensure layer 4 is NOT set
		print("EnemySpawner: Corrected enemy ", enemy.name, " collision_mask to ", enemy.collision_mask)

func _spawn_smoke_puff():
	# Legacy function - use _spawn_smoke_puff_at_position instead
	_spawn_smoke_puff_at_position(global_position)

func _spawn_smoke_puff_at_position(puff_position: Vector2) -> Node:
	print("  _spawn_smoke_puff_at_position() called at ", puff_position)
	print("    smoke_puff_scene: ", smoke_puff_scene)
	
	if smoke_puff_scene:
		print("    Instantiating smoke puff...")
		var puff = smoke_puff_scene.instantiate()
		if puff:
			puff.global_position = puff_position
			# Ensure smoke puff is visible - set high z_index so it appears above ground
			puff.z_index = 10  # High z-index to ensure visibility
			if puff.has_node("Sprite2D"):
				puff.get_node("Sprite2D").z_index = 10
			
			# Add to Entities node (same as enemies) for proper layering
			var entities_node = get_parent().get_node_or_null("Entities")
			var parent = entities_node if entities_node else get_parent()
			if parent:
				parent.add_child(puff)
				print("    ✓ Smoke puff spawned at ", puff_position, " z_index: ", puff.z_index)
				return puff
			else:
				print("    ERROR: No parent node for smoke puff!")
				puff.queue_free()
				return null
		else:
			print("    ERROR: Failed to instantiate smoke puff")
			return null
	else:
		print("    WARNING: No smoke puff scene loaded")
		return null
