extends CharacterBody2D

# Bomb - Explosive projectile that can be thrown or placed

@export var fuse_duration: float = 3.0  # Time until explosion
@export var base_damage: float = 50.0  # Base damage (increased from 30)
@export var damage_radius: float = 48.0  # Area of effect radius (48x48)
@export var screenshake_strength: float = 18.0  # Base screenshake strength (stronger)

var player_owner: Node = null
var is_fused: bool = false
var fuse_timer: float = 0.0
var is_thrown: bool = false  # True if thrown by Dwarf, false if placed
var is_exploding: bool = false
var explosion_frame: int = 0
var explosion_timer: float = 0.0

# Z-axis physics (like interactable_object)
var position_z: float = 0.0
var velocity_z: float = 0.0
var gravity_z: float = 500.0
var is_airborne: bool = false
var throw_velocity: Vector2 = Vector2.ZERO
var rotation_speed: float = 0.0  # Angular velocity when thrown

# Blinking animation
var blink_timer: float = 0.0
var bomb_visible: bool = true  # Renamed to avoid shadowing CanvasItem.is_visible
var blink_start_time: float = 1.0  # Start blinking 1 second before explosion

# Collection
var can_be_collected: bool = false
var collection_delay: float = 0.2  # Can be collected after 0.2 seconds

@onready var sprite = $Sprite2D
@onready var explosion_sprite = $ExplosionSprite
@onready var shadow = $Shadow
@onready var bomb_area = $BombArea
@onready var collection_area = $CollectionArea
@onready var fuse_particles = $FuseParticles
@onready var fuse_light = $FuseLight
@onready var explosion_light = $ExplosionLight

# Damage area (larger than collision)
var damage_area_shape: CircleShape2D = null

const TILE_SIZE: int = 16
const TILE_STRIDE: int = 17  # 16 + separation 1
var _explosion_tile_particle_scene: PackedScene = null

func _ready():
	# Set collision layer to 2 (interactable objects) so it can be grabbed
	collision_layer = 2
	collision_mask = 1 | 2 | 64  # Collide with players, objects, and walls
	
	# Connect area signals
	if bomb_area and not bomb_area.body_entered.is_connected(_on_bomb_area_body_entered):
		bomb_area.body_entered.connect(_on_bomb_area_body_entered)
	if collection_area and not collection_area.body_entered.is_connected(_on_collection_area_body_entered):
		collection_area.body_entered.connect(_on_collection_area_body_entered)
	
	# Ensure bomb sprite is visible
	if sprite:
		sprite.visible = true
	
	# Hide explosion sprite initially
	if explosion_sprite:
		explosion_sprite.visible = false
	
	# Setup shadow (like interactable - visible, under bomb)
	if shadow:
		shadow.visible = true
		shadow.modulate = Color(0, 0, 0, 0.5)
		shadow.z_index = -1
	
	# Group for sync lookup when collected (multiplayer)
	add_to_group("attack_bomb")
	
	# Defer area/shape setup and fuse start – may run during physics (e.g. trap damage → throw)
	call_deferred("_deferred_ready")

func _deferred_ready():
	# Setup damage area (48x48 radius) – safe to touch Area2D/shape when not flushing queries
	if bomb_area:
		var collision_shape = bomb_area.get_node_or_null("CollisionShape2D")
		if collision_shape:
			damage_area_shape = CircleShape2D.new()
			damage_area_shape.radius = damage_radius / 2.0  # 24 pixel radius for 48x48
			collision_shape.shape = damage_area_shape
	
	# Start fuse if not thrown (placed bomb starts fusing immediately; thrown bombs start fuse on land)
	if not is_thrown:
		_start_fuse()

