extends Node3D
## Tower Defense — Lecture 27 (Final)
##
## Base class for towers. Handles state machine (IDLE, TARGETING, ATTACKING),
## enemy detection, and placement. Child classes implement specific attack behavior.

# ============================================================
# SIGNALS
# ============================================================

signal tower_placed()

# ============================================================
# ENUMS
# ============================================================

enum State { IDLE, TARGETING, ATTACKING }

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

const GRID_SIZE := 2.0  # 2x2 meter grid cells

# Tower stats (override in child classes)
var tower_cost: int = 100
var tower_damage: int = 10
var tower_range: float = 8.0
var attack_cooldown: float = 1.0

# Colors
const COLOR_IDLE := Color(0.2, 0.4, 0.8)  # Blue
const COLOR_TARGETING := Color(0.8, 0.6, 0.2)  # Orange
const COLOR_ATTACKING := Color(0.8, 0.2, 0.2)  # Red

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

var current_state: State = State.IDLE
var current_target: PathFollow3D = null
var time_since_last_attack: float = 0.0
var is_preview: bool = false  # Set to true for preview towers (disables AI)

@onready var mesh_instance: MeshInstance3D = $MeshInstance3D
@onready var range_indicator: MeshInstance3D = $RangeIndicator
@onready var detection_area: Area3D = $DetectionArea
@onready var collision_shape: CollisionShape3D = $DetectionArea/CollisionShape3D

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

func _ready() -> void:
	_setup_detection_range()
	_update_color()
	range_indicator.visible = false


func _process(delta: float) -> void:
	# Skip AI if this is a preview tower
	if is_preview:
		return
	
	time_since_last_attack += delta
	
	match current_state:
		State.IDLE:
			_state_idle()
		State.TARGETING:
			_state_targeting()
		State.ATTACKING:
			_state_attacking()


# ============================================================
# STATE MACHINE
# ============================================================

func _state_idle() -> void:
	_find_target()
	if current_target:
		_change_state(State.TARGETING)


func _state_targeting() -> void:
	if not _is_target_valid():
		current_target = null
		_change_state(State.IDLE)
		return
	
	# Rotate to face target
	look_at(current_target.global_position, Vector3.UP)
	
	# Check if can attack
	if time_since_last_attack >= attack_cooldown:
		_change_state(State.ATTACKING)


func _state_attacking() -> void:
	if not _is_target_valid():
		current_target = null
		_change_state(State.IDLE)
		return
	
	# Attack (implemented in child classes)
	_perform_attack()
	time_since_last_attack = 0.0
	
	# Return to targeting
	_change_state(State.TARGETING)


func _change_state(new_state: State) -> void:
	current_state = new_state
	_update_color()


# ============================================================
# TARGETING
# ============================================================

func _find_target() -> void:
	var areas = detection_area.get_overlapping_areas()
	if areas.is_empty():
		return
	
	# Find closest enemy
	var closest_enemy: PathFollow3D = null
	var closest_distance := INF
	
	for area in areas:
		# Area3D's parent should be the PathFollow3D (Enemy)
		var enemy = area.get_parent()
		if enemy is PathFollow3D and enemy.has_method("take_damage"):
			var distance := global_position.distance_to(enemy.global_position)
			if distance < closest_distance:
				closest_distance = distance
				closest_enemy = enemy
	
	current_target = closest_enemy


func _is_target_valid() -> bool:
	if not current_target or not is_instance_valid(current_target):
		return false
	
	var distance := global_position.distance_to(current_target.global_position)
	return distance <= tower_range


# ============================================================
# ATTACK (override in child classes)
# ============================================================

func _perform_attack() -> void:
	# Override in TowerInstant and TowerProjectile
	pass


# ============================================================
# SETUP
# ============================================================

func _setup_detection_range() -> void:
	# Set collision shape radius to match tower range
	var shape := SphereShape3D.new()
	shape.radius = tower_range
	collision_shape.shape = shape
	
	# Set range indicator size (radius = tower_range)
	range_indicator.scale = Vector3(tower_range, 1.0, tower_range)


func _update_color() -> void:
	var color: Color
	match current_state:
		State.IDLE:
			color = COLOR_IDLE
		State.TARGETING:
			color = COLOR_TARGETING
		State.ATTACKING:
			color = COLOR_ATTACKING
	
	var material := StandardMaterial3D.new()
	material.albedo_color = color
	mesh_instance.set_surface_override_material(0, material)


# ============================================================
# PLACEMENT
# ============================================================

## Snap position to grid
static func snap_to_grid(pos: Vector3) -> Vector3:
	return Vector3(
		roundi(pos.x / GRID_SIZE) * GRID_SIZE,
		0.0,
		roundi(pos.z / GRID_SIZE) * GRID_SIZE
	)


## Show range indicator
func show_range() -> void:
	range_indicator.visible = true


## Hide range indicator
func hide_range() -> void:
	range_indicator.visible = false
