extends CharacterBody2D

# Loot Item - Coins and food items that drop from enemies

enum LootType {
	COIN,
	APPLE,
	BANANA,
	CHERRY,
	KEY,
	ITEM # Item instance (equipment, consumables, etc.)
}

@export var loot_type: LootType = LootType.COIN

# Z-axis physics (like boxes and players)
var position_z: float = 0.0
var velocity_z: float = 0.0
var acceleration_z: float = 0.0
var is_airborne: bool = true
var velocity_set_by_spawner: bool = false # Track if velocity was set externally

# Bounce physics
var bounce_restitution: float = 0.6 # How much bounce energy is retained (0-1) - matches old code
var min_bounce_velocity: float = 40.0 # Minimum velocity needed to bounce
var friction: float = 8.0 # Friction when on ground (lower = more gradual slowdown, matches old code)
var bounce_timer: float = 0.0 # Prevent rapid bounce sounds

# Multiplayer sync and prediction
var sync_timer: float = 0.0 # Timer for periodic position/velocity sync
var sync_interval: float = 0.05 # Sync every 0.05 seconds (20 times per second) for smoother sync
var last_sync_time: float = 0.0 # Track last server sync time for reconciliation
var server_position: Vector2 = Vector2.ZERO # Last server-authoritative position
var server_velocity: Vector2 = Vector2.ZERO # Last server-authoritative velocity
var server_position_z: float = 0.0 # Last server-authoritative Z position
var server_velocity_z: float = 0.0 # Last server-authoritative Z velocity
var prediction_error_threshold: float = 10.0 # Distance threshold before correcting (pixels)
var correction_smoothing: float = 0.3 # Lerp factor for smooth correction (0-1, lower = smoother)

# Loot properties
var coin_value: int = 1
var heal_amount: float = 20.0
var collected: bool = false
var item: Item = null # Item instance (for LootType.ITEM)

@onready var sprite = $Sprite2D
@onready var shadow = $Shadow
@onready var pickup_area = $PickupArea
@onready var collision_shape = $CollisionShape2D
@onready var sfx_coin_bounce = $SfxCoinBounce
@onready var sfx_coin_collect = $SfxCoinCollect
@onready var sfx_loot_collect = $SfxLootCollect
@onready var sfx_potion_collect = $SfxPotionCollect
@onready var sfx_banana_collect = $SfxBananaCollect
@onready var sfx_key_collect = $SfxKeyCollect

# Quantity badge for items with quantity > 1
var quantity_badge: Label = null

func _ready():
	add_to_group("loot")
	
	# Setup shadow
	if shadow:
		shadow.modulate = Color(0, 0, 0, 0.5)
		shadow.z_index = -1
	
	# Setup pickup area
	if pickup_area:
		pickup_area.body_entered.connect(_on_pickup_area_body_entered)
		# Set collision mask to detect players (layer 1)
		pickup_area.collision_mask = 1
	
	# Top-down physics
	motion_mode = MOTION_MODE_FLOATING
	
	# Initial velocity is set by spawner (server) or synced via RPC (clients)
	# If not set externally, use defaults (shouldn't happen in normal flow)
	if not velocity_set_by_spawner:
		velocity_z = randf_range(80.0, 120.0)
		var random_angle = randf() * PI * 2
		var random_force = randf_range(50.0, 100.0)
		velocity = Vector2(cos(random_angle), sin(random_angle)) * random_force
		is_airborne = true
	
	# Setup sprite based on loot type (call after all properties are set)
	call_deferred("_setup_sprite")
	
	# CRITICAL: Duplicate sprite material after setup so it isn't shared between loot instances
	# This must be called after _setup_sprite because materials may be applied there (e.g., ItemDatabase.apply_item_colors_to_sprite)
	call_deferred("_duplicate_sprite_material")
	
	# Setup collision shape based on loot type
	call_deferred("_setup_collision_shape")
	
	# Adjust bounce properties based on loot type
	if loot_type == LootType.COIN:
		bounce_restitution = 0.6 # Matches old code - more bouncy
		min_bounce_velocity = 40.0
		friction = 8.0 # Lower friction for coins - more gradual slowdown
	else:
		bounce_restitution = 0.3 # Lower bounce for food items
		min_bounce_velocity = 60.0
		friction = 12.0 # Slightly higher friction for food items

