class_name PlayerProjectile
extends Node2D

const AttackGeometry := preload("res://scripts/core/attack_geometry.gd")

# Projectiles are 2D combat authority. Bridge3D only mirrors this node, exactly
# like it mirrors enemies and pickups; collision and damage never move to 3D.

var direction := Vector2.RIGHT
var speed := 600.0
var damage := 1.0
var knockback := 0.0
var collision_radius := 6.0
var remaining_lifetime := 1.0
var remaining_pierces := 0
var respect_occlusion := true
var projectile_type := "orb"
var projectile_color := Color("7dd3fc")
var combat_effects: Array = []
var explosion_radius := 0.0
var source: Node2D
var hit_enemies: Array[int] = []

func setup(config: Dictionary) -> void:
	direction = (config.get("direction", Vector2.RIGHT) as Vector2).normalized()
	if direction.length_squared() <= 0.001:
		direction = Vector2.RIGHT
	speed = maxf(1.0, float(config.get("speed", speed)))
	damage = maxf(0.0, float(config.get("damage", damage)))
	knockback = maxf(0.0, float(config.get("knockback", knockback)))
	collision_radius = maxf(1.0, float(config.get("radius", collision_radius)))
	remaining_lifetime = maxf(0.05, float(config.get("lifetime", remaining_lifetime)))
	remaining_pierces = maxi(0, int(config.get("piercing", 0)))
	respect_occlusion = bool(config.get("respect_occlusion", true))
	projectile_type = str(config.get("type", "orb"))
	combat_effects = config.get("combat_effects", []).duplicate(true) if config.get("combat_effects", []) is Array else []
	explosion_radius = maxf(0.0, float(config.get("explosion_radius", 0.0)))
	var color: Variant = config.get("color", projectile_color)
	projectile_color = color if color is Color else projectile_color
	source = config.get("source") as Node2D
	queue_redraw()

func _ready() -> void:
	add_to_group("player_projectile")
	# Groupe commun aux deux camps: le pont 3D materialise un tir sans avoir a
	# savoir qui l'a lance.
	add_to_group("combat_projectile")
	z_index = 8
	queue_redraw()

func _physics_process(delta: float) -> void:
	var previous := global_position
	var next := previous + direction * speed * delta
	var candidates: Array[Dictionary] = []
	for node: Node in get_tree().get_nodes_in_group("enemy"):
		if not node is Enemy or not is_instance_valid(node):
			continue
		var enemy := node as Enemy
		var instance_id := enemy.get_instance_id()
		if hit_enemies.has(instance_id) or not bool(enemy.get("active")):
			continue
		var body_radius := float(enemy.call("body_radius")) if enemy.has_method("body_radius") else 0.0
		var contact := AttackGeometry.segment_circle_hit_ratio(previous, next,
			enemy.global_position, collision_radius + body_radius)
		if contact >= 0.0:
			candidates.append({"enemy": enemy, "ratio": contact})
	candidates.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
		return float(a.ratio) < float(b.ratio))
	global_position = next
	for candidate: Dictionary in candidates:
		var enemy := candidate.enemy as Enemy
		if enemy == null or not is_instance_valid(enemy):
			continue
		hit_enemies.append(enemy.get_instance_id())
		if explosion_radius > 0.0:
			global_position = previous.lerp(next, float(candidate.ratio))
			_explode()
			return
		enemy.take_damage(damage, direction, knockback, source)
		enemy.apply_combat_effects(combat_effects, "projectile")
		if respect_occlusion:
			if remaining_pierces <= 0:
				queue_free()
				return
			remaining_pierces -= 1
	remaining_lifetime -= delta
	if remaining_lifetime <= 0.0:
		if explosion_radius > 0.0:
			_explode()
		else:
			queue_free()

func _explode() -> void:
	for node: Node in get_tree().get_nodes_in_group("enemy"):
		if not node is Enemy or not is_instance_valid(node) or not bool(node.get("active")):
			continue
		var enemy := node as Enemy
		var radius := float(enemy.call("body_radius")) if enemy.has_method("body_radius") else 0.0
		if global_position.distance_to(enemy.global_position) > explosion_radius + radius:
			continue
		var push := (enemy.global_position - global_position).normalized()
		# Un nuage de poison à zéro dégât applique son statut sans provoquer un
		# faux impact, une interruption ou un flash de dégâts nuls.
		if damage > 0.0:
			enemy.take_damage(damage, push, knockback, source)
		enemy.apply_combat_effects(combat_effects, "consumable")
	queue_free()

func body_radius() -> float:
	return collision_radius

func _draw() -> void:
	if Render3D.active:
		return
	match projectile_type:
		"bolt", "needle":
			draw_line(-direction * collision_radius * 1.8, direction * collision_radius,
				projectile_color, collision_radius, true)
		"disc":
			draw_circle(Vector2.ZERO, collision_radius, projectile_color)
			draw_circle(Vector2.ZERO, collision_radius * 0.55, projectile_color.lightened(0.35), false, 2.0)
		_:
			draw_circle(Vector2.ZERO, collision_radius, projectile_color)
			draw_circle(Vector2.ZERO, collision_radius, Color.WHITE, false, 2.0)
	if explosion_radius > 0.0:
		draw_circle(Vector2.ZERO, collision_radius * 0.55, Color.WHITE.lightened(0.1))
