extends Sprite3D

# we easin' now
@export var intro_duration: float = 0.9
@export var intro_trans: Tween.TransitionType = Tween.TRANS_SINE
@export var intro_ease: Tween.EaseType = Tween.EASE_IN_OUT

const ENTRY_OFFSET := 0.5
const EXIT_OFFSET := 0.5
const FORWARD_OFFSET := 0.05
const FINGER_OFFSET_X := 0.0

@onready var _button_panel: Node3D = get_parent().get_node("ButtonPanel")

var _buttons: Array = []
var _intro_playing := false
var _sprite_half_h := 0.0
var _intro_sprite_start_y := 0.0
var _intro_sprite_end_y := 0.0
var _intro_button_thresholds: Array = []
var _intro_button_lit: Array = []

func _ready() -> void:
	visible = false
	_sprite_half_h = texture.get_height() * pixel_size * global_transform.basis.get_scale().y * 0.5

	EventBus.game_started.connect(_play_intro)
	EventBus.floor_changed.connect(_on_floor_changed)

func _ensure_buttons() -> void:
	if not _buttons.is_empty():
		return
	_buttons = _collect_buttons()
	if not _buttons.is_empty():
		_set_all_lit(false)

func _collect_buttons() -> Array:
	var arr: Array = []
	var container = _button_panel.get_node_or_null("FloorButtons")
	if container == null or container.get_child_count() == 0:
		container = _button_panel
	for i in range(10):
		var button_name := "%02d" % i
		var btn = container.get_node_or_null(button_name)
		if btn != null:
			arr.append(btn)
	return arr

func _play_intro() -> void:
	if _intro_playing:
		return
	_intro_playing = true

	_ensure_buttons()
	if _buttons.size() < 10:
		_intro_playing = false
		EventBus.intro_finished.emit()
		return

	_set_all_lit(false)

	# animate the hand's global_position.y from start to end over a set duration
	# each button gets a sibling callback scheduled in *parallel*!
	# what fraction of the way from start to end is this button's Y?
	# to figure out when to fire callback on set_delay() to stagge.3.r
	# this way it is easy to change the duration if we want

	var top_button: Node3D = _buttons[9]
	var bottom_button: Node3D = _buttons[0]
	var camera = get_viewport().get_camera_3d()
	var camera_pos = camera.global_position if camera else Vector3.ZERO
	var button_z = top_button.global_position.z
	var hand_z = button_z - FORWARD_OFFSET
	var z_ratio = (hand_z - camera_pos.z) / (button_z - camera_pos.z)
	
	var hand_x = camera_pos.x + (top_button.global_position.x - camera_pos.x) * z_ratio + FINGER_OFFSET_X
	
	var finger_start_y_world = top_button.global_position.y + ENTRY_OFFSET
	var finger_end_y_world = bottom_button.global_position.y - EXIT_OFFSET
	var finger_start_y = camera_pos.y + (finger_start_y_world - camera_pos.y) * z_ratio
	var finger_end_y = camera_pos.y + (finger_end_y_world - camera_pos.y) * z_ratio
	
	var sprite_start_y = finger_start_y - _sprite_half_h
	var sprite_end_y = finger_end_y - _sprite_half_h

	global_position = Vector3(hand_x, sprite_start_y, hand_z)
	visible = true
	
	# now we have to do some more mathin' due to *easing*
	_intro_sprite_start_y = sprite_start_y
	_intro_sprite_end_y = sprite_end_y
	_intro_button_thresholds.clear()
	_intro_button_lit.clear()
	for i in range(_buttons.size()):
		var button_y_world = _buttons[i].global_position.y
		# weird thing: button_y is projected onto the hand's Z-plane to account for perspective
		# to keep the fingertip visually aligned with each button at the exact right moment
		# thanks godot forums!
		var button_y_proj = camera_pos.y + (button_y_world - camera_pos.y) * z_ratio
		var t_fraction = (finger_start_y - button_y_proj) / (finger_start_y - finger_end_y)
		_intro_button_thresholds.append(clamp(t_fraction, 0.0, 1.0))
		_intro_button_lit.append(false)

	var tween := create_tween()
	tween.tween_method(_update_intro_progress, 0.0, 1.0, intro_duration).set_trans(intro_trans).set_ease(intro_ease)

	await tween.finished
	visible = false
	_intro_playing = false
	EventBus.intro_finished.emit()

func _update_intro_progress(progress: float) -> void:
	global_position.y = lerp(_intro_sprite_start_y, _intro_sprite_end_y, progress)
	for i in range(_buttons.size()):
		if not _intro_button_lit[i] and progress >= _intro_button_thresholds[i]:
			_intro_button_lit[i] = true
			_light_button(i)

func _light_button(index: int) -> void:
	var sprite: Sprite3D = _buttons[index].get_node("ButtonSprite")
	sprite.frame = index + 10

func _set_all_lit(lit: bool) -> void:
	for i in range(_buttons.size()):
		var sprite: Sprite3D = _buttons[i].get_node("ButtonSprite")
		sprite.frame = i + (10 if lit else 0)

func _on_floor_changed(floor_num: int) -> void:
	if floor_num >= EventBus.STARTING_FLOOR:
		return
	_ensure_buttons()
	if floor_num < 0 or floor_num >= _buttons.size():
		return
	var sprite: Sprite3D = _buttons[floor_num].get_node("ButtonSprite")
	sprite.frame = floor_num