func _setup_sprite():
	if not sprite:
		return
	
	match loot_type:
		LootType.COIN:
			# Load coin texture
			var coin_texture = load("res://assets/gfx/pickups/gold_coin.png")
			if coin_texture and sprite:
				sprite.texture = coin_texture
				sprite.hframes = 6
				sprite.vframes = 1
				sprite.frame = 0
				print("Coin sprite setup: texture=", coin_texture != null, " hframes=", sprite.hframes, " vframes=", sprite.vframes, " frame=", sprite.frame)
			else:
				print("ERROR: Coin texture or sprite is null! texture=", coin_texture, " sprite=", sprite)
		LootType.APPLE:
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			if items_texture:
				sprite.texture = items_texture
				sprite.hframes = 20
				sprite.vframes = 14
				sprite.frame = (8 * 20) + 10
		LootType.BANANA:
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			if items_texture:
				sprite.texture = items_texture
				sprite.hframes = 20
				sprite.vframes = 14
				sprite.frame = (8 * 20) + 11
		LootType.CHERRY:
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			if items_texture:
				sprite.texture = items_texture
				sprite.hframes = 20
				sprite.vframes = 14
				sprite.frame = (8 * 20) + 12
		LootType.KEY:
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			if items_texture:
				sprite.texture = items_texture
				sprite.hframes = 20
				sprite.vframes = 14
				sprite.frame = (13 * 20) + 10
		LootType.ITEM:
			# Item instance - use item's spritePath and spriteFrame
			if item and item.spritePath != "":
				var items_texture = load(item.spritePath)
				if items_texture:
					sprite.texture = items_texture
					sprite.hframes = item.spriteFrames.x if item.spriteFrames.x > 0 else 20
					sprite.vframes = item.spriteFrames.y if item.spriteFrames.y > 0 else 14
					sprite.frame = item.spriteFrame
					ItemDatabase.apply_item_colors_to_sprite(sprite, item)
					print("Loot: Set up item sprite for ", item.item_name, " frame=", sprite.frame)
					
					# Add quantity badge if quantity > 1
					if item.quantity > 1:
						_create_quantity_badge(item.quantity)
				else:
					print("Loot: ERROR - Could not load texture from spritePath: ", item.spritePath)
			else:
				print("Loot: ERROR - Item loot has no item instance or spritePath! item=", item)

func _setup_collision_shape():
	if not collision_shape:
		# Try to find existing collision shape
		collision_shape = get_node_or_null("CollisionShape2D")
		if not collision_shape:
			# Create collision shape if it doesn't exist
			collision_shape = CollisionShape2D.new()
			add_child(collision_shape)
	
	# Get or create circle shape
	var circle_shape: CircleShape2D = null
	if collision_shape.shape and collision_shape.shape is CircleShape2D:
		circle_shape = collision_shape.shape as CircleShape2D
	else:
		circle_shape = CircleShape2D.new()
	
	# Set collision size based on loot type
	if loot_type == LootType.COIN:
		circle_shape.radius = 3.0 # 6x6 pixels (diameter)
	else:
		circle_shape.radius = 7.0 # 14x14 pixels (diameter)
	
	collision_shape.shape = circle_shape

func _duplicate_sprite_material():
	# Duplicate sprite material so it isn't shared between loot instances
	# This prevents material state from being shared (e.g., colorization, tint effects)
	if sprite and sprite.material:
		sprite.material = sprite.material.duplicate()

func _create_quantity_badge(quantity: int):
	# Create a label to show the quantity
	quantity_badge = Label.new()
	quantity_badge.text = str(quantity)
	quantity_badge.add_theme_font_size_override("font_size", 8)
	quantity_badge.add_theme_color_override("font_color", Color.WHITE)
	quantity_badge.add_theme_color_override("font_outline_color", Color.BLACK)
	quantity_badge.add_theme_constant_override("outline_size", 2)
	quantity_badge.z_index = 100 # Above the sprite
	quantity_badge.position = Vector2(6, -8) # Bottom right of sprite
	add_child(quantity_badge)

