class_name ShotSetup extends Node3D

enum Phase {
	AIM,
	POWER_ADJUST,
	CURVE_ADJUST,
	SHOT,
	FINISHED,
}

const PITCH_MIN := deg_to_rad(-60.0)
const PITCH_MAX := deg_to_rad(-5.0)
const ZOOM_LENGTH := 0.1
const ZOOM_ELEVATION_RATIO := 0.3166

const ZOOM_MIN := 1.0
const ZOOM_MAX := 12.0

const ARROW_ACCELERATION := 8.0

const FREE_CAM_RETURN_TIME := 0.618
const BALL_RETURN_TIME := 0.618
const CAMERA_SNAP_TIME := 0.3

## In Driving Range mode, the ball can be retrieved in the shot phase.
@export var driving_range := false

@export_category("Shot Parameters")
@export var base_power := 1.0
@export var base_curve := 0.0

var base_speed: float = ProjectSettings.get_setting("game/config/controls/camera/free_camera_speed")

var x_sensitivity: float = ProjectSettings.get_setting(
	"game/config/controls/camera/x_axis_sensitivity"
)
var x_acceleration: float = ProjectSettings.get_setting(
	"game/config/controls/camera/x_axis_acceleration"
)
var y_sensitivity: float = ProjectSettings.get_setting(
	"game/config/controls/camera/y_axis_sensitivity"
)
var y_acceleration: float = ProjectSettings.get_setting(
	"game/config/controls/camera/y_axis_acceleration"
)

var invert_pitch: bool = ProjectSettings.get_setting("game/config/controls/camera/invert_pitch")

var control_disabled := false

var phase: Phase:
	set(value):
		if value != phase:
			_on_phase_change(value)
		phase = value

var _free_camera: FreeCamera
var _returning_free_camera := false

@onready var direction: Node3D = %Direction
@onready var elevation: Node3D = %Elevation
@onready var pitch: Node3D = %Pitch
@onready var zoom: Node3D = %Zoom
@onready var camera: Camera3D = %Camera
@onready var tracking_camera: TrackingCamera = %TrackingCamera

@onready var shot_animation: AnimationPlayer = %ShotAnimation

@onready var arrow: Node3D = %Arrow
@onready var arrow_animation: AnimationPlayer = %ArrowAnimation

@onready var power_bar: ProgressBar = %PowerBar
@onready var power_animation: AnimationPlayer = %PowerAnimation

@onready var curve_bar: ProgressBar = %CurveBar
@onready var curve_animation: AnimationPlayer = %CurveAnimation

@onready var ball_point: Node3D = %BallPoint
@onready var physics_ball: RigidBody3D = %PhysicsBall

@onready var drive_ref: RayCast3D = %DriveRef

@onready var nice_animation: AnimationPlayer = %NiceAnimation

@onready var camera_distance := camera.position.z:
	set = _set_camera_distance

@onready var phys_ball_scene := preload("res://src/player/physics_ball/physics_ball.tscn")

@onready var _target_rotation := Vector2(pitch.rotation.x, direction.rotation.y)


func _set_camera_distance(value: float) -> void:
	var tween := get_tree().create_tween()
	tween.tween_property(zoom, "position:z", value, ZOOM_LENGTH).set_trans(Tween.TRANS_SINE)
	tween.set_parallel()
	(
		tween
		. tween_property(elevation, "position:y", value * ZOOM_ELEVATION_RATIO, ZOOM_LENGTH)
		. set_trans(Tween.TRANS_SINE)
	)
	camera_distance = value


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	elif event is InputEventMouseMotion:
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
			camera_motion((event as InputEventMouseMotion).relative)


func camera_motion(motion: Vector2) -> void:
	if not control_disabled and phase == Phase.AIM:
		# Can only control camera while aiming
		_target_rotation.y = _target_rotation.y - deg_to_rad(motion.x * x_sensitivity)
		_target_rotation.x = clampf(
			_target_rotation.x - deg_to_rad(motion.y * y_sensitivity) * (-1 if invert_pitch else 1),
			PITCH_MIN,
			PITCH_MAX
		)


func take_shot() -> void:
	print("WHACK!")
	print("Power: ", power_bar.value)
	print("Curve: ", curve_bar.value)

	var impulse := drive_ref.global_basis * Vector3.ONE * base_power
	print("Shot impulse: ", impulse)

	physics_ball.freeze = false
	physics_ball.apply_central_impulse(impulse.rotated(Vector3.UP, -PI / 2.0))


