extends CharacterBody3D

signal died
signal health_changed(current: int, maximum: int)

@export var move_speed: float = 7.0
@export var kick_range: float = 3.5
@export var kick_force: float = 60.0
@export var kick_cooldown: float = 0.6
@export var kick_angle: float = 60.0
@export var max_health: int = 300

var health: int = max_health
var kick_tier: int = 0
var toughness_tier: int = 0
var has_stick_armor: bool = false
var has_leather_armor: bool = false
var has_plate_armor: bool = false
var has_wooden_shield: bool = false
var has_iron_shield: bool = false
var shield_tier: int = 0
var is_shielding: bool = false
var kick_timer: float = 0.0
var invincible_timer: float = 0.0
var is_alive: bool = true
var last_move_dir: Vector3 = Vector3.FORWARD

var _aim_yaw: float = 0.0
var _is_aiming: bool = false

@onready var mesh_node: MeshInstance3D = $player_man
@onready var mesh_node_boot: MeshInstance3D = $boot
@onready var indicator_node: MeshInstance3D = $KickIndicator
@onready var anim_player: AnimationPlayer = $AnimationPlayer2

var player_mat: StandardMaterial3D
var indicator_mat: StandardMaterial3D

const IFRAMES_DURATION := 0.6
const BASE_COLOR := Color(0.2, 0.55, 1.0)

func _ready() -> void:
	add_to_group("player")
	player_mat = mesh_node.get_surface_override_material(0).duplicate() as StandardMaterial3D
	mesh_node.set_surface_override_material(0, player_mat)
	mesh_node_boot.visible = false
	_setup_indicator()

func _apply_boot_mesh(tier: int) -> void:
	if tier <= 0:
		mesh_node_boot.visible = false
		return
	mesh_node_boot.visible = true
	var tex := load("res://assets/boot%d.png" % tier) as Texture2D
	if tex == null:
		return
	var mat := mesh_node_boot.get_surface_override_material(0)
	if mat == null:
		mat = StandardMaterial3D.new()
		mesh_node_boot.set_surface_override_material(0, mat)
	(mat as StandardMaterial3D).albedo_texture = tex

func _setup_indicator() -> void:
	indicator_node.position.y = 0.02
	indicator_node.mesh = _make_kick_arc_mesh()
	indicator_mat = StandardMaterial3D.new()
	indicator_mat.albedo_color = Color(1.0, 0.85, 0.1, 0.2)
	indicator_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
	indicator_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	indicator_mat.no_depth_test = true
	indicator_mat.cull_mode = BaseMaterial3D.CULL_DISABLED
	indicator_node.material_override = indicator_mat

func _make_kick_arc_mesh() -> ArrayMesh:
	var verts := PackedVector3Array()
	var half_rad: float = deg_to_rad(kick_angle * 0.5)
	const SEGS := 24
	verts.append(Vector3(0.0, 0.0, 0.0))
	for i in range(SEGS + 1):
		var t: float = float(i) / float(SEGS)
		var a: float = lerpf(-half_rad, half_rad, t)
		verts.append(Vector3(sin(a) * kick_range, 0.0, -cos(a) * kick_range))
	var indices := PackedInt32Array()
	for i in range(SEGS):
		indices.append(0)
		indices.append(i + 1)
		indices.append(i + 2)
	var arrays: Array = []
	arrays.resize(Mesh.ARRAY_MAX)
	arrays[Mesh.ARRAY_VERTEX] = verts
	arrays[Mesh.ARRAY_INDEX] = indices
	var mesh := ArrayMesh.new()
	mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
	return mesh

func _input(event: InputEvent) -> void:
	if event is InputEventKey and event.pressed and not event.echo:
		if event.keycode == KEY_E:
			_try_interact()

func _physics_process(delta: float) -> void:
	if not is_alive:
		return
	is_shielding = shield_tier > 0 and Input.is_key_pressed(KEY_SPACE)
	_handle_movement(delta)
	_handle_kick(delta)
	_handle_iframes(delta)
	if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and kick_timer <= 0.0:
		_do_kick()

