extends Node3D

# Arcade main scene. Owns:
#   - current depth
#   - map generation + rendering
#   - player spawning
#   - enemy spawning (proportional to room size)
#   - level clear detection (player steps onto stairs_down)
#   - death / restart
# Emits signals for the HUD.

signal depth_changed(depth: int, total: int)
signal enemies_changed(remaining: int, total: int)
signal game_over(won: bool)

const T_STAIRS_DOWN := 8

@export var base_seed: int = 25
@export var start_depth: int = 1
@export var total_depths: int = 10       # win after clearing this many
# Enemy count per room scales with depth. At depth 1, dungeons are sparse;
# by depth 10 they're thick.
@export var enemy_density_base: float = 0.04
@export var enemy_density_per_depth: float = 0.015
@export var enemy_min_per_room: int = 0
@export var enemy_max_per_room: int = 3

@onready var world_root: Node3D = $World
@onready var player: CharacterBody3D = $Player
@onready var hud: Node = $HUD

var _current_depth := 1
var _mesh_library: MeshLibrary
var _grid_map: GridMap = null
var _grid: Dictionary
var _enemies: Array[Node3D] = []
var _rng := RandomNumberGenerator.new()
var _transitioning := false

func _ready() -> void:
	_rng.randomize()
	_mesh_library = MeshLibraryBuilder.build(true)
	player.died.connect(_on_player_died)
	player.hp_changed.connect(_on_player_hp)
	_current_depth = start_depth
	_build_level()

func _process(_delta: float) -> void:
	if _transitioning:
		return
	_check_stairs_down()

# --- level lifecycle ---

func _build_level() -> void:
	# Clear old level.
	if _grid_map:
		_grid_map.queue_free()
	for e in _enemies:
		if is_instance_valid(e):
			e.queue_free()
	_enemies.clear()

	# Generate.
	var gen := BrogueGen.new()
	_grid = gen.generate(base_seed + _current_depth, _current_depth)
	gen.free()

	_grid_map = GridMap.new()
	_grid_map.mesh_library = _mesh_library
	_grid_map.cell_size = Vector3(1, 1, 1)
	world_root.add_child(_grid_map)
	_populate_grid_map(_grid_map, _grid)

	# Spawn player at up-stair.
	var up: Vector2i = _grid["stairs_up"] as Vector2i
	if up.x >= 0:
		player.global_position = GridUtil.grid_to_world(up, 1.1)
		player.velocity = Vector3.ZERO
	# Reset facing so each level starts neutral.
	player.rotation.y = 0.0

	# Spawn enemies.
	_spawn_enemies()

	print("Arcade: built depth %d — %d rooms, %d enemies, up=%s down=%s" % [
		_current_depth, (_grid["rooms"] as Array).size(), _enemies.size(),
		_grid["stairs_up"], _grid["stairs_down"],
	])
	depth_changed.emit(_current_depth, total_depths)
	enemies_changed.emit(_enemies.size(), _enemies.size())

func _populate_grid_map(gm: GridMap, grid: Dictionary) -> void:
	var w: int = grid["width"]
	var h: int = grid["height"]
	var terrain: PackedByteArray = grid["terrain"]
	var liquid: PackedByteArray = grid["liquid"]
	for y in range(h):
		for x in range(w):
			var idx := y * w + x
			var t: int = terrain[idx]
			var liq: int = liquid[idx]
			if t == GridUtil.T_LIQUID and liq == GridUtil.L_CHASM:
				continue
			var tile := _tile_for(t, liq)
			if tile < 0:
				continue
			gm.set_cell_item(Vector3i(x, 0, y), tile)

func _tile_for(terrain: int, liquid: int) -> int:
	match terrain:
		GridUtil.T_FLOOR:        return MeshLibraryBuilder.TILE_FLOOR
		GridUtil.T_CORRIDOR:     return MeshLibraryBuilder.TILE_CORRIDOR
		GridUtil.T_DOOR:         return MeshLibraryBuilder.TILE_DOOR
		GridUtil.T_WALL:         return MeshLibraryBuilder.TILE_WALL
		GridUtil.T_BRIDGE:       return MeshLibraryBuilder.TILE_BRIDGE
		GridUtil.T_STAIRS_UP:    return MeshLibraryBuilder.TILE_STAIRS_UP
		GridUtil.T_STAIRS_DOWN:  return MeshLibraryBuilder.TILE_STAIRS_DOWN
		GridUtil.T_LIQUID:
			match liquid:
				GridUtil.L_WATER:     return MeshLibraryBuilder.TILE_WATER
				GridUtil.L_LAVA:      return MeshLibraryBuilder.TILE_LAVA
				GridUtil.L_BRIMSTONE: return MeshLibraryBuilder.TILE_BRIMSTONE
				_:                    return MeshLibraryBuilder.TILE_WATER
		_: return -1

