class_name PlayerController extends Node

const SPEED = 192.0

@export var dl_map: CombatMap

signal combat_requested(attacker: DeployedUnit, defender: DeployedUnit)
signal mouse_grid_changed(coords: Vector2i)
signal camera_drag(delta: Vector2)

var input_disabled := false

var _selected_unit: DeployedUnit = null
var _target_pos: Vector2
var _goal_pos: Vector2
var _moving := false

var _left_pending := false
var _drag_start := Vector2.ZERO
var _dragging := false
var _current_grid_coords := Vector2i(-99999, -99999)

const DRAG_THRESHOLD := 8.0


func _ready() -> void:
	for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
		deployed.unit_died.connect(_on_unit_died)
	get_tree().node_added.connect(_on_node_added)


func _on_node_added(node: Node) -> void:
	if node is DeployedUnit and node.is_in_group("deployed_units"):
		if not node.unit_died.is_connected(_on_unit_died):
			node.unit_died.connect(_on_unit_died)


func _on_unit_died(deployed: DeployedUnit) -> void:
	if _selected_unit == deployed:
		_selected_unit = null
		_moving = false


func _process(_delta: float) -> void:
	if input_disabled:
		return
	var mouse_pos := get_viewport().get_canvas_transform().affine_inverse() * get_viewport().get_mouse_position()
	var coords := BattleMapHelper.world_to_coords(mouse_pos)
	if coords != _current_grid_coords:
		_current_grid_coords = coords
		mouse_grid_changed.emit(coords)


func _unhandled_input(event: InputEvent) -> void:
	if input_disabled:
		return

	if event is InputEventMouseButton:
		match event.button_index:
			MOUSE_BUTTON_LEFT:
				if event.pressed:
					_left_pending = true
					_drag_start = event.position
				else:
					if _dragging:
						_dragging = false
						_left_pending = false
						Input.set_default_cursor_shape(Input.CURSOR_ARROW)
						get_viewport().set_input_as_handled()
					else:
						_left_pending = false
						_handle_left_click(event.position)
			MOUSE_BUTTON_MIDDLE:
				if event.pressed:
					_dragging = true
					_drag_start = event.position
					Input.set_default_cursor_shape(Input.CURSOR_DRAG)
				else:
					_dragging = false
					Input.set_default_cursor_shape(Input.CURSOR_ARROW)
				get_viewport().set_input_as_handled()

	elif event is InputEventMouseMotion:
		if _left_pending and not _dragging:
			if event.position.distance_to(_drag_start) >= DRAG_THRESHOLD:
				_dragging = true
				_left_pending = false
				Input.set_default_cursor_shape(Input.CURSOR_DRAG)
		if _dragging:
			var delta: Vector2 = _drag_start - event.position
			_drag_start = event.position
			camera_drag.emit(delta)
			get_viewport().set_input_as_handled()


func _physics_process(delta: float) -> void:
	if not _selected_unit or not _selected_unit.is_alive():
		return

	if _moving:
		var remaining := _target_pos - _selected_unit.position
		var step := SPEED * delta

		if remaining.length() <= step:
			_selected_unit.position = _target_pos
			_moving = false
		else:
			_selected_unit.position += remaining.normalized() * step
		return

	if _selected_unit.position != _goal_pos:
		var diff := _goal_pos - _selected_unit.position
		var dir: Vector2
		if absf(diff.x) >= absf(diff.y):
			dir = Vector2(signf(diff.x), 0)
		else:
			dir = Vector2(0, signf(diff.y))

		var next_pos := _selected_unit.position + dir * BattleMapHelper.TILE_SIZE
		var grid_coords := BattleMapHelper.world_to_coords(next_pos)
		var current_coords := BattleMapHelper.world_to_coords(_selected_unit.position)
		if not dl_map.is_tile_passable(current_coords, grid_coords):
			_goal_pos = _selected_unit.position
			return

		_target_pos = next_pos
		_moving = true


func _handle_left_click(screen_pos: Vector2) -> void:
	var world_pos: Vector2 = get_viewport().get_canvas_transform().affine_inverse() * screen_pos
	var clicked_unit := _get_unit_at(world_pos)

	if clicked_unit:
		if _selected_unit and clicked_unit != _selected_unit and _selected_unit.is_alive() and clicked_unit.is_alive():
			combat_requested.emit(_selected_unit, clicked_unit)
		else:
			_select_unit(clicked_unit)
		get_viewport().set_input_as_handled()
	elif _selected_unit:
		var snapped_pos := BattleMapHelper.snap_to_grid(world_pos)
		var grid_coords := BattleMapHelper.world_to_coords(world_pos)
		if not dl_map.is_tile_valid(grid_coords):
			return
		_goal_pos = snapped_pos
		get_viewport().set_input_as_handled()


func _select_unit(deployed: DeployedUnit) -> void:
	if _selected_unit:
		_selected_unit.set_selected(false)
	_selected_unit = deployed
	_selected_unit.set_selected(true)
	_goal_pos = _selected_unit.position
	_target_pos = _selected_unit.position
	_moving = false


func _get_unit_at(world_pos: Vector2) -> DeployedUnit:
	var snapped_coords := BattleMapHelper.snap_to_grid(world_pos)
	for deployed: DeployedUnit in get_tree().get_nodes_in_group("deployed_units"):
		if not deployed.is_alive():
			continue
		var unit_snapped := BattleMapHelper.snap_to_grid(deployed.global_position)
		if unit_snapped == snapped_coords:
			return deployed
	return null
