extends CharacterBody2D

var speed: float = 150.0
var gravity: float = 1200.0

var max_health: int = 100
var health: int = 100

var attack_range: float = 85.0
var attack_damage: int = 10
var cooldown: float = 0.0
var attack_windup: float = 0.18
var attack_recover: float = 0.85

var is_blocking: bool = false
var block_chance: float = 0.25
var block_damage_multiplier: float = 0.35
var block_time_left: float = 0.0

var min_x: float = 40.0
var max_x: float = 1240.0

var anim: AnimatedSprite2D = null
var player_ref: CharacterBody2D = null
func set_player(p: CharacterBody2D) -> void:
	player_ref = p

const SPRITE_FOLDER := "res://assets/enemy"

func _ready() -> void:
	await get_tree().process_frame

	health = max_health
	velocity = Vector2.ZERO

	anim = _get_or_create_anim()
	if anim.sprite_frames == null or anim.sprite_frames.get_animation_names().is_empty():
		anim.sprite_frames = _build_sprite_frames_from_folder(SPRITE_FOLDER)

	anim.visible = true
	anim.modulate.a = 1.0
	if anim.scale == Vector2.ZERO:
		anim.scale = Vector2.ONE

	_safe_play("idle")

func _physics_process(delta: float) -> void:
	if player_ref == null:
		return

	if cooldown > 0.0:
		cooldown = max(cooldown - delta, 0.0)
	if block_time_left > 0.0:
		block_time_left = max(block_time_left - delta, 0.0)
	else:
		is_blocking = false

	# gravity
	if not is_on_floor():
		velocity.y += gravity * delta
	else:
		if velocity.y > 0.0:
			velocity.y = 0.0

	var dx: float = player_ref.global_position.x - global_position.x
	var dist: float = abs(dx)

	# face player
	if anim != null:
		anim.flip_h = (dx < 0.0)

	# block sometimes when close
	if not is_blocking and dist < 120.0 and randf() < (block_chance * delta * 2.0):
		is_blocking = true
		block_time_left = 0.6

	# movement
	if not is_blocking and dist > 70.0:
		velocity.x = speed if dx > 0.0 else -speed
		_safe_play("walk")
	else:
		velocity.x = 0.0
		if is_blocking:
			_safe_play("block")
		else:
			_safe_play("idle")

	move_and_slide()
	global_position.x = clamp(global_position.x, min_x, max_x)

	# attack
	if (not is_blocking) and cooldown <= 0.0 and dist <= attack_range:
		_start_attack()

func _start_attack() -> void:
	cooldown = attack_windup + attack_recover
	_safe_play("attack")
	var t := get_tree().create_timer(attack_windup)
	t.timeout.connect(func(): _try_apply_damage())

func _try_apply_damage() -> void:
	if player_ref == null:
		return

	var dist: float = abs(player_ref.global_position.x - global_position.x)
	if dist > attack_range:
		return

	if player_ref.has_method("take_damage"):
		player_ref.call("take_damage", attack_damage, global_position.x)

func take_damage(amount: int, _attacker_x: float = 0.0) -> void:
	if health <= 0:
		return

	var final_amount := amount
	if is_blocking:
		final_amount = int(round(float(amount) * block_damage_multiplier))

	health = max(health - final_amount, 0)
	_safe_play("hit")

	if not is_blocking and randf() < 0.25:
		is_blocking = true
		block_time_left = 0.7

	if health <= 0:
		var parent_node := get_parent()
		if parent_node != null and parent_node.has_method("end_fight"):
			parent_node.call("end_fight", true)

# -------- sprite helpers --------

func _get_or_create_anim() -> AnimatedSprite2D:
	var found := _find_first_animated_sprite(self)
	if found != null:
		return found

	var new_anim := AnimatedSprite2D.new()
	new_anim.name = "AnimatedSprite2D"
	add_child(new_anim)
	new_anim.position = Vector2(0, -40)
	new_anim.scale = Vector2(0.25, 0.25)
	return new_anim

func _find_first_animated_sprite(root: Node) -> AnimatedSprite2D:
	if root is AnimatedSprite2D:
		return root
	for c in root.get_children():
		var res := _find_first_animated_sprite(c)
		if res != null:
			return res
	return null

func _safe_play(anim_name: String) -> void:
	if anim == null or anim.sprite_frames == null:
		return

	if not anim.sprite_frames.has_animation(anim_name):
		if anim_name != "idle" and anim.sprite_frames.has_animation("idle"):
			if anim.animation != "idle":
				anim.play("idle")
		return

	if anim.animation != anim_name:
		anim.play(anim_name)

func _build_sprite_frames_from_folder(folder: String) -> SpriteFrames:
	var frames := SpriteFrames.new()

	_add_anim_from_prefix(frames, folder, "idle", 6.0, true)
	_add_anim_from_prefix(frames, folder, "walk", 10.0, true)
	_add_anim_from_prefix(frames, folder, "attack", 12.0, false)
	_add_anim_from_prefix(frames, folder, "hit", 10.0, false)
	_add_anim_from_prefix(frames, folder, "block", 8.0, true)
	_add_anim_from_prefix(frames, folder, "jump", 8.0, false)
	if frames.get_animation_names().is_empty():
		push_warning("Enemy: No sprites found in " + folder + " (expected idle_1.png, walk_1.png etc.)")

	return frames

func _add_anim_from_prefix(frames: SpriteFrames, folder: String, anim_name: String, fps: float, loop: bool) -> void:
	var textures: Array[Texture2D] = _load_textures_sorted(folder, anim_name + "_")
	if textures.is_empty():
		return

	if not frames.has_animation(anim_name):
		frames.add_animation(anim_name)
	frames.set_animation_speed(anim_name, fps)
	frames.set_animation_loop(anim_name, loop)

	for t in textures:
		frames.add_frame(anim_name, t)

func _load_textures_sorted(folder: String, prefix: String) -> Array[Texture2D]:
	var out: Array[Texture2D] = []
	var dir := DirAccess.open(folder)
	if dir == null:
		push_warning("Enemy: Folder not found: " + folder)
		return out

	dir.list_dir_begin()
	var file := dir.get_next()
	while file != "":
		if not dir.current_is_dir():
			var lower := file.to_lower()
			if lower.ends_with(".png") and file.begins_with(prefix):
				var tex := load(folder + "/" + file) as Texture2D
				if tex != null:
					out.append(tex)
		file = dir.get_next()
	dir.list_dir_end()

	out.sort_custom(func(a: Texture2D, b: Texture2D) -> bool:
		return _extract_suffix_number(a.resource_path, prefix) < _extract_suffix_number(b.resource_path, prefix)
	)
	return out

func _extract_suffix_number(path: String, prefix: String) -> int:
	var file := path.get_file()
	if not file.begins_with(prefix):
		return 0
	var s := file.replace(prefix, "").replace(".png", "")
	return int(s)
