extends CharacterBody2D

var speed = 300
var direction = Vector2.ZERO
var stick_duration = 3.0 # How long the arrow stays stuck to players
var stick_duration_enemy = 1.2 # Shorter when stuck to enemy - fade out faster
var wall_stick_duration = 30.0 # Much longer duration for wall-stuck arrows (30 seconds)
var is_stuck = false
var is_collected = false
var stick_timer = 0.0
var stuck_to_wall = false # Track if stuck to wall (vs enemy/player)
var stuck_to_enemy = false # Track if stuck to enemy (use shorter duration)
var can_be_collected = false # True after delay on wall
var shooter_can_collect = false # Shooter can collect after 0.2 seconds
var shooter_collection_delay = 0.2 # Fast pickup for shooter
var others_collection_delay = 5.0 # Other players wait 5 seconds

# Flight duration based on charge
var flight_timer = 0.0
var max_flight_duration = 6.0 # How long arrow flies before landing (set by charge)
var can_deal_damage = true # False after arrow "lands" in flight

var initiated_by: Node2D = null
var player_owner: Node = null # Like sword_projectile
var hit_targets = {} # Track what we've already hit (Dictionary for O(1) lookup)

# Collection area for wall-stuck arrows
var collection_area: Area2D = null

@onready var arrow_area = $ArrowArea # Assuming you have an Area2D node named ArrowArea
@onready var shadow = $Shadow # Assuming you have a Shadow node under the CharacterBody2D

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	arrow_area.set_deferred("monitoring", true)
	# Connect area signals (only if not already connected)
	if arrow_area and not arrow_area.body_entered.is_connected(_on_arrow_area_body_entered):
		arrow_area.body_entered.connect(_on_arrow_area_body_entered)
	$SfxArrowFire.play()
	call_deferred("_initialize_arrow")

func _initialize_arrow() -> void:
	var angle = direction.angle()
	self.rotation = angle - PI / 2 # Adjust for sprite orientation
	# Set initial rotation based on direction
	velocity = direction * speed # Set initial velocity to move the arrow
	
	# Calculate the offset for the shadow position, which should be below the arrow
	var shadow_offset = Vector2(0, 4) # Adjust the 16 to how far you want the shadow from the arrow (this is just an example)
	
	# Apply the rotation of the arrow to the shadow offset
	shadow.position += shadow_offset.rotated(-self.rotation)
	if abs(direction.x) == 1:
		shadow.scale.x = 0.26
		shadow.scale.y = 0.062
		
	elif abs(direction.x) > 0:
		shadow.scale.x = 0.18
		shadow.scale.y = 0.08
	else:
		shadow.scale.x = 0.1
		shadow.scale.y = 0.1
	
	# Calculate the shadow's scale based on the velocity or direction of the arrow
	#var velocity_magnitude = velocity.length()

	# Scale more in the horizontal direction if moving diagonally or horizontally
	#var scale_factor = 0.28 + abs(velocity.x) / velocity_magnitude  # Adjust the factor to your preference

	# Apply the scaling to the shadow
	shadow.rotation = - (angle - PI / 2)

func shoot(shoot_direction: Vector2, start_pos: Vector2, owner_player: Node = null, charge_percentage: float = 1.0) -> void:
	direction = shoot_direction.normalized()
	global_position = start_pos
	player_owner = owner_player
	initiated_by = owner_player
	
	# Apply charge percentage to arrow speed and flight duration
	# Speed: min 120, max 320, scales with charge % (0.5 to 1.0)
	var min_speed = 120.0
	var max_speed = 320.0
	speed = lerp(min_speed, max_speed, (charge_percentage - 0.5) * 2.0) # Map 0.5-1.0 to 0.0-1.0 range
	
	# Flight duration: 50% charge = 0.5s, 100% charge = 2.5s
	max_flight_duration = lerp(0.5, 2.5, (charge_percentage - 0.5) * 2.0)
	
	print("Arrow shot at ", charge_percentage * 100, "% charge (speed: ", speed, ", flight duration: ", max_flight_duration, "s)")