# --- enemy spawning ---

func _spawn_enemies() -> void:
	var player_grid := GridUtil.world_to_grid(player.global_position)
	var rooms: Array = _grid["rooms"]
	for room in rooms:
		var cells: Array = room["cells"]
		if cells.size() < 4:
			continue
		var density := enemy_density_base + enemy_density_per_depth * (_current_depth - 1)
		var target := int(round(cells.size() * density))
		target = clamp(target, enemy_min_per_room, enemy_max_per_room)
		# Room containing the player gets at most 1 enemy, placed far from them.
		var room_has_player := false
		for c in cells:
			if c == player_grid:
				room_has_player = true
				break
		if room_has_player:
			target = min(target, 1)
		for i in range(target):
			var cell: Vector2i = cells[_rng.randi() % cells.size()]
			if room_has_player and cell.distance_to(player_grid) < 4.0:
				continue
			_spawn_enemy_at(cell, cells)

func _spawn_enemy_at(cell: Vector2i, room_cells: Array) -> void:
	var enemy := CharacterBody3D.new()
	enemy.set_script(load("res://scripts/arcade/enemy.gd"))

	# Visible body: a red box above the floor.
	var mesh := MeshInstance3D.new()
	var box := BoxMesh.new()
	box.size = Vector3(0.6, 1.4, 0.6)
	mesh.mesh = box
	var mat := StandardMaterial3D.new()
	mat.albedo_color = Color(0.80, 0.20, 0.20)
	box.material = mat
	mesh.position = Vector3(0, 0.7, 0)
	enemy.add_child(mesh)

	# Collision shape.
	var col := CollisionShape3D.new()
	var shape := CapsuleShape3D.new()
	shape.radius = 0.3
	shape.height = 1.4
	col.shape = shape
	col.position = Vector3(0, 0.7, 0)
	enemy.add_child(col)

	world_root.add_child(enemy)
	enemy.global_position = GridUtil.grid_to_world(cell, 0.1)
	enemy.grid_ref = _grid
	enemy.set_room(room_cells)
	enemy.player_ref = player
	enemy.died.connect(_on_enemy_died.bind(enemy))
	_enemies.append(enemy)

# --- transitions ---

func _check_stairs_down() -> void:
	var pg := GridUtil.world_to_grid(player.global_position)
	var w: int = _grid["width"]
	var terrain: PackedByteArray = _grid["terrain"]
	var idx := pg.y * w + pg.x
	if idx < 0 or idx >= terrain.size():
		return
	if terrain[idx] == T_STAIRS_DOWN:
		_advance_depth()

func _advance_depth() -> void:
	if _transitioning:
		return
	_transitioning = true
	_current_depth += 1
	if _current_depth > start_depth + total_depths - 1:
		game_over.emit(true)
		return
	call_deferred("_build_level")
	call_deferred("_clear_transition_flag")

func _clear_transition_flag() -> void:
	_transitioning = false

func _on_player_died() -> void:
	game_over.emit(false)

func _on_player_hp(_cur: int, _max: int) -> void:
	# HUD listens directly; no-op here.
	pass

func _on_enemy_died(enemy: Node3D) -> void:
	_enemies.erase(enemy)
	var total_spawned := _enemies.size()
	# We emit remaining = enemies.size; total isn't tracked separately for
	# this MVP. HUD shows alive count.
	enemies_changed.emit(_enemies.size(), total_spawned)

# Called by HUD restart button.
func restart_from_start() -> void:
	_transitioning = true
	_current_depth = start_depth
	player.hp = player.max_hp
	player.hp_changed.emit(player.hp, player.max_hp)
	player._alive = true
	call_deferred("_build_level")
	call_deferred("_clear_transition_flag")
