extends RigidBody3D

class_name Dart

#states
var in_flight: bool = false
var dragging: bool = false

#drag
var drag_plane: Plane
var drag_offset: Vector3 = Vector3.ZERO
var target_pos: Vector3 = Vector3.ZERO

# velocity tracking for release throw
var position_history: Array[Vector3] = []
var time_history: Array[float] = []
const HISTORY_LENGTH = 6

# drag "stiffness" tuning
@export var drag_follow_speed: float = 20.0   # how fast it chases the mouse
@export var max_drag_speed: float = 42.0      # clamp so it doesn't go insane
@export_range(-180,180) var angle_of_plane: float = 15.0
@export var throw_force_multiplier: float = 0.8   # scale raw throw velocity down if it's too fast
@export var max_throw_speed: float = 42.0         # hard cap so fast flicks don't kill the arc
@export var flight_gravity_scale: float = 4.0     # higher = more visible arc
@export var nose_align_speed: float = 1.0         # how fast dart rotates to face velocity
@export var dart_forward_axis: Vector3 = Vector3.FORWARD

func _ready():
	freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
	freeze = true
	
func start_drag(camera: Camera3D, mouse_pos: Vector2, hit_point: Vector3):
	dragging = true
	in_flight = false
	freeze = false

	# keep it physics-active — don't freeze
	gravity_scale = 0.0          # optional: ignore gravity while held so it doesn't sag
	linear_damp = 5.0            # optional: a bit of damping for stability while dragged
	angular_damp = 10.0
	
	#drag_plane = Plane(camera.global_transform.basis.z, hit_point)
	var base_normal = camera.global_transform.basis.z
	var tilt = base_normal.rotated(camera.global_transform.basis.x, deg_to_rad(angle_of_plane))  # tilt adjustable around camera's right axis
	drag_plane = Plane(tilt.normalized(), hit_point)
	drag_offset = global_position - hit_point
	
	target_pos = global_position #fixes a bug where dart drops until mouse is moved.
	
	position_history.clear()
	time_history.clear()

func update_drag(camera: Camera3D, mouse_pos: Vector2):
	if not dragging:
		return

	var ray_origin = camera.project_ray_origin(mouse_pos)
	var ray_dir = camera.project_ray_normal(mouse_pos)

	var intersection = drag_plane.intersects_ray(ray_origin, ray_dir)
	if intersection:
		target_pos = intersection + drag_offset

func _align_dart_to_velocity(delta: float):
	if linear_velocity.length() < 0.5:
		return

	var vel_dir = linear_velocity.normalized()
	var current_forward = global_transform.basis * dart_forward_axis
	var rotation_axis = current_forward.cross(vel_dir)

	if rotation_axis.length() < 0.001:
		return

	rotation_axis = rotation_axis.normalized()
	var angle = current_forward.angle_to(vel_dir)
	var rotate_amount = min(angle, nose_align_speed * delta)

	global_transform.basis = global_transform.basis.rotated(rotation_axis, rotate_amount)
	global_transform.basis = global_transform.basis.orthonormalized()

func _physics_process(delta):
	if dragging:
		# chase the target position with velocity instead of teleporting
		var to_target = target_pos - global_position
		var desired_velocity = to_target * drag_follow_speed

		if desired_velocity.length() > max_drag_speed:
			desired_velocity = desired_velocity.normalized() * max_drag_speed

		linear_velocity = desired_velocity
		angular_velocity = angular_velocity.lerp(Vector3.ZERO, 0.2)  # optional: damp spin while dragging

		# track actual position for momentum calc on release
		position_history.append(global_position)
		time_history.append(Time.get_ticks_msec() / 1000.0)
		if position_history.size() > HISTORY_LENGTH:
			position_history.pop_front()
			time_history.pop_front()
	elif in_flight:
		# Debug: confirm Y velocity is dropping (becoming negative) each frame
		print("local +X world dir: ", global_transform.basis.x)
		print("local +Y world dir: ", global_transform.basis.y)
		print("local +Z world dir: ", global_transform.basis.z)
		print("velocity dir: ", linear_velocity.normalized())
	
		# Align dart nose to face velocity direction (tip points the way it's flying)
		if linear_velocity.length() > 0.5:
			_align_dart_to_velocity(delta)
		else:
			in_flight = false
			angular_velocity = Vector3.ZERO
			# If your dart tip points along +Z, use global_position + linear_velocity
			# If it points along -Z, negate the direction below
			var look_target = global_position + linear_velocity.normalized()
			var up_ref = Vector3.UP

			# Avoid gimbal lock when flying straight up/down
			if abs(linear_velocity.normalized().dot(Vector3.UP)) > 0.99:
				up_ref = Vector3.FORWARD

			var target_basis = Basis.looking_at(linear_velocity.normalized(), up_ref)
			global_transform.basis = global_transform.basis.slerp(
				target_basis, nose_align_speed * delta
			)

func release_drag():
	if not dragging:
		return
	dragging = false
	in_flight = true

	gravity_scale = flight_gravity_scale
	angular_damp = 0.0
	linear_damp = 0.0  # reset to whatever your normal dart damping is

	var velocity = calculate_throw_velocity() * throw_force_multiplier
	if velocity.length() > max_throw_speed:
		velocity = velocity.normalized() * max_throw_speed
	linear_velocity = velocity
	# angular_velocity left as-is from physics, or compute your own spin here
	
	print("throw speed: %.2f" % velocity.length())
	print("gravity_scale: ", gravity_scale)
	print("linear_damp: ", linear_damp)

func calculate_throw_velocity() -> Vector3:
	if position_history.size() < 2:
		return linear_velocity  # fallback to whatever physics already had

	var start_pos = position_history[0]
	var end_pos = position_history[position_history.size() - 1]
	var start_time = time_history[0]
	var end_time = time_history[time_history.size() - 1]

	var dt = end_time - start_time
	if dt <= 0.0:
		return linear_velocity

	return (end_pos - start_pos) / dt