# Called every frame. 'delta' is the e	lapsed time since the previous frame.
func _process(delta: float) -> void:
	if not is_stuck:
		# Track flight time and "land" arrow after max_flight_duration
		flight_timer += delta
		if flight_timer >= max_flight_duration:
			# Defer landing so any body_entered from this frame's physics runs first.
			# Otherwise we can set can_deal_damage=false before the overlap is processed.
			call_deferred("_land_arrow_from_flight")
			return # Exit early to prevent further movement this frame
		
		# Continue flying
		velocity = direction * speed
	
	if is_stuck:
		# Handle fade out here if it's stuck
		stick_timer += delta
		
		# Enable collection for wall-stuck arrows with different delays
		if stuck_to_wall:
			# Shooter can collect after 0.2 seconds
			if stick_timer >= shooter_collection_delay and not shooter_can_collect:
				shooter_can_collect = true
				_enable_collection_area()
			# Everyone else can collect after 5 seconds
			if stick_timer >= others_collection_delay and not can_be_collected:
				can_be_collected = true
		
		# Use appropriate duration based on what it's stuck to (enemy = faster fade)
		var duration: float
		if stuck_to_wall:
			duration = wall_stick_duration
		elif stuck_to_enemy:
			duration = stick_duration_enemy
		else:
			duration = stick_duration
		
		if stick_timer >= duration:
			# Start fading out after it sticks
			modulate.a = max(0, 1 - (stick_timer - duration) / 1.0) # Fade out over 1 second
			if stick_timer >= duration + 1.0: # Extra second for fade out
				queue_free() # Remove the arrow after fade out
	
	move_and_slide()

func _physics_process(_delta: float) -> void:
	# If the arrow is stuck, stop it from moving
	if is_stuck:
		velocity = Vector2.ZERO # Stop movement
		# Optional: disable further physics interaction by setting linear_velocity
		# move_and_slide(Vector2.ZERO)  # You can also use this to stop the character
		
func play_impact():
	$SfxImpactSound.play()

# Called when the arrow hits a wall or another object (like sword_projectile)
func _on_arrow_area_body_entered(body: Node2D) -> void:
	if is_stuck:
		return
	
	# Arrow has landed (flew too long) - can't deal damage anymore
	if not can_deal_damage:
		# Check if it's a wall/terrain (not player/enemy) - stick to it
		if not body.is_in_group("player") and not body.is_in_group("enemy"):
			$SfxImpactWall.play()
			_stick_to_wall()
		return
	
	# Don't hit the owner
	if body == player_owner or body == initiated_by:
		return
	
	# Don't hit the same target twice
	if body in hit_targets:
		return
	
	# Ignore other players - arrow passes through (no friendly fire)
	if body.is_in_group("player") and body.has_method("rpc_take_damage"):
		hit_targets[body] = true
		return
	
	# Deal damage to enemies
	if body.is_in_group("enemy") and body.has_method("rpc_take_damage"):
		# CRITICAL: Only the authority should process enemy collisions
		# This ensures hit/miss/dodge calculations happen once and are consistent
		if player_owner and not player_owner.is_multiplayer_authority():
			return # Non-authority ignores enemy collisions
		
		var attacker_pos = player_owner.global_position if player_owner else global_position
		var damage = 20.0 # TODO: Get actual damage from player
		if player_owner and player_owner.character_stats:
			damage = player_owner.character_stats.damage
		
		# Check hit chance (based on player's DEX stat)
		var hit_roll = randf()
		var hit_chance = 0.95
		if player_owner and player_owner.character_stats:
			hit_chance = player_owner.character_stats.hit_chance
		var is_miss = hit_roll >= hit_chance
		
		if is_miss:
			# MISS - arrow passes through enemy and continues flying!
			if body.has_method("_show_damage_number"):
				body._show_damage_number(0.0, attacker_pos, false, true, false) # is_miss = true
			hit_targets[body] = true
			# Sync miss to all clients - arrow continues flying
			if is_inside_tree() and is_instance_valid(self) and is_instance_valid(body) and body.is_inside_tree():
				_sync_arrow_miss_via_gameworld(name, body.name)
			return
		
		# Check enemy dodge chance (based on enemy's DEX stat)
		var dodge_roll = randf()
		var dodge_chance = 0.0
		if body.character_stats:
			dodge_chance = body.character_stats.dodge_chance
		var is_dodge = dodge_roll < dodge_chance
		
		if is_dodge:
			# DODGE - arrow passes through enemy and continues flying!
			if body.has_method("_show_damage_number"):
				body._show_damage_number(0.0, attacker_pos, false, false, true) # is_dodged = true
			hit_targets[body] = true
			# Sync dodge to all clients - arrow continues flying
			if is_inside_tree() and is_instance_valid(self) and is_instance_valid(body) and body.is_inside_tree():
				_sync_arrow_dodge_via_gameworld(name, body.name)
			print(body.name, " DODGED arrow! Arrow continues flying...")
			return
		
		# HIT - add to hit_targets and stick to enemy
		hit_targets[body] = true
		play_impact()
		
		# Deal damage
		var enemy_peer_id = body.get_multiplayer_authority()
		if enemy_peer_id != 0:
			if multiplayer.is_server() and enemy_peer_id == multiplayer.get_unique_id():
				body.rpc_take_damage(damage, attacker_pos, false)
			else:
				body.rpc_take_damage.rpc_id(enemy_peer_id, damage, attacker_pos, false)
		else:
			body.rpc_take_damage.rpc(damage, attacker_pos, false)
		
		# Sync hit to all clients - arrow sticks
		# CRITICAL: Route through game_world to avoid node path issues
		if is_inside_tree() and is_instance_valid(self) and is_instance_valid(body) and body.is_inside_tree():
			var arrow_name = name
			_sync_arrow_hit_via_gameworld(arrow_name, body.name)
		
		# Arrow hit effect (sync so all clients see it)
		var hit_pos = body.global_position
		var gw = get_tree().get_first_node_in_group("game_world")
		if gw and gw.has_method("_sync_arrow_hit_effect"):
			if multiplayer.is_server():
				gw._sync_arrow_hit_effect(hit_pos.x, hit_pos.y)
				if gw.has_method("_rpc_to_ready_peers"):
					gw._rpc_to_ready_peers("_sync_arrow_hit_effect", [hit_pos.x, hit_pos.y])
			else:
				gw._request_arrow_hit_effect.rpc_id(1, hit_pos.x, hit_pos.y)
		
		_stick_to_target(body, true)
		return
	
	# Hit wall or other object
	$SfxImpactWall.play()
	_stick_to_wall()

