@tool
extends Function
class_name Movable

signal on_move(position: Vector3, rotation: Vector3)
signal on_moved()

@export var restricted: bool = false
@export var resizable: bool = false
@export var lock_rotation: bool = false
@export var restrict_movement: Callable
@export var disabled: bool = false

var initiator = null
var initiator2 = null
var resizing = false

var relative_transform = Transform3D()
var initial_point = Vector3()
var initial_rotation = Vector3()

# For Scaling
var initial_position = Vector3()
var initial_direction = Vector3()
var initial_up = Vector3()
var initial_transform = Transform3D()
var distances = Vector2()

func _process(delta):
	if get_tree().debug_collisions_hint&&resizing:
		DebugDraw3D.draw_line(initial_position, initial_position + initial_direction, Color(1, 0, 0))
		DebugDraw3D.draw_line(initial_position, initial_position + initial_up, Color(0, 1, 0))

func _on_grab_down(event: EventPointer):
	if disabled:
		return

	if restricted&&event.target != get_parent():
		return

	if resizing&&initiator2 != null:
		return

	if resizable&&initiator != null:
		initiator2 = event.initiator
		resizing = true

		distances.y = event.ray.get_collision_point().distance_to(event.ray.global_position)

		initial_position = _get_first_ray_point()
		initial_direction = _get_second_ray_point() - initial_position
		initial_up = -initiator.node.global_transform.basis.z.normalized() * distances.x
		initial_transform = get_parent().global_transform

		return

	if resizable:
		distances.x = event.ray.get_collision_point().distance_to(event.ray.global_position)

	initiator = event.initiator

	_update_relative_transform()

	if lock_rotation:
		initial_point = get_parent().to_local(event.ray.get_collision_point())
		initial_rotation = get_parent().global_rotation

func _on_grab_move(event: EventPointer):
	if event.initiator != initiator:
		return

	if resizing:
		var new_position = _get_first_ray_point()
		var new_direction = _get_second_ray_point() - new_position
		var new_up = -initiator.node.global_transform.basis.z.normalized() * distances.x

		if get_tree().debug_collisions_hint:
			DebugDraw3D.draw_line(new_position, new_position + new_direction, Color(1, 0, 0))
			DebugDraw3D.draw_line(new_position, new_position + new_up, Color(0, 1, 0))

		get_parent().global_transform = TransformTools.calc_delta_transform(initial_position, initial_direction, initial_up, new_position, new_direction, new_up) * initial_transform
		return

	get_parent().global_transform = initiator.node.global_transform * relative_transform

	if lock_rotation:
		get_parent().global_transform = TransformTools.rotate_around_point(get_parent().global_transform, get_parent().to_global(initial_point), initial_rotation - get_parent().global_rotation)

func _on_grab_up(event: EventPointer):
	if event.initiator == initiator2:
		initiator2 = null
		resizing = false
		_update_relative_transform()
		return

	if event.initiator == initiator:
		if initiator2 != null:
			initiator = initiator2
			initiator2 = null
			resizing = false
			_update_relative_transform()
		else:
			initiator = null
			initiator2 = null
			resizing = false
			on_moved.emit()

func _get_first_ray_point():
	if initiator == null:
		return Vector3()

	return initiator.node.global_position - initiator.node.global_transform.basis.z.normalized() * distances.x

func _get_second_ray_point():
	if initiator2 == null:
		return Vector3()

	return initiator2.node.global_position - initiator2.node.global_transform.basis.z.normalized() * distances.y

func _update_relative_transform():
	relative_transform = initiator.node.global_transform.affine_inverse() * get_parent().global_transform

func _get_configuration_warnings() -> PackedStringArray:
	var warnings := PackedStringArray()

	if get_parent() is StaticBody3D == false:
		warnings.append("Movable requires a StaticBody3D as parent.")

	return warnings