func _physics_process(delta):
	# Stop all physics processing if collected
	if collected:
		return
	
	var is_client = multiplayer.has_multiplayer_peer() and not is_multiplayer_authority()
	var is_server = not multiplayer.has_multiplayer_peer() or is_multiplayer_authority()
	
	# Server (authority): Run physics normally
	if is_server:
		# Update bounce timer
		if bounce_timer > 0.0:
			bounce_timer -= delta
			if bounce_timer < 0:
				bounce_timer = 0.0
		
		# Update Z-axis physics
		if is_airborne:
			# Apply gravity to Z-axis (matches old code)
			acceleration_z = -300.0 # Gravity
			velocity_z += acceleration_z * delta
			position_z += velocity_z * delta
			
			# Ground collision and bounce (matches old code - simpler, no aggressive damping)
			if position_z <= 0.0:
				position_z = 0.0
				
				# Apply friction ONLY when on ground (matches old code behavior)
				velocity = velocity.lerp(Vector2.ZERO, 1.0 - exp(-friction * delta))
				
				# Check if we should bounce (simpler logic matching old code)
				if not collected and abs(velocity_z) > min_bounce_velocity:
					# Play bounce sound for coins (matches old code volume formula)
					if loot_type == LootType.COIN and bounce_timer == 0.0 and velocity_z < 0.0:
						if sfx_coin_bounce:
							# Old code formula: -1 + (-10 - (velocityZ * 0.1))
							# Adjusted for negative velocity_z
							sfx_coin_bounce.volume_db = -1.0 + (-10.0 - (abs(velocity_z) * 0.1))
							sfx_coin_bounce.play()
							bounce_timer = 0.08 # Matches old code timing
					
					# Simple bounce (matches old code)
					velocity_z = - velocity_z * bounce_restitution
					is_airborne = true # Still bouncing
				else:
					# Velocity too small or collected - stop bouncing
					velocity_z = 0.0
					is_airborne = false
		else:
			is_airborne = false
			# Ensure velocity_z is zero when on ground
			velocity_z = 0.0
			# Apply friction when on ground (matches old code)
			velocity = velocity.lerp(Vector2.ZERO, 1.0 - exp(-friction * delta))
		
		# Move and check for collisions
		move_and_slide()
		
		# Check for wall collisions (skip if collected to prevent bounce sounds)
		# Matches old code behavior - simpler wall bounce without aggressive velocity reduction
		if not collected:
			for i in get_slide_collision_count():
				var collision = get_slide_collision(i)
				if collision:
					var collider = collision.get_collider()
					# Only bounce off walls, not players (players are detected via PickupArea)
					if collider and not collider.is_in_group("player"):
						# Check if velocity is too small before bouncing (prevent infinite micro-bounces)
						var velocity_magnitude = velocity.length()
						if velocity_magnitude < 15.0: # If velocity is very small, stop bouncing
							velocity = Vector2.ZERO
							continue # Skip bounce and sound
						
						# Bounce off walls (matches old code - no aggressive velocity reduction)
						var normal = collision.get_normal()
						velocity = velocity.bounce(normal) # Old code didn't reduce velocity here
						
						# Play bounce sound for coins hitting walls (matches old code)
						if loot_type == LootType.COIN and bounce_timer == 0.0:
							if sfx_coin_bounce:
								sfx_coin_bounce.volume_db = -5.0
								sfx_coin_bounce.play()
								bounce_timer = 0.08 # Matches old code timing
		
		# Update visual position based on Z
		_update_visuals()
		
		# Animate coin rotation (always animate, even when not airborne)
		if loot_type == LootType.COIN:
			_animate_coin(delta)
		
		# Server: Periodically sync position/velocity to clients (sync more frequently when airborne)
		sync_timer += delta
		# Sync more frequently when airborne (bouncing), less when settled
		var current_interval = sync_interval if is_airborne else sync_interval * 2.0
		if sync_timer >= current_interval:
			sync_timer = 0.0
			var game_world = get_tree().get_first_node_in_group("game_world")
			if game_world and game_world.has_method("_sync_loot_physics"):
				var loot_id = get_meta("loot_id") if has_meta("loot_id") else -1
				if loot_id >= 0:
					game_world._rpc_to_ready_peers("_sync_loot_physics", [loot_id, global_position, velocity, position_z, velocity_z])
	
	# Client (prediction): Run physics locally for smooth movement, then reconcile with server
	elif is_client:
		# Run physics locally (client-side prediction) - same logic as server
		# Update bounce timer
		if bounce_timer > 0.0:
			bounce_timer -= delta
			if bounce_timer < 0:
				bounce_timer = 0.0
		
		# Update Z-axis physics
		if is_airborne:
			# Apply gravity to Z-axis (matches server)
			acceleration_z = -300.0
			velocity_z += acceleration_z * delta
			position_z += velocity_z * delta
			
			# Ground collision and bounce (matches server logic)
			if position_z <= 0.0:
				position_z = 0.0
				velocity = velocity.lerp(Vector2.ZERO, 1.0 - exp(-friction * delta))
				
				if not collected and abs(velocity_z) > min_bounce_velocity:
					# Play bounce sound for coins
					if loot_type == LootType.COIN and bounce_timer == 0.0 and velocity_z < 0.0:
						if sfx_coin_bounce:
							sfx_coin_bounce.volume_db = -1.0 + (-10.0 - (abs(velocity_z) * 0.1))
							sfx_coin_bounce.play()
							bounce_timer = 0.08
					velocity_z = - velocity_z * bounce_restitution
					is_airborne = true
				else:
					velocity_z = 0.0
					is_airborne = false
		else:
			is_airborne = false
			velocity_z = 0.0
			velocity = velocity.lerp(Vector2.ZERO, 1.0 - exp(-friction * delta))
		
		# Move and check for collisions
		move_and_slide()
		
		# Check for wall collisions
		if not collected:
			for i in get_slide_collision_count():
				var collision = get_slide_collision(i)
				if collision:
					var collider = collision.get_collider()
					if collider and not collider.is_in_group("player"):
						var velocity_magnitude = velocity.length()
						if velocity_magnitude < 15.0:
							velocity = Vector2.ZERO
							continue
						var normal = collision.get_normal()
						velocity = velocity.bounce(normal)
						if loot_type == LootType.COIN and bounce_timer == 0.0:
							if sfx_coin_bounce:
								sfx_coin_bounce.volume_db = -5.0
								sfx_coin_bounce.play()
								bounce_timer = 0.08
		
		# Update visuals
		_update_visuals()
		
		# Animate coin rotation
		if loot_type == LootType.COIN:
			_animate_coin(delta)
		
		# Reconcile with server state if available (called from game_world._sync_loot_physics)
		# Server state is stored in server_position, server_velocity, etc. variables
		# Reconciliation happens in game_world._reconcile_loot_state()

