extends Node3D
## Tower Defense — Lecture 27 (Final)
##
## Main game world with enemy spawning, wave management,
## player health tracking, money system, and tower placement.
## Supports both instant-hit and projectile towers.

# ============================================================
# CONSTANTS
# ============================================================

const PLAYER_START_HP := 100

# Enemy types and wave definitions
const WAVE_1 := ["slime", "slime", "slime"]
const WAVE_2 := ["slime", "slime", "goblin", "slime"]
const WAVE_3 := ["goblin", "goblin", "slime", "goblin", "slime"]
const WAVE_4 := ["goblin", "goblin", "goblin", "orc"]
const WAVE_5 := ["orc", "goblin", "goblin", "orc", "slime"]

const SPAWN_DELAY := 1.0  # Seconds between each enemy in a wave
const WAVE_DELAY := 5.0   # Seconds between waves

# Money and rewards
const START_MONEY := 500
const KILL_REWARD_SLIME := 25
const KILL_REWARD_GOBLIN := 40
const KILL_REWARD_ORC := 75

# ============================================================
# STATE
# ============================================================

var player_hp: int = PLAYER_START_HP
var current_wave: int = 0
var enemies_remaining_in_wave: int = 0
var is_spawning: bool = false
var money: int = START_MONEY

# Tower placement
var selected_tower_type: int = 0  # 0 = instant, 1 = projectile
var preview_tower: Node3D = null
var hover_cell_indicator: MeshInstance3D = null
var placed_towers: Array[Node3D] = []
var occupied_cells: Array[Vector2i] = []

# Wave list
var waves: Array[Array] = [WAVE_1, WAVE_2, WAVE_3, WAVE_4, WAVE_5]

# ============================================================
# NODES
# ============================================================

@onready var hp_label: Label = $UI/HPLabel
@onready var wave_label: Label = $UI/WaveLabel
@onready var money_label: Label = $UI/MoneyLabel
@onready var tower_info_label: Label = $UI/TowerInfoLabel
@onready var spawn_timer: Timer = $SpawnTimer
@onready var wave_timer: Timer = $WaveTimer
@onready var enemy_path: Path3D = $EnemyPath
@onready var camera: Camera3D = $Camera3D

# Enemy and tower scene preload
var enemy_scene := preload("res://scenes/enemy.tscn")
var tower_instant_scene := preload("res://scenes/tower_instant.tscn")
var tower_projectile_scene := preload("res://scenes/tower_projectile.tscn")

# ============================================================
# LIFECYCLE
# ============================================================

func _ready() -> void:
	_create_hover_cell_indicator()
	_update_ui()
	_start_next_wave()


func _process(_delta: float) -> void:
	_update_tower_preview()


func _input(event: InputEvent) -> void:
	# Tower type selection
	if event.is_action_pressed("ui_text_backspace"):  # Key 1
		selected_tower_type = 0
		_destroy_preview_tower()
		_update_ui()
	elif event is InputEventKey and event.pressed and event.keycode == KEY_2:
		selected_tower_type = 1
		_destroy_preview_tower()
		_update_ui()
	
	# Place tower
	if event.is_action_pressed("left_click"):
		_try_place_tower()


# ============================================================
# WAVE MANAGEMENT
# ============================================================

## Starts the next wave if available
func _start_next_wave() -> void:
	if current_wave >= waves.size():
		_game_won()
		return
	
	current_wave += 1
	var wave_enemies := waves[current_wave - 1]
	enemies_remaining_in_wave = wave_enemies.size()
	
	_update_ui()
	
	# Start spawning enemies
	is_spawning = true
	_spawn_next_enemy_in_wave(wave_enemies, 0)


## Recursively spawns enemies with delay
func _spawn_next_enemy_in_wave(wave_enemies: Array, index: int) -> void:
	if index >= wave_enemies.size():
		is_spawning = false
		return
	
	_spawn_enemy(wave_enemies[index])
	
	# Schedule next enemy
	await get_tree().create_timer(SPAWN_DELAY).timeout
	_spawn_next_enemy_in_wave(wave_enemies, index + 1)