func _stick_to_target(target: Node2D, to_enemy: bool = false):
	# Stop the arrow
	velocity = Vector2.ZERO
	is_stuck = true
	stuck_to_enemy = to_enemy
	stick_timer = 0.0
	arrow_area.set_deferred("monitoring", false)
	
	# Calculate the collision point - move arrow slightly back from its direction
	var collision_normal = - direction
	var offset_distance = 8
	var stick_position = global_position + (collision_normal * offset_distance)
	
	# Make arrow a child of the target to stick to it
	var global_rot = global_rotation
	get_parent().call_deferred("remove_child", self)
	target.call_deferred("add_child", self)
	self.set_deferred("global_position", stick_position)
	self.set_deferred("global_rotation", global_rot)

func _land_arrow_from_flight():
	# Called deferred when flight_timer expired - ensures body_entered from this frame ran first
	if is_stuck:
		return
	can_deal_damage = false
	$SfxLandsOnGround.play()
	_stick_to_wall()
	print("Arrow landed after flying for ", flight_timer, " seconds")

func _stick_to_wall():
	# Stop the arrow
	velocity = Vector2.ZERO
	is_stuck = true
	stuck_to_wall = true
	stick_timer = 0.0
	arrow_area.set_deferred("monitoring", false)

func _sync_arrow_collected_via_gameworld(arrow_name: String):
	# Route sync through game_world (RPC on arrow caused "node not found" on host).
	# Collector already added to inventory and will queue_free locally.
	if arrow_name.is_empty():
		return
	var gw = get_tree().get_first_node_in_group("game_world")
	if gw and gw.has_method("_sync_arrow_collected") and multiplayer.has_multiplayer_peer():
		gw._sync_arrow_collected.rpc(arrow_name)

func _sync_arrow_hit_via_gameworld(arrow_name: String, target_name: String):
	# Route arrow hit sync through game_world to avoid node path issues
	if arrow_name.is_empty() or target_name.is_empty():
		return
	var gw = get_tree().get_first_node_in_group("game_world")
	if gw and gw.has_method("_sync_arrow_hit") and multiplayer.has_multiplayer_peer():
		gw._sync_arrow_hit.rpc(arrow_name, target_name)

func _sync_arrow_miss_via_gameworld(arrow_name: String, target_name: String):
	# Route arrow miss sync through game_world to avoid node path issues
	if arrow_name.is_empty() or target_name.is_empty():
		return
	var gw = get_tree().get_first_node_in_group("game_world")
	if gw and gw.has_method("_sync_arrow_miss") and multiplayer.has_multiplayer_peer():
		gw._sync_arrow_miss.rpc(arrow_name, target_name)

func _sync_arrow_dodge_via_gameworld(arrow_name: String, target_name: String):
	# Route arrow dodge sync through game_world to avoid node path issues
	if arrow_name.is_empty() or target_name.is_empty():
		return
	var gw = get_tree().get_first_node_in_group("game_world")
	if gw and gw.has_method("_sync_arrow_dodge") and multiplayer.has_multiplayer_peer():
		gw._sync_arrow_dodge.rpc(arrow_name, target_name)