func _update_z_physics(delta):
	position_z += velocity_z * delta

func _update_visuals():
	# Update sprite position based on Z
	if sprite:
		sprite.position.y = - position_z * 0.5
	
	# Update shadow scale and opacity based on Z
	if shadow:
		var shadow_scale = 1.0 - (position_z / 50.0) * 0.5
		shadow.scale = Vector2.ONE * max(0.3, shadow_scale)
		shadow.modulate.a = 0.5 - (position_z / 50.0) * 0.2

var coin_anim_time: float = 0.0
func _animate_coin(delta):
	# Rotate coin animation
	if sprite and loot_type == LootType.COIN:
		# Make sure sprite is set up correctly (in case _setup_sprite hasn't run yet)
		if sprite.hframes < 6 or sprite.texture == null:
			_setup_sprite()
		# Animate coin rotation
		coin_anim_time += delta * 10.0 # Animation speed
		var frame = int(coin_anim_time) % 6
		sprite.frame = frame

func _on_pickup_area_body_entered(body):
	if body and body.is_in_group("player") and not body.is_dead:
		# Check if this item was dropped by this player recently (5 second cooldown)
		if has_meta("dropped_by_peer_id") and has_meta("drop_time"):
			var dropped_by_peer_id = get_meta("dropped_by_peer_id")
			var drop_time = get_meta("drop_time")
			var current_time = Time.get_ticks_msec()
			var time_since_drop = (current_time - drop_time) / 1000.0 # Convert to seconds
			
			# Check if this player dropped the item and cooldown hasn't expired
			if body.has_method("get_multiplayer_authority"):
				var player_peer_id = body.get_multiplayer_authority()
				if player_peer_id == dropped_by_peer_id and time_since_drop < 5.0:
					# Player can't pick up their own dropped item for 5 seconds
					print("Loot: Player ", body.name, " cannot pick up item they dropped (", time_since_drop, "s ago, need 5s cooldown)")
					return
		
		print("Loot: Pickup area entered by player: ", body.name, " is_local: ", body.is_local_player if "is_local_player" in body else "unknown", " is_server: ", multiplayer.is_server())
		_pickup(body)