func _on_phase_change(new_phase: Phase) -> void:
	match new_phase:
		Phase.AIM:
			camera.make_current()
			power_bar.hide()
			curve_bar.hide()
		Phase.POWER_ADJUST:
			curve_bar.hide()

			power_bar.show()
			power_animation.stop()  # Reset if needed
		Phase.CURVE_ADJUST:
			curve_bar.show()
			curve_animation.play("fill")
		Phase.SHOT:
			power_bar.hide()
			curve_bar.hide()

			nice_animation.play("display")

			if not driving_range:
				shot_animation.play("shoot")
			arrow_animation.play("hide")
			take_shot()


func insert_free_cam() -> void:
	arrow_animation.play("hide")
	_free_camera = FreeCamera.create(camera)
	add_sibling(_free_camera)
	control_disabled = true
	camera.current = false


func return_free_cam() -> void:
	# TODO alter shot aim based on free camera selection
	arrow_animation.play("show")
	_free_camera.queue_free()
	_free_camera = null
	control_disabled = false
	_returning_free_camera = false
	camera.current = true


func return_ball() -> void:
	var tween := get_tree().create_tween()

	physics_ball.freeze = true
	(
		tween
		. tween_property(
			physics_ball,
			"global_transform",
			ball_point.global_transform,
			BALL_RETURN_TIME,
		)
		. set_trans(Tween.TRANS_SINE)
	)


func start_shot_track() -> void:
	tracking_camera.make_current()
	tracking_camera.active = true


func end_shot_track() -> void:
	camera.make_current()
	tracking_camera.active = false


func _process(delta: float) -> void:
	# Rotation
	direction.rotation.y = lerp_angle(
		direction.rotation.y, _target_rotation.y, delta * x_acceleration
	)
	pitch.rotation.x = lerp_angle(pitch.rotation.x, _target_rotation.x, delta * y_acceleration)

	arrow.rotation.y = lerp_angle(arrow.rotation.y, _target_rotation.y, delta * ARROW_ACCELERATION)

	# Input Handling
	if control_disabled:
		if Input.is_action_just_pressed("camera_cancel"):
			if is_instance_valid(_free_camera) and not _returning_free_camera:
				_returning_free_camera = true
				var tween := get_tree().create_tween()
				(
					tween
					. tween_property(
						_free_camera,
						"global_transform",
						camera.global_transform,
						FREE_CAM_RETURN_TIME
					)
					. set_trans(Tween.TRANS_SINE)
				)
				tween.tween_callback(return_free_cam)
		return

	match phase:
		Phase.AIM:
			# Camera zoom
			if Input.is_action_just_pressed("shot_zoom_in"):
				camera_distance = max(camera_distance - 1.0, ZOOM_MIN)
			if Input.is_action_just_pressed("shot_zoom_out"):
				camera_distance = min(camera_distance + 1.0, ZOOM_MAX)

			# Switch to free cam
			if (
				Input.is_action_just_pressed("camera_back")
				or Input.is_action_just_pressed("camera_forward")
				or Input.is_action_just_pressed("camera_left")
				or Input.is_action_just_pressed("camera_right")
			):
				insert_free_cam()

			# Advance to next phase
			if Input.is_action_just_pressed("shot_accept"):
				phase = Phase.POWER_ADJUST
		Phase.POWER_ADJUST:
			if Input.is_action_just_pressed("shot_accept"):
				# TODO set power gauge parameters if needed
				power_animation.play("fill")
			if Input.is_action_just_released("shot_accept") and power_bar.value > 0:
				power_animation.pause()
				phase = Phase.CURVE_ADJUST
		Phase.CURVE_ADJUST:
			if Input.is_action_just_pressed("shot_accept"):
				curve_animation.pause()
				phase = Phase.SHOT
		Phase.SHOT:
			# REMOVEME
			if Input.is_action_just_pressed("camera_forward"):
				insert_free_cam()
			if driving_range and Input.is_action_just_pressed("shot_accept"):
				phase = Phase.AIM
				return_ball()


func _on_physics_ball_body_entered(body: Node) -> void:
	print("BONK! ", body.name)
	print("Rotation: ", physics_ball.rotation_degrees)
	print(
		"Linear velocity: ",
		physics_ball.linear_velocity,
		" (",
		physics_ball.linear_velocity.length(),
		")"
	)
	print("Angular velocity: ", physics_ball.angular_velocity)