func setup(target_position: Vector2, owner_player: Node, throw_force: Vector2 = Vector2.ZERO, thrown: bool = false):
	# Don't overwrite position if already set (for thrown bombs)
	if not thrown:
		global_position = target_position
	
	player_owner = owner_player
	is_thrown = thrown
	throw_velocity = throw_force
	
	if is_thrown:
		# Thrown bomb - start airborne
		is_airborne = true
		position_z = 2.5
		velocity_z = 100.0
		# Rotation when thrown (based on throw direction)
		if throw_force.length_squared() > 1.0:
			var perp = Vector2(-throw_force.y, throw_force.x)
			rotation_speed = sign(perp.x + perp.y) * 12.0
		else:
			rotation_speed = 8.0
		# Make sure sprite is visible
		if sprite:
			sprite.visible = true
		# Start fuse after landing
	else:
		# Placed bomb - start fusing immediately
		_start_fuse()

func _start_fuse():
	if is_fused:
		return
	
	is_fused = true
	fuse_timer = 0.0
	
	# Play fuse sound
	if has_node("SfxFuse"):
		$SfxFuse.play()
	
	# Start fuse particles
	if fuse_particles:
		fuse_particles.emitting = true
	
	# Enable fuse light
	if fuse_light:
		fuse_light.enabled = true
	
	print("Bomb fuse started!")

func _physics_process(delta):
	if is_exploding:
		# Handle explosion animation
		explosion_timer += delta
		if explosion_sprite:
			# Play 9 frames of explosion animation at ~15 FPS
			if explosion_timer >= 0.06666667:  # ~15 FPS
				explosion_timer = 0.0
				explosion_frame += 1
				if explosion_frame < 9:
					explosion_sprite.frame = explosion_frame
				else:
					# Explosion animation complete - remove
					queue_free()
		return
	
	# Update fuse timer
	if is_fused:
		fuse_timer += delta
		
		# Start blinking when close to explosion
		if fuse_timer >= (fuse_duration - blink_start_time):
			blink_timer += delta
			if blink_timer >= 0.1:  # Blink every 0.1 seconds
				blink_timer = 0.0
				bomb_visible = not bomb_visible
				if sprite:
					sprite.modulate.a = 1.0 if bomb_visible else 0.3
		
		# Explode when fuse runs out
		if fuse_timer >= fuse_duration:
			_explode()
			return
	
	# Z-axis physics (if thrown)
	if is_thrown and is_airborne:
		# Apply gravity
		velocity_z -= gravity_z * delta
		position_z += velocity_z * delta
		
		# Update sprite position and rotation based on height
		if sprite:
			sprite.position.y = -position_z * 0.5
			var height_scale = 1.0 - (position_z / 50.0) * 0.2
			sprite.scale = Vector2.ONE * max(0.8, height_scale)
			sprite.rotation += rotation_speed * delta
		
		# Update shadow (like interactable - scale down when airborne for visibility)
		if shadow:
			shadow.visible = true
			var shadow_scale = 1.0 - (position_z / 75.0) * 0.5
			shadow.scale = Vector2.ONE * max(0.5, shadow_scale)
			shadow.modulate = Color(0, 0, 0, 0.5 - (position_z / 100.0) * 0.3)
		
		# Apply throw velocity
		velocity = throw_velocity
		
		# Check if landed
		if position_z <= 0.0:
			_land()
	else:
		# On ground - reset sprite/shadow (shadow visible like interactable)
		if sprite:
			sprite.position.y = 0
			sprite.scale = Vector2.ONE
			sprite.rotation = 0.0
		if shadow:
			shadow.visible = true
			shadow.scale = Vector2.ONE
			shadow.modulate = Color(0, 0, 0, 0.5)
		
		# Apply friction if on ground
		if not is_airborne:
			throw_velocity = throw_velocity.lerp(Vector2.ZERO, delta * 5.0)
			if throw_velocity.length() < 5.0:
				throw_velocity = Vector2.ZERO
			velocity = throw_velocity
	
	# Move
	move_and_slide()
	
	# Check for collisions while airborne (instant explode on enemy/player hit)
	if is_airborne and throw_velocity.length() > 50.0:
		for i in get_slide_collision_count():
			var collision = get_slide_collision(i)
			var collider = collision.get_collider()
			
			if collider and (collider.is_in_group("player") or collider.is_in_group("enemy")):
				if collider != player_owner:
					# Instant explode on hit
					_explode()
					return
	
	# Enable collection after delay
	if is_fused and not can_be_collected:
		if fuse_timer >= collection_delay:
			can_be_collected = true
			if collection_area:
				collection_area.set_deferred("monitoring", true)