func _pickup(player: Node):
	# Prevent multiple pickups
	if collected:
		print("Loot: Already collected, ignoring pickup")
		return
	
	var player_auth_str = "N/A"
	if "get_multiplayer_authority" in player:
		player_auth_str = str(player.get_multiplayer_authority())
	print("Loot: _pickup called by player: ", player.name, " is_server: ", multiplayer.is_server(), " has_peer: ", multiplayer.has_multiplayer_peer(), " player_authority: ", player_auth_str)
	
	# In multiplayer, only process on server or if player has authority
	# If client player picks it up, send RPC to server
	if multiplayer.has_multiplayer_peer():
		if not multiplayer.is_server():
			# Client: send pickup request to server
			if player.is_multiplayer_authority():
				# This is the local player, send request to server
				var player_peer_id = player.get_multiplayer_authority()
				print("Loot: Client sending pickup request to server for player peer_id: ", player_peer_id)
				# Route through game_world to avoid node path issues
				var game_world = get_tree().get_first_node_in_group("game_world")
				var loot_id = get_meta("loot_id") if has_meta("loot_id") else -1
				if game_world and game_world.has_method("_request_loot_pickup"):
					game_world._request_loot_pickup.rpc_id(1, loot_id, global_position, player_peer_id)
				else:
					# Fallback: try direct RPC
					rpc_id(1, "_request_pickup", player_peer_id)
			else:
				print("Loot: Client player does not have authority, cannot pickup")
			return
		else:
			# Server: If player doesn't have authority, this is a client player
			# The client will send _request_pickup RPC, so we should ignore this Area2D signal
			# to avoid duplicate processing and errors
			if not player.is_multiplayer_authority():
				# Client player - they will send RPC, so ignore this signal completely
				# This prevents race conditions where server's Area2D fires before client's RPC arrives
				# Don't log as error since this is expected behavior
				return
	
	# Process the pickup (common code for both server and single-player)
	_process_pickup_on_server(player)

