class_name EnemyProjectile
extends Node2D

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

# Mirror of PlayerProjectile, aimed the other way. It stays a separate script
# rather than a flag on the player one: the two never share a target list, and a
# single node that could hit either side would be one bug away from friendly
# fire. Both join the "combat_projectile" group so Bridge3D mirrors them alike.
#
# A creature shot has no piercing and no occlusion setting: there is exactly one
# body it can hit, so it stops on contact or expires.

var direction := Vector2.RIGHT
var speed := 400.0
var damage := 1.0
var knockback := 0.0
var collision_radius := 6.0
var remaining_lifetime := 1.0
var projectile_type := "orb"
var projectile_color := Color("ff8fa3")
var combat_effects: Array = []
var source: Node2D
var source_id := ""

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)))
	projectile_type = str(config.get("type", "orb"))
	combat_effects = config.get("combat_effects", []).duplicate(true) if config.get("combat_effects", []) is Array else []
	var color: Variant = config.get("color", projectile_color)
	projectile_color = color if color is Color else projectile_color
	source = config.get("source") as Node2D
	source_id = str(config.get("source_id", ""))
	queue_redraw()

func _ready() -> void:
	add_to_group("enemy_projectile")
	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
	global_position = next
	var hero := get_tree().get_first_node_in_group("player") as Player
	if hero != null and is_instance_valid(hero):
		var contact := AttackGeometry.segment_circle_hit_ratio(previous, next,
			hero.global_position, collision_radius + hero.body_radius())
		if contact >= 0.0:
			global_position = previous.lerp(next, contact)
			_hit(hero)
			return
	remaining_lifetime -= delta
	if remaining_lifetime <= 0.0:
		queue_free()

func _hit(hero: Player) -> void:
	var push := direction
	var offset := hero.global_position - global_position
	if offset.length_squared() > 0.001:
		push = offset.normalized()
	hero.take_damage(damage, push, knockback)
	if hero.has_method("apply_combat_effects"):
		hero.call("apply_combat_effects", combat_effects, "enemy:%s" % source_id)
	if not source_id.is_empty():
		GameState.add_entity_counter("enemies", source_id, "times_damaged_player")
		Registry.run_hook("enemies", source_id, "on_damage_player", source)
	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)
