class_name Enemy
extends CharacterBody3D

const PICKUP_SCENE   := preload("res://scenes/Pickup.tscn")
const LEATHER_SCENE  := preload("res://scenes/Leather.tscn")
const IRON_SCENE     := preload("res://scenes/Iron.tscn")
const ESSENCE_SCENE  := preload("res://scenes/Essence.tscn")

signal died(points: int)
signal merged(upgrade: bool)

enum State { CHASING, FLYING, STUNNED, DEAD, MERGING }

static var first_leather_spawned: bool = false
static var first_iron_spawned: bool = false
static var first_essence_spawned: bool = false

var kickable_type: String = ""
var kick_tier: int = 1
var toughness_tier: int = 1
var move_speed: float = 3.0
var health: int = 30
var damage_to_player: int = 8
var score_value: int = 10
var enemy_level: int = 1
var enemy_type: String = "slime"
var wall_damage_mult: float = 1.8
var chain_factor: float = 0.65
var stun_time: float = 0.5
var base_scale: float = 1.0
var wave_num: int = 1
var damage_modifier: float = 0.75
var enemy_kick_timer: float = 0.0
var kickable_kick_timer: float = 0.0
const ENEMY_KICK_COOLDOWN    := 1.2
const KICKABLE_KICK_COOLDOWN := 2.5
const ENEMY_KICK_RANGE       := 2.2

var state: State = State.CHASING
var fly_vel: Vector3 = Vector3.ZERO
var stun_timer: float = 0.0
var contact_timer: float = 0.0
var target: Node3D = null
var merge_partner: Node = null
var is_upgrading: bool = false

@onready var mesh_node: MeshInstance3D = $BodyMesh
@onready var mesh_node1: MeshInstance3D = $BodyMesh1
@onready var mesh_node2: MeshInstance3D = $BodyMesh2
@onready var anim_player: AnimationPlayer = $AnimationPlayer

var mat: StandardMaterial3D
var type_label: Label3D
var active_mesh: MeshInstance3D


var COLOR_CHASE = Color(1.0, 0.28, 0.18)
var COLOR_FLY   = Color(1.0, 0.85, 0.1)
var COLOR_STUN  = Color(0.55, 0.55, 0.65)

const CONTACT_CD   = 0.7
const AIR_FRICTION = 0.86

func _ready() -> void:
	process_mode = Node.PROCESS_MODE_PAUSABLE
	add_to_group("enemies")
	active_mesh = mesh_node
	var orig := mesh_node.material_override
	mat = (orig.duplicate() if orig != null else StandardMaterial3D.new()) as StandardMaterial3D
	mesh_node.material_override = mat
	#mesh_node1.set_visible(false)
	#mesh_node2.set_visible(false)
	COLOR_CHASE = mat.albedo_color
	type_label = Label3D.new()
	type_label.billboard = BaseMaterial3D.BILLBOARD_ENABLED
	type_label.position = Vector3(0, 1.6, 0)
	type_label.font_size = 28
	type_label.outline_size = 5
	type_label.modulate = Color.WHITE
	add_child(type_label)

func _update_label() -> void:
	if type_label == null:
		return
	var stars := ""
	for i in range(enemy_level - 1):
		stars += "★"
	type_label.text = enemy_type + (" " + stars if stars != "" else "")

func setup(type: String, wave: int) -> void:
	enemy_type = type
	kickable_type = type
	wave_num = wave
	match type:
		"slime":
			move_speed = 2.8 + wave * 0.12
			health     = 40 + wave * 4
			score_value = 10
			damage_to_player = 8
			enemy_level = 1
		"bat":
			move_speed = 5 + wave * 0.15
			health     = 100 + wave * 2
			score_value = 20
			damage_to_player = 6
			base_scale = 0.7
			mesh_node.scale = Vector3(0.7, 0.7, 0.7)
			COLOR_CHASE = Color(0.6, 0.2, 0.8)
			mat.albedo_color = COLOR_CHASE
			enemy_level = 2
			#mesh_node.set_visible(false)
			#mesh_node1.set_visible(true)
		"ogre":
			move_speed = 3 + wave * 0.08
			health     = 200 + wave * 12
			score_value = 50
			damage_to_player = 18
			base_scale = 1.5
			mesh_node.scale = Vector3(1.5, 1.5, 1.5)
			COLOR_CHASE = Color(0.3, 0.7, 0.3)
			mat.albedo_color = COLOR_CHASE
			enemy_level = 3
			#mesh_node1.set_visible(false)
			#mesh_node2.set_visible(true)
	kick_tier = enemy_level
	toughness_tier = enemy_level
	_update_label()
	_apply_mesh(enemy_level)