func _process_pickup_on_server(player: Node):
	# Internal function to process pickup on server (called from _request_pickup RPC)
	# This skips the authority check since we've already validated the request
	# Mark as collected immediately to prevent duplicate pickups
	# (Note: This may already be set by _request_pickup, but set it here too for safety)
	if not collected:
		collected = true
	
	# Disable pickup area immediately to prevent further collisions
	# Use set_deferred() because we're in a signal callback
	if pickup_area:
		pickup_area.set_deferred("monitoring", false)
		pickup_area.set_deferred("monitorable", false)
	
	# Sync removal to all clients FIRST (before processing pickup)
	# This ensures clients remove the loot even if host processes it
	# Use game_world to route removal sync instead of direct RPC to avoid node path issues
	if multiplayer.has_multiplayer_peer() and is_inside_tree():
		var loot_id = get_meta("loot_id") if has_meta("loot_id") else -1
		var game_world = get_tree().get_first_node_in_group("game_world")
		if game_world and game_world.has_method("_sync_loot_remove"):
			print("Loot: Server syncing removal of loot id=", loot_id, " at ", global_position)
			game_world._rpc_to_ready_peers("_sync_loot_remove", [loot_id, global_position])
		else:
			# If GameWorld isn't ready, skip removal sync to avoid node path RPC errors
			print("Loot: GameWorld not ready, skipping removal sync for loot id=", loot_id)
	
	match loot_type:
		LootType.COIN:
			if sfx_coin_collect:
				sfx_coin_collect.play()
			# Give player coin
			if player.has_method("add_coins"):
				player.add_coins(coin_value)
			# Show floating text with item graphic and text
			var coin_texture = load("res://assets/gfx/pickups/gold_coin.png")
			_show_floating_text(player, "+" + str(coin_value) + " COIN", Color(1.0, 0.84, 0.0), 0.5, 0.5, coin_texture, 6, 1, 0)
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+" + str(coin_value) + " COIN", Color(1.0, 0.84, 0.0), 0, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_coin_collect and sfx_coin_collect.playing:
				await sfx_coin_collect.finished
			queue_free()
		LootType.APPLE:
			if sfx_potion_collect:
				sfx_potion_collect.play()
			
			# Create Item instance and add to inventory instead of directly healing
			var apple_item = ItemDatabase.create_item("apple")
			if apple_item and player.character_stats:
				var was_encumbered = player.character_stats.is_over_encumbered()
				player.character_stats.add_item(apple_item)
				if not was_encumbered and player.character_stats.is_over_encumbered():
					if player.has_method("show_floating_status"):
						player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2))
				
				# Sync inventory+equipment to joiner (server added to their copy; joiner's client must apply)
				if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
					var owner_id = player.get_multiplayer_authority()
					if owner_id != 1 and owner_id != multiplayer.get_unique_id():
						var inv_data: Array = []
						for inv_item in player.character_stats.inventory:
							inv_data.append(inv_item.save() if inv_item else null)
						var equip_data: Dictionary = {}
						for slot_name in player.character_stats.equipment.keys():
							var eq = player.character_stats.equipment[slot_name]
							equip_data[slot_name] = eq.save() if eq else null
						if player.has_method("_apply_inventory_and_equipment_from_server"):
							player._apply_inventory_and_equipment_from_server.rpc_id(owner_id, inv_data, equip_data)
			
			# Show floating text with item name (uppercase)
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			var display_text = "APPLE"
			var text_color = Color.GREEN
			_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 10)
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, (8 * 20) + 10, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_potion_collect and sfx_potion_collect.playing:
				await sfx_potion_collect.finished
			queue_free()
		LootType.BANANA:
			if sfx_banana_collect:
				sfx_banana_collect.play()
			
			# Create Item instance and add to inventory instead of directly healing
			var banana_item = ItemDatabase.create_item("banana")
			if banana_item and player.character_stats:
				var was_encumbered = player.character_stats.is_over_encumbered()
				player.character_stats.add_item(banana_item)
				if not was_encumbered and player.character_stats.is_over_encumbered():
					if player.has_method("show_floating_status"):
						player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2))
				
				# Sync inventory+equipment to joiner (server added to their copy; joiner's client must apply)
				if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
					var owner_id = player.get_multiplayer_authority()
					if owner_id != 1 and owner_id != multiplayer.get_unique_id():
						var inv_data: Array = []
						for inv_item in player.character_stats.inventory:
							inv_data.append(inv_item.save() if inv_item else null)
						var equip_data: Dictionary = {}
						for slot_name in player.character_stats.equipment.keys():
							var eq = player.character_stats.equipment[slot_name]
							equip_data[slot_name] = eq.save() if eq else null
						if player.has_method("_apply_inventory_and_equipment_from_server"):
							player._apply_inventory_and_equipment_from_server.rpc_id(owner_id, inv_data, equip_data)
			
			# Show floating text with item name (uppercase)
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			var display_text = "BANANA"
			var text_color = Color.GREEN
			_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 11)
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, (8 * 20) + 11, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_banana_collect and sfx_banana_collect.playing:
				await sfx_banana_collect.finished
			queue_free()
		LootType.CHERRY:
			if sfx_banana_collect:
				sfx_banana_collect.play()
			
			# Create Item instance and add to inventory instead of directly healing
			var cherry_item = ItemDatabase.create_item("cherry")
			if cherry_item and player.character_stats:
				var was_encumbered = player.character_stats.is_over_encumbered()
				player.character_stats.add_item(cherry_item)
				if not was_encumbered and player.character_stats.is_over_encumbered():
					if player.has_method("show_floating_status"):
						player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2))
				
				# Sync inventory+equipment to joiner (server added to their copy; joiner's client must apply)
				if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
					var owner_id = player.get_multiplayer_authority()
					if owner_id != 1 and owner_id != multiplayer.get_unique_id():
						var inv_data: Array = []
						for inv_item in player.character_stats.inventory:
							inv_data.append(inv_item.save() if inv_item else null)
						var equip_data: Dictionary = {}
						for slot_name in player.character_stats.equipment.keys():
							var eq = player.character_stats.equipment[slot_name]
							equip_data[slot_name] = eq.save() if eq else null
						if player.has_method("_apply_inventory_and_equipment_from_server"):
							player._apply_inventory_and_equipment_from_server.rpc_id(owner_id, inv_data, equip_data)
			
			# Show floating text with item name (uppercase)
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			var display_text = "CHERRY"
			var text_color = Color.GREEN
			_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, 20, 14, (8 * 20) + 12)
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, (8 * 20) + 12, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_banana_collect and sfx_banana_collect.playing:
				await sfx_banana_collect.finished
			queue_free()
		LootType.KEY:
			if sfx_key_collect:
				sfx_key_collect.play()
			# Give player key in inventory
			if player.has_method("add_key"):
				player.add_key(1)
			# Show floating text with item graphic and text
			var items_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			_show_floating_text(player, "+1 KEY", Color.YELLOW, 0.5, 0.5, items_texture, 20, 14, (13 * 20) + 10)
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, "+1 KEY", Color.YELLOW, (13 * 20) + 10, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_key_collect and sfx_key_collect.playing:
				await $SfxKeyCollect.finished
			queue_free()
		LootType.ITEM:
			# Item instance pickup
			if not item:
				print("Loot: ERROR - Item loot has no item instance!")
				queue_free()
				return
			
			if sfx_loot_collect:
				sfx_loot_collect.play()
			
			# Handle item pickup based on type
			var was_encumbered = player.character_stats.is_over_encumbered() if player.character_stats else false
			if item.item_type == Item.ItemType.Equippable:
				# Equippable item - add to inventory
				if player.character_stats:
					player.character_stats.add_item(item)
					print(name, " picked up item: ", item.item_name, " (added to inventory)")
			elif item.item_type == Item.ItemType.Restoration:
				# Consumable item - add to inventory (use with F key in inventory)
				if player.character_stats:
					player.character_stats.add_item(item)
					print(name, " picked up item: ", item.item_name, " (added to inventory)")
			if player.character_stats and not was_encumbered and player.character_stats.is_over_encumbered():
				if player.has_method("show_floating_status"):
					player.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2))
			
			# Sync inventory+equipment to joiner (server added to their copy; joiner's client must apply)
			if multiplayer.has_multiplayer_peer() and multiplayer.is_server():
				var owner_id = player.get_multiplayer_authority()
				if owner_id != 1 and owner_id != multiplayer.get_unique_id():
					var inv_data: Array = []
					for inv_item in player.character_stats.inventory:
						inv_data.append(inv_item.save() if inv_item else null)
					var equip_data: Dictionary = {}
					for slot_name in player.character_stats.equipment.keys():
						var eq = player.character_stats.equipment[slot_name]
						equip_data[slot_name] = eq.save() if eq else null
					if player.has_method("_apply_inventory_and_equipment_from_server"):
						player._apply_inventory_and_equipment_from_server.rpc_id(owner_id, inv_data, equip_data)
			
			# Show floating text with item name (uppercase)
			var items_texture = load(item.spritePath)
			var display_text = item.item_name.to_upper() # Always uppercase
			var text_color = Color.WHITE
			
			# Color code based on item type
			if item.item_type == Item.ItemType.Equippable:
				text_color = Color.CYAN # Cyan for equipment
			elif item.item_type == Item.ItemType.Restoration:
				text_color = Color.GREEN # Green for consumables
			
			_show_floating_text(player, display_text, text_color, 0.5, 0.5, items_texture, item.spriteFrames.x, item.spriteFrames.y, item.spriteFrame, item)
			
			# Sync floating text to client via GameWorld to avoid loot node path RPCs
			if multiplayer.has_multiplayer_peer() and player.get_multiplayer_authority() != 1:
				var game_world = get_tree().get_first_node_in_group("game_world")
				if game_world and game_world.has_method("_sync_loot_floating_text"):
					game_world._sync_loot_floating_text.rpc_id(player.get_multiplayer_authority(), loot_type, display_text, text_color, item.spriteFrame, player.get_multiplayer_authority())
			
			self.visible = false
			
			# Wait for sound to finish before removing
			if sfx_loot_collect and sfx_loot_collect.playing:
				await sfx_loot_collect.finished
			queue_free()