func _handle_movement(delta: float) -> void:
	var effective_speed := move_speed * (0.2 if is_shielding else 1.0)
	var input_x: float = (
		float(Input.is_key_pressed(KEY_D) or Input.is_key_pressed(KEY_RIGHT)) -
		float(Input.is_key_pressed(KEY_A) or Input.is_key_pressed(KEY_LEFT))
	)
	var input_z: float = (
		float(Input.is_key_pressed(KEY_S) or Input.is_key_pressed(KEY_DOWN)) -
		float(Input.is_key_pressed(KEY_W) or Input.is_key_pressed(KEY_UP))
	)
	var is_moving: bool = abs(input_x) > 0.0 or abs(input_z) > 0.0
	if is_moving:
		if not anim_player.is_playing() or anim_player.current_animation != "walk":
			anim_player.play("walk")
	else:
		if anim_player.current_animation == "walk":
			anim_player.stop()

	var cam := get_viewport().get_camera_3d()
	if is_moving and cam != null:
		var cam_fwd := -cam.global_transform.basis.z
		cam_fwd.y = 0.0
		cam_fwd = cam_fwd.normalized() if cam_fwd.length() > 0.01 else Vector3(0.0, 0.0, -1.0)
		var cam_right := cam.global_transform.basis.x
		cam_right.y = 0.0
		cam_right = cam_right.normalized() if cam_right.length() > 0.01 else Vector3(1.0, 0.0, 0.0)
		var move := cam_fwd * (-input_z) + cam_right * input_x
		if move.length() > 0.01:
			move = move.normalized()
			velocity.x = move.x * effective_speed
			velocity.z = move.z * effective_speed
			last_move_dir = move
			if not _is_aiming:
				var target_y: float = atan2(-move.x, -move.z)
				rotation.y = lerp_angle(rotation.y, target_y, 16.0 * delta)
	else:
		velocity.x = move_toward(velocity.x, 0.0, effective_speed * 12.0 * delta)
		velocity.z = move_toward(velocity.z, 0.0, effective_speed * 12.0 * delta)
	if _is_aiming:
		rotation.y = lerp_angle(rotation.y, _aim_yaw, 14.0 * delta)
		_is_aiming = false
	velocity.y = 0.0
	move_and_slide()

func _handle_kick(delta: float) -> void:
	kick_timer = max(0.0, kick_timer - delta)
	var t: float = clamp(1.0 - kick_timer / kick_cooldown, 0.0, 1.0)
	indicator_mat.albedo_color.a = 0.05 + t * 0.3

func _handle_iframes(delta: float) -> void:
	if invincible_timer > 0.0:
		invincible_timer -= delta
		if invincible_timer > 0.0:
			mesh_node.visible = fmod(invincible_timer * 10.0, 1.0) > 0.5
		else:
			mesh_node.visible = true

func _do_kick() -> void:
	kick_timer = kick_cooldown
	_play_kick_blend()
	var forward := -global_transform.basis.z
	forward.y = 0.0
	forward = forward.normalized() if forward.length() > 0.01 else Vector3(0.0, 0.0, -1.0)
	var half_cos: float = cos(deg_to_rad(kick_angle * 0.5))
	var candidates: Array[Node3D] = []
	var candidate_dists: Array[float] = []
	for e in get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("kickable"):
		if not is_instance_valid(e):
			continue
		var en := e as Node3D
		if en == null:
			continue
		var diff := en.global_position - global_position
		diff.y = 0.0
		var dist := diff.length()
		if dist < 0.01 or dist > kick_range:
			continue
		if dist < 1.0 or (diff / dist).dot(forward) >= half_cos:
			candidates.append(en)
			candidate_dists.append(dist)

	if candidates.is_empty():
		return

	var nearest_idx := 0
	for i in range(1, candidates.size()):
		if candidate_dists[i] < candidate_dists[nearest_idx]:
			nearest_idx = i

	var best := candidates[nearest_idx]
	var best_diff := best.global_position - global_position
	best_diff.y = 0.0
	var best_dir := best_diff.normalized()
	var obj_toughness: int = best.get("toughness_tier") if best.get("toughness_tier") != null else 0
	var diff_tier := kick_tier - obj_toughness
	var force: float
	if GameSettings.difficulty == "immortal":
		force = 50.0
	elif diff_tier < 0:
		force = 15.0
	elif diff_tier == 0:
		force = 50.0
	elif diff_tier == 1:
		force = 70.0
	else:
		force = 80.0
	best.call("receive_kick", best_dir, force)
	SFX.kick_player(get_parent())
	FX.hit_spark(best.global_position + Vector3(0, 0.4, 0), get_parent())
	_squish_effect()