## Spawns a single enemy of the given type
func _spawn_enemy(enemy_type: String) -> void:
	var enemy := enemy_scene.instantiate()
	enemy_path.add_child(enemy)
	
	# Set enemy type
	enemy.enemy_type = enemy_type
	enemy.initialize()
	
	# Connect signals
	enemy.reached_end.connect(_on_enemy_reached_end)
	enemy.died.connect(_on_enemy_died)


# ============================================================
# ENEMY CALLBACKS
# ============================================================

## Called when enemy reaches end of path
func _on_enemy_reached_end(damage: int) -> void:
	player_hp -= damage
	player_hp = maxi(player_hp, 0)
	_update_ui()
	
	enemies_remaining_in_wave -= 1
	_check_wave_complete()
	
	if player_hp <= 0:
		_game_over()


## Called when enemy is killed by tower
func _on_enemy_died(enemy_type: String) -> void:
	# Award money based on enemy type
	var reward := 0
	match enemy_type:
		"slime":
			reward = KILL_REWARD_SLIME
		"goblin":
			reward = KILL_REWARD_GOBLIN
		"orc":
			reward = KILL_REWARD_ORC
	
	money += reward
	_update_ui()
	
	enemies_remaining_in_wave -= 1
	_check_wave_complete()


## Checks if wave is complete and starts next wave
func _check_wave_complete() -> void:
	if enemies_remaining_in_wave <= 0 and not is_spawning:
		# Wait before next wave
		wave_timer.start(WAVE_DELAY)
		await wave_timer.timeout
		_start_next_wave()


# ============================================================
# UI
# ============================================================

func _update_ui() -> void:
	hp_label.text = "HP: %d" % player_hp
	wave_label.text = "Wave: %d / %d" % [current_wave, waves.size()]
	money_label.text = "Money: $%d" % money
	
	# Tower info
	var tower_name := "Instant Tower" if selected_tower_type == 0 else "Projectile Tower"
	var tower_cost := 100 if selected_tower_type == 0 else 150
	tower_info_label.text = "[1] %s ($%d) | [2] Projectile ($150)" % [tower_name, tower_cost]


# ============================================================
# GAME OVER / WIN
# ============================================================

func _game_over() -> void:
	print("Game Over!")
	get_tree().paused = true
	# TODO: Show game over screen in Lecture 28


func _game_won() -> void:
	print("Victory! All waves defeated!")
	get_tree().paused = true
	# TODO: Show victory screen in Lecture 28


# ============================================================
# TOWER PLACEMENT
# ============================================================

## Updates tower preview position
func _update_tower_preview() -> void:
	var mouse_pos := get_viewport().get_mouse_position()
	var from := camera.project_ray_origin(mouse_pos)
	var to := from + camera.project_ray_normal(mouse_pos) * 1000.0
	
	var space_state := get_world_3d().direct_space_state
	var query := PhysicsRayQueryParameters3D.create(from, to)
	var result: Dictionary = space_state.intersect_ray(query)
	
	if result:
		# Snap to grid
		var world_pos: Vector3 = result.position
		var snapped_pos := _snap_to_grid(world_pos)
		
		# Check if cell is valid for placement
		var cell := _world_to_cell(snapped_pos)
		var is_occupied := cell in occupied_cells
		var is_on_road: bool = enemy_path.is_cell_on_road(cell) if enemy_path else false
		var is_valid := not is_occupied and not is_on_road
		
		# Update hover cell indicator
		_update_hover_cell(snapped_pos, is_valid)
		
		# Create or update preview
		if not preview_tower:
			_create_preview_tower()
		
		if preview_tower:
			preview_tower.global_position = snapped_pos
			
			# Color preview based on validity
			_update_preview_color(is_valid)
	else:
		# Hide hover cell when not over ground
		if hover_cell_indicator:
			hover_cell_indicator.visible = false