var processing_pickup: bool = false # Mutex to prevent concurrent pickup processing

@rpc("any_peer", "reliable")
func _request_pickup(player_peer_id: int):
	# Server receives pickup request from client
	print("Loot: _request_pickup RPC received on server for player_peer_id: ", player_peer_id, " is_server: ", multiplayer.is_server())
	
	if not multiplayer.is_server():
		print("Loot: _request_pickup called on non-server, ignoring")
		return
	
	# Check cooldown: prevent player from picking up their own dropped item for 5 seconds
	if has_meta("dropped_by_peer_id") and has_meta("drop_time"):
		var dropped_by_peer_id = get_meta("dropped_by_peer_id")
		var drop_time = get_meta("drop_time")
		var current_time = Time.get_ticks_msec()
		var time_since_drop = (current_time - drop_time) / 1000.0 # Convert to seconds
		
		if player_peer_id == dropped_by_peer_id and time_since_drop < 5.0:
			# Player can't pick up their own dropped item for 5 seconds
			print("Loot: Player ", player_peer_id, " cannot pick up item they dropped (", time_since_drop, "s ago, need 5s cooldown)")
			return
	
	# Use mutex to prevent concurrent processing (race condition protection)
	if processing_pickup:
		print("Loot: Pickup already being processed, ignoring duplicate request")
		return
	
	# Check if already collected (this prevents race conditions with Area2D signals)
	if collected:
		print("Loot: Already collected (collected=", collected, "), ignoring pickup request")
		return
	
	# Set mutex and mark as collected IMMEDIATELY to prevent any race conditions
	processing_pickup = true
	collected = true
	print("Loot: Marked as collected=true and set processing_pickup=true at start of _request_pickup")
	
	# Find the player by peer ID
	var player = null
	var players = get_tree().get_nodes_in_group("player")
	print("Loot: Searching for player with peer_id ", player_peer_id, " among ", players.size(), " players")
	for p in players:
		var p_authority = p.get_multiplayer_authority() if "get_multiplayer_authority" in p else -1
		print("Loot: Checking player ", p.name, " authority: ", p_authority)
		if p_authority == player_peer_id:
			player = p
			break
	
	if not player:
		print("Loot: ERROR - Could not find player with peer_id ", player_peer_id, " for pickup")
		# Clear mutex and reset collected if player not found (allow retry)
		processing_pickup = false
		collected = false
		return
	
	print("Loot: Found player ", player.name, " processing pickup on server")
	# Process pickup on server directly (skip authority check since we've already validated the request came from the right client)
	# Don't call _pickup() because it will check authority and reject client players
	_process_pickup_on_server(player)
	
	# Clear mutex after processing completes
	processing_pickup = false