func _try_interact() -> void:
	var best: Node3D = null
	var best_dist := 2.5
	for node in get_tree().get_nodes_in_group("interactable"):
		var n := node as Node3D
		if n == null:
			continue
		var d := global_position.distance_to(n.global_position)
		if d < best_dist:
			best_dist = d
			best = n
	if best != null:
		best.call("interact", self)

func set_aim_direction(yaw_rad: float) -> void:
	_aim_yaw = yaw_rad
	_is_aiming = true

func _play_kick_blend() -> void:
	var tw := create_tween().set_parallel(true)
	tw.tween_method(func(v: float): mesh_node.set_blend_shape_value(0, v), 0.0, 1.0, 0.12)
	tw.tween_method(func(v: float): mesh_node_boot.set_blend_shape_value(0, v), 0.0, 1.0, 0.12)
	tw.chain()
	tw.tween_method(func(v: float): mesh_node.set_blend_shape_value(0, v), 1.0, 0.0, 0.28)
	tw.tween_method(func(v: float): mesh_node_boot.set_blend_shape_value(0, v), 1.0, 0.0, 0.28)

func _squish_effect() -> void:
	var tw := create_tween()
	tw.tween_property(mesh_node, "scale", Vector3(1.3, 0.55, 1.3), 0.07)
	tw.tween_property(mesh_node, "scale", Vector3(1.0, 1.0, 1.0), 0.18)

func receive_kick(direction: Vector3, force: float) -> void:
	if not is_alive or invincible_timer > 0.0:
		return
	velocity.x = direction.x * force
	velocity.z = direction.z * force
	invincible_timer = IFRAMES_DURATION * 0.5
	_squish_effect()

func take_damage(amount: int, attacker_toughness: int = 0) -> void:
	if GameSettings.difficulty == "immortal":
		return
	if not is_alive or invincible_timer > 0.0:
		return
	invincible_timer = IFRAMES_DURATION
	if is_shielding and shield_tier > 0:
		var diff := shield_tier - attacker_toughness
		var mod: float = 0.15 if diff >= 2 else (0.30 if diff == 1 else 0.50)
		amount = int(amount * mod)
		
	health = min(health - amount, max_health)
	emit_signal("health_changed", health, max_health)
	SFX.damage(get_parent())
	_squish_effect()

func heal(amount: int) -> void:
	if not is_alive:
		return
	health = min(health + amount, max_health)
	emit_signal("health_changed", health, max_health)
	var tw := create_tween()
	tw.tween_property(player_mat, "albedo_color", Color(0.1, 1.0, 0.35), 0.08)
	tw.tween_property(player_mat, "albedo_color", BASE_COLOR, 0.3)

func _die() -> void:
	is_alive = false
	emit_signal("died")
	var tw := create_tween()
	tw.tween_property(self, "scale", Vector3(2.0, 0.0, 2.0), 0.35)

func apply_upgrade(id: String) -> void:
	match id:
		"kick_force":    kick_force += 6.0
		"kick_range":
			kick_range += 0.7
			indicator_node.mesh = _make_kick_arc_mesh()
		"kick_cooldown": kick_cooldown = max(0.12, kick_cooldown - 0.09)
		"move_speed":    move_speed += 1.2
		"max_health":
			max_health += 30
			health = min(health + 30, max_health)
			emit_signal("health_changed", health, max_health)

func apply_upgrade_boots(speed_bonus: float, _tier: int) -> void:
	kick_tier += 1
	move_speed += speed_bonus
	_apply_boot_mesh(kick_tier)

func apply_upgrade_armor() -> void:
	kick_tier += 1

func apply_upgrade_enchant() -> void:
	kick_tier += 1

func apply_stick_armor() -> bool:
	if has_stick_armor:
		return false
	has_stick_armor = true
	toughness_tier += 1
	return true

func apply_leather_armor() -> bool:
	if has_leather_armor:
		return false
	has_leather_armor = true
	toughness_tier += 1
	return true

func apply_plate_armor() -> bool:
	if has_plate_armor:
		return false
	has_plate_armor = true
	toughness_tier += 1
	return true

func apply_wooden_shield() -> bool:
	if has_wooden_shield:
		return false
	has_wooden_shield = true
	shield_tier = max(shield_tier, 1)
	return true

func apply_iron_shield() -> bool:
	if has_iron_shield:
		return false
	has_iron_shield = true
	shield_tier = max(shield_tier, 2)
	return true