func _apply_mesh(level: int) -> void:
	var idx := clampi(level, 1, 3)
	var scene := load("res://assets/gnome%d.glb" % idx) as PackedScene
	if scene == null:
		return
	var root := scene.instantiate()
	var src: MeshInstance3D = null
	for child in root.get_children():
		var mi := child as MeshInstance3D
		if mi != null:
			src = mi
			break
	if src == null:
		root.queue_free()
		return
	mesh_node.mesh = src.mesh
	var new_mat: StandardMaterial3D
	if src.get_surface_override_material(0) != null:
		new_mat = src.get_surface_override_material(0).duplicate() as StandardMaterial3D
	else:
		new_mat = StandardMaterial3D.new()
		var tex := load("res://assets/gnome%d.png" % idx) as Texture2D
		if tex != null:
			new_mat.albedo_texture = tex
	new_mat.albedo_color = Color.WHITE
	mesh_node.material_override = new_mat
	root.queue_free()
	mat         = new_mat
	COLOR_CHASE = Color.WHITE
	active_mesh = mesh_node

func _physics_process(delta: float) -> void:
	match state:
		State.CHASING: _chase(delta)
		State.FLYING:  _fly(delta)
		State.STUNNED: _stun_tick(delta)
		State.DEAD:    pass

func _chase(delta: float) -> void:
	if not is_instance_valid(target):
		return
	contact_timer = max(0.0, contact_timer - delta)
	enemy_kick_timer = max(0.0, enemy_kick_timer - delta)
	kickable_kick_timer = max(0.0, kickable_kick_timer - delta)
	var diff := target.global_position - global_position
	diff.y = 0.0
	var dist := diff.length()
	if dist < 1.0 and contact_timer <= 0.0:
		contact_timer = CONTACT_CD
		_play_kick_blend()
		if target.has_method("take_damage"):
			target.take_damage(damage_to_player)
			FX.hit_spark(target.global_position + Vector3(0, 0.5, 0), get_parent(), Color(0.95, 0.18, 0.08))
		#if randf() < 0.3:
		var knock_dir := diff.normalized() if dist > 0.01 else -global_transform.basis.z
		knock_dir.y = 0.0
		target.call("receive_kick", knock_dir, 12.0 + kick_tier * 10.0)
	if enemy_kick_timer <= 0.0:
		_try_enemy_kick()
	var sep := Vector3.ZERO
	for e in get_tree().get_nodes_in_group("enemies"):
		if e == self:
			continue
		var en := e as Node3D
		if en == null:
			continue
		var away := global_position - en.global_position
		away.y = 0.0
		var away_dist := away.length()
		if away_dist < 2.2 and away_dist > 0.01:
			sep += away.normalized() * (2.2 - away_dist)
	if dist > 0.05:
		var dir := diff.normalized()
		var move_dir := (dir + sep * 0.6).normalized()
		velocity.x = move_dir.x * move_speed
		velocity.z = move_dir.z * move_speed
		rotation.y = lerp_angle(rotation.y, atan2(move_dir.x, move_dir.z), 8.0 * delta)
	velocity.y = 0.0
	move_and_slide()