## Creates preview tower
func _create_preview_tower() -> void:
	var scene := tower_instant_scene if selected_tower_type == 0 else tower_projectile_scene
	preview_tower = scene.instantiate()
	add_child(preview_tower)
	preview_tower.is_preview = true  # Disable AI for preview
	preview_tower.show_range()


## Destroys the preview tower
func _destroy_preview_tower() -> void:
	if preview_tower:
		preview_tower.queue_free()
		preview_tower = null


## Updates preview tower color
func _update_preview_color(is_valid: bool) -> void:
	if not preview_tower:
		return
	
	var mesh := preview_tower.get_node("MeshInstance3D") as MeshInstance3D
	var material := StandardMaterial3D.new()
	material.albedo_color = Color.GREEN if is_valid else Color.RED
	material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
	material.albedo_color.a = 0.5
	mesh.set_surface_override_material(0, material)


## Tries to place tower at current preview position
func _try_place_tower() -> void:
	if not preview_tower:
		return
	
	var pos := preview_tower.global_position
	var cell := _world_to_cell(pos)
	
	# Check if cell is occupied by tower
	if cell in occupied_cells:
		print("Cell already occupied!")
		return
	
	# Check if cell is on road
	if enemy_path and enemy_path.is_cell_on_road(cell):
		print("Cannot place tower on road!")
		return
	
	# Check if enough money
	var cost := 100 if selected_tower_type == 0 else 150
	if money < cost:
		print("Not enough money!")
		return
	
	# Place tower
	money -= cost
	occupied_cells.append(cell)
	
	var scene := tower_instant_scene if selected_tower_type == 0 else tower_projectile_scene
	var tower := scene.instantiate()
	add_child(tower)
	tower.global_position = pos
	placed_towers.append(tower)
	
	_update_ui()
	print("Tower placed!")


## Snaps world position to grid
func _snap_to_grid(pos: Vector3) -> Vector3:
	const GRID_SIZE := 2.0
	return Vector3(
		roundi(pos.x / GRID_SIZE) * GRID_SIZE,
		0.0,
		roundi(pos.z / GRID_SIZE) * GRID_SIZE
	)


## Converts world position to grid cell
func _world_to_cell(pos: Vector3) -> Vector2i:
	const GRID_SIZE := 2.0
	return Vector2i(
		roundi(pos.x / GRID_SIZE),
		roundi(pos.z / GRID_SIZE)
	)


# ============================================================
# HOVER CELL INDICATOR
# ============================================================

## Creates the hover cell indicator mesh
func _create_hover_cell_indicator() -> void:
	const GRID_SIZE := 2.0
	
	hover_cell_indicator = MeshInstance3D.new()
	var mesh := PlaneMesh.new()
	mesh.size = Vector2(GRID_SIZE, GRID_SIZE)
	hover_cell_indicator.mesh = mesh
	
	var material := StandardMaterial3D.new()
	material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
	material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
	material.cull_mode = BaseMaterial3D.CULL_DISABLED  # Visible from both sides
	hover_cell_indicator.material_override = material
	
	hover_cell_indicator.rotation.x = -PI / 2  # Rotate to lie flat
	hover_cell_indicator.position.y = 0.02  # Slightly above ground
	hover_cell_indicator.visible = false
	
	add_child(hover_cell_indicator)


## Updates hover cell color based on validity
func _update_hover_cell(snapped_pos: Vector3, is_valid: bool) -> void:
	if not hover_cell_indicator:
		return
	
	hover_cell_indicator.visible = true
	hover_cell_indicator.global_position = snapped_pos
	hover_cell_indicator.global_position.y = 0.02
	
	var material := hover_cell_indicator.material_override as StandardMaterial3D
	material.albedo_color = Color(0.2, 1.0, 0.2, 0.3) if is_valid else Color(1.0, 0.2, 0.2, 0.3)