func _land():
	is_airborne = false
	position_z = 0.0
	velocity_z = 0.0
	
	# Start fuse when landing
	if not is_fused:
		_start_fuse()

func _explode():
	if is_exploding:
		return
	
	is_exploding = true
	
	# Hide bomb sprite and shadow, show explosion
	if sprite:
		sprite.visible = false
	if shadow:
		shadow.visible = false
	if explosion_sprite:
		explosion_sprite.visible = true
		explosion_sprite.frame = 0
		explosion_frame = 0
		explosion_timer = 0.0
	
	# Stop fuse sound and particles
	if has_node("SfxFuse"):
		$SfxFuse.stop()
	if fuse_particles:
		fuse_particles.emitting = false
	
	# Disable fuse light, enable explosion light
	if fuse_light:
		fuse_light.enabled = false
	if explosion_light:
		explosion_light.enabled = true
		# Fade out explosion light over time
		var tween = create_tween()
		tween.tween_property(explosion_light, "energy", 0.0, 0.3)
		tween.tween_callback(func(): if explosion_light: explosion_light.enabled = false)
	
	# Play explosion sound
	if has_node("SfxExplosion"):
		$SfxExplosion.play()
	
	# Deal area damage
	_deal_explosion_damage()
	
	# Cause screenshake
	_cause_screenshake()
	
	# Spawn tile debris particles (4 pieces per affected tile, bounce, fade)
	_spawn_explosion_tile_particles()
	if has_node("SfxDebrisFromParticles"):
		$SfxDebrisFromParticles.play()
	
	# Disable collision
	if bomb_area:
		bomb_area.set_deferred("monitoring", false)
	if collection_area:
		collection_area.set_deferred("monitoring", false)
	
	print("Bomb exploded!")

func _deal_explosion_damage():
	if not bomb_area:
		return
	
	# Get all bodies in explosion radius
	var bodies = bomb_area.get_overlapping_bodies()
	
	# Calculate total damage including strength bonus
	var total_damage = base_damage
	
	# Add strength-based bonus damage
	if player_owner and player_owner.character_stats:
		var strength = player_owner.character_stats.baseStats.str + player_owner.character_stats.get_pass("str")
		# Add 1.5 damage per strength point
		var strength_bonus = strength * 1.5
		total_damage += strength_bonus
		print("Bomb damage: base=", base_damage, " + str_bonus=", strength_bonus, " (STR=", strength, ") = ", total_damage)
	
	for body in bodies:
		# CRITICAL: Only the bomb owner (authority) should deal damage
		if player_owner and not player_owner.is_multiplayer_authority():
			continue
		
		# Note: Removed the check that skips player_owner - bombs now damage the player who used them!
		
		# Calculate distance for damage falloff
		var distance = global_position.distance_to(body.global_position)
		var damage_multiplier = 1.0 - (distance / damage_radius)  # Linear falloff
		damage_multiplier = max(0.1, damage_multiplier)  # Minimum 10% damage
		var final_damage = total_damage * damage_multiplier
		
		# Deal damage to players
		if body.is_in_group("player") and body.has_method("rpc_take_damage"):
			var attacker_pos = player_owner.global_position if player_owner else global_position
			var player_peer_id = body.get_multiplayer_authority()
			# Avoid "RPC on yourself": call take_damage directly when victim is local peer
			if player_peer_id != 0 and player_peer_id == multiplayer.get_unique_id():
				if body.has_method("take_damage"):
					body.take_damage(final_damage, attacker_pos)
			elif player_peer_id != 0:
				body.rpc_take_damage.rpc_id(player_peer_id, final_damage, attacker_pos)
			else:
				body.rpc_take_damage.rpc(final_damage, attacker_pos)
			
			print("Bomb hit player: ", body.name, " for ", final_damage, " damage!")
		
		# Deal damage to enemies
		elif body.is_in_group("enemy") and body.has_method("rpc_take_damage"):
			var attacker_pos = player_owner.global_position if player_owner else global_position
			var enemy_peer_id = body.get_multiplayer_authority()
			# Avoid "RPC on yourself": call take_damage directly when enemy authority is local peer
			if enemy_peer_id != 0 and enemy_peer_id == multiplayer.get_unique_id():
				if body.has_method("take_damage"):
					body.take_damage(final_damage, attacker_pos, false, false, false)
			elif enemy_peer_id != 0:
				body.rpc_take_damage.rpc_id(enemy_peer_id, final_damage, attacker_pos, false, false, false)
			else:
				body.rpc_take_damage.rpc(final_damage, attacker_pos, false, false, false)
			
			print("Bomb hit enemy: ", body.name, " for ", final_damage, " damage!")
		
		# Deal damage to interactable objects (pots, boxes, etc.)
		elif body.has_method("can_be_destroyed") and body.can_be_destroyed() and body.has_method("take_damage"):
			var attacker_pos = player_owner.global_position if player_owner else global_position
			body.take_damage(final_damage, attacker_pos)
			print("Bomb hit interactable: ", body.name, " for ", final_damage, " damage!")