func _try_enemy_kick() -> void:
	if not is_instance_valid(target):
		return
	var player_pos := target.global_position
	var to_player := player_pos - global_position
	to_player.y = 0.0
	var kick_dir := to_player.normalized() if to_player.length() > 0.01 else -global_transform.basis.z

	# 1. Kick nearest kickable towards player
	if kickable_kick_timer <= 0.0:
		var nearest_kickable: Node3D = null
		var nearest_dist := ENEMY_KICK_RANGE
		for node in get_tree().get_nodes_in_group("kickable"):
			var k := node as Node3D
			if k == null or not is_instance_valid(k):
				continue
			#if k.get("kickable_type") == "stick":
				#continue
			var kfv = k.get("fly_vel")
			if kfv != null and Vector2((kfv as Vector3).x, (kfv as Vector3).z).length() > 15.0:
				continue
			var d := (k.global_position - global_position)
			d.y = 0.0
			if d.length() < nearest_dist:
				nearest_dist = d.length()
				nearest_kickable = k
		if nearest_kickable != null:
			_play_kick_blend()
			nearest_kickable.call("receive_kick", kick_dir, 35.0 + kick_tier * 8.0)
			FX.hit_spark(nearest_kickable.global_position + Vector3(0, 0.4, 0), get_parent(), Color(1.0, 0.72, 0.1))
			kickable_kick_timer = KICKABLE_KICK_COOLDOWN
			return

	# 2. Kick lower-toughness enemy nearest to player direction
	if kick_tier > 0:
		var nearest_enemy: Node3D = null
		var nearest_enemy_dist := ENEMY_KICK_RANGE
		for node in get_tree().get_nodes_in_group("enemies"):
			var en := node as Node3D
			if en == null or en == self or not is_instance_valid(en):
				continue
			if (en.get("toughness_tier") if en.get("toughness_tier") != null else 0) >= kick_tier:
				continue
			var d := (en.global_position - global_position)
			d.y = 0.0
			if d.length() < nearest_enemy_dist:
				nearest_enemy_dist = d.length()
				nearest_enemy = en
		if nearest_enemy != null:
			_play_kick_blend()
			nearest_enemy.call("receive_kick", kick_dir, 40.0 + kick_tier * 10.0)
			enemy_kick_timer = ENEMY_KICK_COOLDOWN
			return

	# 3. Kick player directly if lower toughness and in range
	#if   kick_tier > 0  and to_player.length() < ENEMY_KICK_RANGE:
	if  to_player.length() < ENEMY_KICK_RANGE:
		var player_toughness: int = target.get("toughness_tier") if target.get("toughness_tier") != null else 0
		if player_toughness < kick_tier:
			_play_kick_blend()
			target.call("receive_kick", kick_dir, 35.0 + kick_tier * 8.0)
			FX.hit_spark(target.global_position + Vector3(0, 0.5, 0), get_parent(), Color(0.95, 0.18, 0.08))
			enemy_kick_timer = ENEMY_KICK_COOLDOWN

func _fly(delta: float) -> void:
	var speed_now := Vector2(fly_vel.x, fly_vel.z).length()
	velocity = fly_vel
	velocity.y = 0.0
	move_and_slide()

	var hit_wall := false
	for i in get_slide_collision_count():
		var col := get_slide_collision(i)
		var col3d := col.get_collider() as Node3D
		if col3d == null:
			continue
		if col3d.has_meta("is_wall"):
			_take_hit(int(speed_now * wall_damage_mult))
			_wall_impact_effect()
			fly_vel = Vector3.ZERO
			velocity = Vector3.ZERO
			_enter_stun()
			hit_wall = true
			break
		elif col3d.is_in_group("enemies") and col3d != self:
			var merged := KickSystem.resolve(self, col3d, fly_vel)
			if not merged and is_instance_valid(col3d):
				var chain_dir := col3d.global_position - global_position
				chain_dir.y = 0.0
				if chain_dir.length() > 0.01:
					col3d.call("receive_kick", chain_dir.normalized(), speed_now * chain_factor)
		elif col3d.is_in_group("kickable"):
			KickSystem.resolve(self, col3d, fly_vel)
			var rock_dir := col3d.global_position - global_position
			rock_dir.y = 0.0
			if rock_dir.length() > 0.01:
				col3d.call("receive_kick", rock_dir.normalized(), speed_now * 0.5)
		elif col3d.is_in_group("player"):
			col3d.call("take_damage", int(speed_now * 0.6))

	if not hit_wall:
		fly_vel = velocity
		fly_vel.y = 0.0
		fly_vel *= pow(AIR_FRICTION, delta * 60.0)
		if Vector2(fly_vel.x, fly_vel.z).length() < 0.4:
			_enter_chase()

	mesh_node.rotation.y += delta * 10.0

func _stun_tick(delta: float) -> void:
	velocity = Vector3.ZERO
	stun_timer -= delta
	if stun_timer <= 0.0:
		_enter_chase()

func can_merge_with(other: Node3D, collision_speed: float) -> bool:
	return (collision_speed >= 18.0
		and other.get("enemy_type") == enemy_type
		and other.get("enemy_level") == enemy_level
		and not is_upgrading
		and not other.get("is_upgrading"))

func do_merge_with(other: Node3D) -> void:
	_start_merge(other)

func apply_collision_damage(dmg: float) -> void:
	_take_hit(int(dmg))

func receive_kick(direction: Vector3, force: float) -> void:
	if state == State.DEAD:
		return
	fly_vel = direction * force
	fly_vel.y = 0.0
	state = State.FLYING
	mat.albedo_color = COLOR_FLY