# Helper method for game_world to process arrow hit sync
func _process_arrow_hit_sync(target: Node):
	# Process arrow hit sync from game_world
	if not is_instance_valid(self) or not is_inside_tree():
		return
	
	if target not in hit_targets:
		hit_targets[target] = true
		play_impact()
		var is_enemy = target.is_in_group("enemy") if target else false
		_stick_to_target(target, is_enemy)
		print("Arrow synced as HIT to: ", target.name)

func _enable_collection_area():
	# Create an Area2D for collecting the arrow
	if collection_area:
		return # Already created
	
	collection_area = Area2D.new()
	collection_area.name = "CollectionArea"
	collection_area.collision_layer = 0
	collection_area.collision_mask = 1 # Detect players (layer 1)
	
	var collision_shape = CollisionShape2D.new()
	var circle = CircleShape2D.new()
	circle.radius = 16.0 # Collection radius
	collision_shape.shape = circle
	collection_area.add_child(collision_shape)
	
	add_child(collection_area)
	collection_area.body_entered.connect(_on_collection_area_body_entered)

func _on_collection_area_body_entered(body: Node2D):
	if not is_instance_valid(self):
		return
	
	# Check if it's a player
	if not body.is_in_group("player"):
		return
	if is_collected:
		return
	
	# Only local players can collect
	if not body.is_local_player:
		return
	
	# Check if this player is allowed to collect
	var is_shooter = (body == player_owner or body == initiated_by)
	if is_shooter:
		# Shooter can collect after short delay
		if not shooter_can_collect:
			return
	else:
		# Other players need to wait longer
		if not can_be_collected:
			return
	
	# Create arrow item and add to player
	if not body.character_stats or not is_instance_valid(body.character_stats):
		print("ERROR: body.character_stats is invalid when trying to collect arrow")
		return
	
	# Capture stable arrow name for sync (route through game_world to avoid RPC path issues)
	var arrow_name = name
	
	# Check if player has arrows equipped in offhand
	var offhand_item = body.character_stats.equipment.get("offhand", null)
	if offhand_item and is_instance_valid(offhand_item) and offhand_item.item_name == "Arrow":
		is_collected = true
		$SfxPickup.play()
		# Add directly to equipped arrows
		offhand_item.quantity += 1
		body.character_stats.character_changed.emit(body.character_stats)
		print(body.name, " collected arrow from wall! Total arrows: ", offhand_item.quantity)
		_sync_arrow_collected_via_gameworld(arrow_name)
		await $SfxPickup.finished
		queue_free()
		return
	
	# Check if offhand is empty and player has a bow equipped
	var mainhand_item = body.character_stats.equipment.get("mainhand", null)
	var has_bow = mainhand_item and is_instance_valid(mainhand_item) and mainhand_item.weapon_type == Item.WeaponType.BOW
	
	if not offhand_item and has_bow:
		# Re-equip arrow to offhand if player has a bow and offhand is empty
		var new_arrow = ItemDatabase.create_item("arrow")
		if new_arrow and is_instance_valid(new_arrow):
			is_collected = true
			$SfxPickup.play()
			new_arrow.quantity = 1
			body.character_stats.equipment["offhand"] = new_arrow
			body.character_stats.character_changed.emit(body.character_stats)
			print(body.name, " collected arrow and re-equipped to offhand!")
			_sync_arrow_collected_via_gameworld(arrow_name)
			await $SfxPickup.finished
			queue_free()
			return
	
	# Add to inventory (will stack if arrows already in inventory)
	var inventory_arrow = ItemDatabase.create_item("arrow")
	if not inventory_arrow or not is_instance_valid(inventory_arrow):
		print("ERROR: Failed to create arrow item for inventory")
		return
	
	inventory_arrow.quantity = 1
	if not body.character_stats.has_method("add_item"):
		print("ERROR: character_stats missing add_item method")
		return
	
	var was_encumbered = body.character_stats.is_over_encumbered()
	body.character_stats.add_item(inventory_arrow)
	if not was_encumbered and body.character_stats.is_over_encumbered():
		if body.has_method("show_floating_status"):
			body.show_floating_status("Encumbered!", Color(1.0, 0.5, 0.2))
	is_collected = true
	$SfxPickup.play()
	print(body.name, " collected arrow from wall into inventory!")
	_sync_arrow_collected_via_gameworld(arrow_name)
	await $SfxPickup.finished
	queue_free()