func _spawn_explosion_tile_particles():
	var game_world = get_tree().get_first_node_in_group("game_world")
	if not game_world:
		return
	
	var layer = game_world.get_node_or_null("Environment/DungeonLayer0")
	if not layer or not layer is TileMapLayer:
		return
	
	if not _explosion_tile_particle_scene:
		_explosion_tile_particle_scene = load("res://scenes/explosion_tile_particle.tscn") as PackedScene
	if not _explosion_tile_particle_scene:
		return
	
	var tex = load("res://assets/gfx/RPG DUNGEON VOL 3.png") as Texture2D
	if not tex:
		return
	
	var center = global_position
	var r = damage_radius
	var layer_pos = center - layer.global_position
	var center_cell = layer.local_to_map(layer_pos)
	var half_cells = ceili(r / float(TILE_SIZE)) + 1
	
	var parent = get_parent()
	if not parent:
		parent = game_world.get_node_or_null("Entities")
	if not parent:
		return
	
	for gx in range(center_cell.x - half_cells, center_cell.x + half_cells + 1):
		for gy in range(center_cell.y - half_cells, center_cell.y + half_cells + 1):
			var cell = Vector2i(gx, gy)
			if layer.get_cell_source_id(cell) < 0:
				continue
			var atlas = layer.get_cell_atlas_coords(cell)
			var world = layer.map_to_local(cell) + layer.global_position
			if world.distance_to(center) > r:
				continue
			var bx = atlas.x * TILE_STRIDE
			var by = atlas.y * TILE_STRIDE
			var h = 8.0  # TILE_SIZE / 2
			var regions = [
				Rect2(bx, by, h, h),
				Rect2(bx + h, by, h, h),
				Rect2(bx, by + h, h, h),
				Rect2(bx + h, by + h, h, h)
			]
			# Direction from explosion center to this tile (outward) – particles fly away from bomb
			var to_tile = world - center
			var outward = to_tile.normalized() if to_tile.length() > 1.0 else Vector2.RIGHT.rotated(randf() * TAU)
			# Reduced particles: 1 piece per tile instead of 2 (use index 0)
			var i = 0
			var p = _explosion_tile_particle_scene.instantiate() as CharacterBody2D
			var spr = p.get_node_or_null("Sprite2D") as Sprite2D
			if not spr:
				p.queue_free()
				continue
			spr.texture = tex
			spr.region_enabled = true
			spr.region_rect = regions[i]
			
			# CRITICAL: Apply level's material and colorization to tile particles
			# Get the material from the tilemap layer and duplicate it
			# Duplicating ShaderMaterial copies all shader parameters (colorization, tint, ambient, etc.)
			if layer.material and layer.material is ShaderMaterial:
				var layer_material = layer.material as ShaderMaterial
				var particle_material = layer_material.duplicate() as ShaderMaterial
				spr.material = particle_material
			
			p.global_position = world
			var speed = randf_range(280.0, 420.0)  # Much faster - fly around more
			var d = outward + Vector2(randf_range(-0.4, 0.4), randf_range(-0.4, 0.4))
			p.velocity = d.normalized() * speed
			p.angular_velocity = randf_range(-14.0, 14.0)
			p.position_z = 0.0
			p.velocity_z = randf_range(100.0, 180.0)  # Upward burst, then gravity brings them down
			parent.add_child(p)