func _start_merge(other: Node) -> void:
	is_upgrading = true
	other.is_upgrading = true
	other.merge_partner = self
	merge_partner = other
	state = State.MERGING
	other.state = State.MERGING
	fly_vel = Vector3.ZERO
	velocity = Vector3.ZERO
	other.fly_vel = Vector3.ZERO
	other.velocity = Vector3.ZERO
	mat.albedo_color = Color(1.0, 1.0, 0.5)
	other.mat.albedo_color = Color(1.0, 1.0, 0.5)
	var tw := create_tween().set_parallel(true)
	tw.tween_property(self, "global_position", other.global_position, 0.2)
	tw.tween_property(other, "global_position", global_position, 0.2)
	tw.tween_callback(_on_merge_complete)

static func _next_enemy_type(current: String) -> String:
	match current:
		"slime": return "bat"
		"bat":   return "ogre"
		_:       return "titan"

func _on_merge_complete() -> void:
	var merge_pos := global_position
	var merge_type := _next_enemy_type(enemy_type)
	var new_level: int = enemy_level + 1
	var new_wave: int = wave_num
	var par := get_parent()
	if is_instance_valid(merge_partner):
		merge_pos = (global_position + merge_partner.global_position) / 2.0
		merge_partner.queue_free()
	FX.merge_smoke(merge_pos + Vector3(0, 0.3, 0), par)
	queue_free()
	emit_signal("merged", true)
	var tree := get_tree()
	if tree != null and tree.has_group("main"):
		var main := tree.get_nodes_in_group("main")[0] as Node
		if main != null and main.has_method("_spawn_upgraded_enemy"):
			var new_enemy := main.call("_spawn_upgraded_enemy", merge_pos, merge_type, new_level, new_wave) as CharacterBody3D
			if new_enemy != null:
				new_enemy.state = State.FLYING
				new_enemy.fly_vel = Vector3.ZERO

func _enter_stun() -> void:
	state = State.STUNNED
	stun_timer = stun_time
	mat.albedo_color = COLOR_STUN

func _enter_chase() -> void:
	state = State.CHASING
	mat.albedo_color = COLOR_CHASE
	mesh_node.rotation.y = 0.0
	mesh_node1.rotation.y = 0.0
	mesh_node2.rotation.y = 0.0

func _take_hit(dmg: int) -> void:
	if state == State.DEAD:
		return
	health -= dmg
	if health <= 0:
		_die()

func _play_kick_blend() -> void:
	SFX.kick_enemy(get_parent())
	if anim_player != null and anim_player.has_animation("kick"):
		anim_player.stop()
		anim_player.play("kick")

func _wall_impact_effect() -> void:
	var tw := create_tween()
	tw.tween_property(mat, "albedo_color", Color.WHITE, 0.04)
	tw.tween_property(mat, "albedo_color", COLOR_STUN, 0.12)

func _try_drop_pickup() -> void:
	if enemy_level == 1:
		var drop_leather := not first_leather_spawned or randf() < 0.20
		if drop_leather:
			first_leather_spawned = true
			var leather := LEATHER_SCENE.instantiate() as Node3D
			get_parent().add_child(leather)
			leather.global_position = global_position

	if enemy_level == 2:
		var drop_iron := not first_iron_spawned or randf() < 0.20
		if drop_iron:
			first_iron_spawned = true
			var iron := IRON_SCENE.instantiate() as Node3D
			get_parent().add_child(iron)
			iron.global_position = global_position

	if enemy_level == 3:
		var drop_essence := not first_essence_spawned or randf() < 0.20
		if drop_essence:
			first_essence_spawned = true
			var essence := ESSENCE_SCENE.instantiate() as Node3D
			get_parent().add_child(essence)
			essence.global_position = global_position

	var roll := randf()
	var p_type := ""
	var p_heal := 0
	match enemy_type:
		"slime":
			if   roll < 0.30: p_type = "health"; p_heal = 15
			elif roll < 0.40: p_type = "score"
		"bat":
			if   roll < 0.20: p_type = "health"; p_heal = 12
			elif roll < 0.25: p_type = "score"
		"ogre":
			if   roll < 0.55: p_type = "health"; p_heal = 35
			elif roll < 0.80: p_type = "score"
	if p_type == "":
		return
	var pickup := PICKUP_SCENE.instantiate()
	pickup.setup(p_type, p_heal)
	get_parent().add_child(pickup)
	pickup.global_position = global_position

func _die() -> void:
	if state == State.DEAD:
		return
	state = State.DEAD
	set_physics_process(false)
	emit_signal("died", score_value)
	_try_drop_pickup()
	var tw := create_tween()
	tw.tween_property(self, "scale", Vector3(2.0, 0.05, 2.0), 0.18)
	tw.tween_property(self, "scale", Vector3(0.0, 0.0, 0.0), 0.1)
	tw.tween_callback(queue_free)
