class_name ArcadeEnemy
extends CharacterBody3D

# Three-state FSM:
#   WANDER: pick a random passable cell in this enemy's room, walk there,
#           occasionally idle.
#   CHASE:  move toward last-known player grid cell via BFS.
#   ATTACK: within melee range — swing, tick cooldown.
#
# Transitions:
#   WANDER -> CHASE  when player has LOS AND distance ≤ sight_radius
#   CHASE  -> WANDER when player has been out of sight for lose_sight_turns
#   CHASE  -> ATTACK when close enough to hit
#   ATTACK -> CHASE  when out of range

signal died

enum State { WANDER, CHASE, ATTACK }

@export var hp := 8
@export var move_speed := 3.0
@export var sight_radius := 10.0
@export var melee_range := 1.4
@export var melee_damage := 2
@export var melee_cooldown := 0.9
@export var lose_sight_seconds := 3.0

# Set by spawner on instantiate.
var grid_ref: Dictionary = {}
var room_cells: Array = []   # Array[Vector2i]
var player_ref: Node3D = null
var rng := RandomNumberGenerator.new()

var _state := State.WANDER
var _path_target: Vector2i = Vector2i(-1, -1)
var _repath_timer := 0.0
var _attack_timer := 0.0
var _lose_sight_timer := 0.0
var _alive := true

func _ready() -> void:
	rng.randomize()
	_pick_wander_target()

func set_room(cells: Array) -> void:
	room_cells = cells
	_pick_wander_target()

func _physics_process(delta: float) -> void:
	if not _alive:
		return
	if _attack_timer > 0.0:
		_attack_timer -= delta

	_update_state(delta)

	match _state:
		State.WANDER:  _do_wander(delta)
		State.CHASE:   _do_chase(delta)
		State.ATTACK:  _do_attack(delta)

	# Gravity always.
	if not is_on_floor():
		velocity.y -= 22.0 * delta
	else:
		velocity.y = max(velocity.y, 0.0)

	move_and_slide()

# --- state machine ---

func _update_state(delta: float) -> void:
	if player_ref == null:
		return
	var my_grid := GridUtil.world_to_grid(global_position)
	var p_grid := GridUtil.world_to_grid(player_ref.global_position)
	var dist := float(my_grid.distance_to(p_grid))
	var can_see := dist <= sight_radius and GridUtil.has_los(grid_ref, my_grid, p_grid)

	match _state:
		State.WANDER:
			if can_see:
				_state = State.CHASE
				_path_target = p_grid
				_repath_timer = 0.0
		State.CHASE:
			if can_see:
				_lose_sight_timer = 0.0
				_path_target = p_grid
			else:
				_lose_sight_timer += delta
				if _lose_sight_timer >= lose_sight_seconds:
					_state = State.WANDER
					_pick_wander_target()
					return
			# Close enough to swing?
			var world_dist := global_position.distance_to(player_ref.global_position)
			if world_dist <= melee_range:
				_state = State.ATTACK
		State.ATTACK:
			var world_dist2 := global_position.distance_to(player_ref.global_position)
			if world_dist2 > melee_range * 1.3:
				_state = State.CHASE
			if not can_see:
				_lose_sight_timer += delta
				if _lose_sight_timer >= lose_sight_seconds:
					_state = State.WANDER
					_pick_wander_target()

# --- behaviors ---

func _do_wander(delta: float) -> void:
	_repath_timer -= delta
	if _path_target == Vector2i(-1, -1) or _at_target():
		if _repath_timer <= 0.0:
			_repath_timer = rng.randf_range(0.8, 2.0)
		_pick_wander_target()
	_step_toward(_path_target, move_speed * 0.5)

func _do_chase(delta: float) -> void:
	_repath_timer -= delta
	if _repath_timer <= 0.0:
		_repath_timer = 0.25  # repath 4×/sec
	_step_toward(_path_target, move_speed)

func _do_attack(_delta: float) -> void:
	velocity.x = 0.0
	velocity.z = 0.0
	if _attack_timer <= 0.0:
		_attack_timer = melee_cooldown
		if player_ref and player_ref.has_method("take_damage"):
			player_ref.take_damage(melee_damage, self)

# --- helpers ---

func _pick_wander_target() -> void:
	if room_cells.is_empty():
		_path_target = GridUtil.world_to_grid(global_position)
		return
	_path_target = GridUtil.random_passable(grid_ref, room_cells, rng)

func _at_target() -> bool:
	var my_grid := GridUtil.world_to_grid(global_position)
	return my_grid == _path_target

func _step_toward(target: Vector2i, speed: float) -> void:
	if target == Vector2i(-1, -1):
		velocity.x = 0.0
		velocity.z = 0.0
		return
	var my_grid := GridUtil.world_to_grid(global_position)
	var nxt := GridUtil.next_step_toward(grid_ref, my_grid, target)
	if nxt == my_grid:
		velocity.x = 0.0
		velocity.z = 0.0
		return
	var target_world := GridUtil.grid_to_world(nxt, global_position.y)
	var dir := (target_world - global_position)
	dir.y = 0
	dir = dir.normalized()
	velocity.x = dir.x * speed
	velocity.z = dir.z * speed
	# Face movement direction so we look right visually.
	if dir.length() > 0.1:
		look_at(global_position + dir, Vector3.UP)

func take_damage(amount: int, _source: Node) -> void:
	if not _alive:
		return
	hp -= amount
	if hp <= 0:
		_alive = false
		died.emit()
		queue_free()