# This function can be called directly (not just via RPC) when game_world routes the update
func _sync_remove():
	# Clients remove loot when any player picks it up
	# Only process if we're not the authority (i.e., we're a client)
	if multiplayer.is_server():
		return # Server ignores its own updates
	
	print("Loot: Client received removal sync for loot at ", global_position)
	
	# Mark as collected immediately to stop physics and sounds
	collected = true
	
	# Play pickup sound on client so they can hear it
	match loot_type:
		LootType.COIN:
			if sfx_coin_collect:
				sfx_coin_collect.play()
		LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM:
			if sfx_loot_collect:
				sfx_loot_collect.play()
	
	# Disable pickup area to prevent further collisions
	if pickup_area:
		pickup_area.monitoring = false
		pickup_area.monitorable = false
	
	# Hide immediately
	visible = false
	
	# Wait for sound to finish before removing (if sound is playing)
	var _sound_playing = false
	if loot_type == LootType.COIN and sfx_coin_collect and sfx_coin_collect.playing:
		_sound_playing = true
		await sfx_coin_collect.finished
	elif loot_type in [LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.ITEM] and sfx_loot_collect and sfx_loot_collect.playing:
		_sound_playing = true
		await sfx_loot_collect.finished
	
	# Remove from scene
	if not is_queued_for_deletion():
		queue_free()

@rpc("any_peer", "reliable")
func _sync_show_floating_text(loot_type_value: int, text: String, color_value: Color, _value: int, sprite_frame_value: int, player_peer_id: int):
	# Client receives floating text sync from server
	if multiplayer.is_server():
		return # Server ignores this (it's the sender)
	
	# Find player by peer ID
	var player = null
	var players = get_tree().get_nodes_in_group("player")
	for p in players:
		if p.get_multiplayer_authority() == player_peer_id:
			player = p
			break
	
	if not player or not is_instance_valid(player):
		return # Can't find player
	
	# Determine texture and parameters based on loot type
	var item_texture: Texture2D = null
	var sprite_hframes: int = 1
	var sprite_vframes: int = 1
	
	match loot_type_value:
		LootType.COIN:
			item_texture = load("res://assets/gfx/pickups/gold_coin.png")
			sprite_hframes = 6
			sprite_vframes = 1
		LootType.APPLE, LootType.BANANA, LootType.CHERRY, LootType.KEY:
			item_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			sprite_hframes = 20
			sprite_vframes = 14
		LootType.ITEM:
			# Item instance - use item's sprite path
			# Note: item data is not available on client in this sync, so we use default
			# The actual item sprite is set when the loot is created
			item_texture = load("res://assets/gfx/pickups/items_n_shit.png")
			sprite_hframes = 20
			sprite_vframes = 14
	
	# Show floating text on client
	_show_floating_text(player, text, color_value, 0.5, 0.5, item_texture, sprite_hframes, sprite_vframes, sprite_frame_value)

func _show_floating_text(player: Node, text: String, color: Color, show_time: float = 0.5, fade_time: float = 0.5, item_texture: Texture2D = null, sprite_hframes: int = 1, sprite_vframes: int = 1, sprite_frame: int = 0, item_param = null):
	# Create floating text and item graphic above player's head
	# Shows for show_time seconds, then fades out over fade_time seconds
	var floating_text_scene = preload("res://scenes/floating_text.tscn")
	if floating_text_scene and player and is_instance_valid(player):
		var floating_text = floating_text_scene.instantiate()
		var parent = player.get_parent()
		if parent:
			parent.add_child(floating_text)
			floating_text.global_position = Vector2(player.global_position.x, player.global_position.y - 20)
			floating_text.setup(text, color, show_time, fade_time, item_texture, sprite_hframes, sprite_vframes, sprite_frame, item_param)