func _cause_screenshake():
	# Calculate screenshake based on distance from local players
	var game_world = get_tree().get_first_node_in_group("game_world")
	if not game_world:
		return
	
	# Get local players from player_manager
	var player_manager = game_world.get_node_or_null("PlayerManager")
	if not player_manager or not player_manager.has_method("get_local_players"):
		return
	
	var local_players = player_manager.get_local_players()
	if local_players.is_empty():
		return
	
	# Find closest local player
	var min_distance = INF
	for player in local_players:
		if not is_instance_valid(player):
			continue
		var distance = global_position.distance_to(player.global_position)
		min_distance = min(min_distance, distance)
	
	# Calculate screenshake strength (inverse distance, capped)
	var shake_strength = screenshake_strength / max(1.0, min_distance / 50.0)
	shake_strength = min(shake_strength, screenshake_strength * 2.0)  # Cap at 2x base
	
	# Apply screenshake (longer duration for bigger boom)
	if game_world.has_method("add_screenshake"):
		game_world.add_screenshake(shake_strength, 0.5)  # 0.5 second duration

func _on_bomb_area_body_entered(_body):
	# This is for explosion damage (handled in _deal_explosion_damage)
	pass

func can_be_grabbed() -> bool:
	# Bomb can be grabbed if it's fused and can be collected
	return is_fused and can_be_collected and not is_exploding

func on_grabbed(by_player):
	# When bomb is grabbed, collect it immediately (don't wait for release)
	if not can_be_collected or is_exploding:
		return
	
	if not by_player.character_stats:
		return
	
	var offhand_item = by_player.character_stats.equipment.get("offhand", null)
	var can_collect = false
	
	if not offhand_item:
		# Empty offhand - can collect
		can_collect = true
	elif offhand_item.item_name == "Bomb":
		# Already has bombs - can stack
		can_collect = true
	
	if can_collect:
		# Stop fuse sound, particles, and light when collecting
		if has_node("SfxFuse"):
			$SfxFuse.stop()
		if fuse_particles:
			fuse_particles.emitting = false
		if fuse_light:
			fuse_light.enabled = false
		
		# Create bomb item
		var bomb_item = ItemDatabase.create_item("bomb")
		if bomb_item:
			bomb_item.quantity = 1
			
			if not offhand_item:
				# Equip to offhand
				by_player.character_stats.equipment["offhand"] = bomb_item
			else:
				# Add to existing stack
				offhand_item.quantity += 1
			
			by_player.character_stats.character_changed.emit(by_player.character_stats)
			
			# Show "+1 Bomb" above player
			var floating_text_scene = load("res://scenes/floating_text.tscn") as PackedScene
			if floating_text_scene and by_player and is_instance_valid(by_player):
				var ft = floating_text_scene.instantiate()
				var parent = by_player.get_parent()
				if parent:
					parent.add_child(ft)
					ft.global_position = Vector2(by_player.global_position.x, by_player.global_position.y - 20)
					ft.setup("+1 Bomb", Color(0.9, 0.5, 0.2), 0.5, 0.5)  # Orange-ish
			
			# Play pickup sound
			if has_node("SfxPickup"):
				$SfxPickup.play()
			
			print(by_player.name, " collected bomb!")
			
			# Sync removal to other clients so bomb doesn't keep exploding on their sessions
			if multiplayer.has_multiplayer_peer() and by_player and is_instance_valid(by_player):
				if by_player.has_method("_rpc_to_ready_peers") and by_player.is_inside_tree():
					by_player._rpc_to_ready_peers("_sync_bomb_collected", [name])
			
			# Remove bomb immediately
			queue_free()

func _on_collection_area_body_entered(_body):
	# This is a backup - main collection happens via can_be_grabbed/on_grabbed
	# But we can also handle it here if needed
	pass
