class_name Show
extends Node2D
# Manage slide cycling


signal end_reached

const VOID_SLIDE_ID := -1

@export_group("Slide management")
@export var start_slide := 0 ## Id of the slide focused at startup
@export var hide_slides := false ## Hide every slide at startup (except the ones with always_visible enabled)

@export_group("Input setup", "action_")
@export var action_finish_slide := "ui_accept"
@export var action_skip_slide := "ui_right"
@export var action_previous_slide := "ui_left"
@export var action_home_slide := "ui_cancel"

@export_group("Export")
@export var one_slide_per_frame := true
@export var quit_after_last_slide := true

var current_id := VOID_SLIDE_ID
var current_slide: Slide

@onready var focus: Focus = $"../Focus"
@onready var slides: Array = get_children()
@onready var movie_enabled: bool = OS.has_feature("movie")
@onready var auto_next_slide: bool = movie_enabled and one_slide_per_frame


func _ready() -> void:
	_enforce()
	print("The show contains %s slides" % slides.size())

	if hide_slides:
		_hide_every_slide()

	_global_slide_setup()

	if auto_next_slide:
		end_reached.connect(get_tree().quit)
		return

	go_slowly_to_next_slide()


func _process(_delta: float) -> void:
	if auto_next_slide:
		go_fast_to_next_slide()


func go_slowly_to_next_slide() -> void:
	next_slide(Focus.Transit.SMOOTH)


func go_fast_to_next_slide() -> void:
	next_slide(Focus.Transit.BEAM)


func go_fast_to_previous_slide() -> void:
	next_slide_at(_previous_id(), Focus.Transit.BEAM)


func next_slide(trans: Focus.Transit) -> void:
	next_slide_at(_next_id(), trans)


func next_slide_at(next_id: int, trans: Focus.Transit) -> void:
	_disable_current_slide()

	current_id = next_id
	current_slide = _focus_slide(current_id, trans)

	if is_last_slide():
		end_reached.emit()

	_enable_current_slide()


func is_last_slide() -> bool:
	return current_id == slides.size() - 1


func _focus_slide(id: int, trans: Focus.Transit) -> Slide:
	var slide: Slide = slides[id]
	focus.focus_on(slide.get_center(), slide.get_scale().x, trans)
	return slide


func _hide_every_slide() -> void:
	for slide in slides:
		slide.gently_hide()


func _global_slide_setup() -> void:
	for slide in slides:
		slide.set_action_finish(action_finish_slide)


func _disable_current_slide() -> void:
	if current_slide != null:
		current_slide.disable()
		current_slide.finished.disconnect(_on_current_slide_finished)


func _enable_current_slide() -> void:
	current_slide.enable()
	current_slide.finished.connect(_on_current_slide_finished)


func _enforce() -> void:
	_enforce_parameters()
	_enforce_children()


func _enforce_parameters() -> void:
	assert(focus, "A Focus node must be next to the Show node")
	assert(start_slide >= 0,
		"Negative index is not supported for start_slide")
	assert(start_slide < slides.size(),
		"start_slide index is out of bound (max is %s)" % (slides.size() - 1))


func _enforce_children() -> void:
	assert(!slides.is_empty(), "A slideshow must contain slides")
	for object in slides:
		assert(object is Slide, "A slideshow can only contain slides but encontered %s" % object)


func _next_id() -> int:
	if current_id == VOID_SLIDE_ID:
		return start_slide
	elif is_last_slide():
		return 0

	return current_id + 1


func _previous_id() -> int:
	var next_id: int = current_id - 1

	if next_id < 0:
		return slides.size() - 1

	return next_id


func _reset_id() -> void:
		current_id = VOID_SLIDE_ID


func _unhandled_key_input(event: InputEvent) -> void:
	if !event.is_pressed():
		return

	if event.is_action(action_skip_slide):
		get_viewport().set_input_as_handled()
		go_fast_to_next_slide()
	elif event.is_action(action_previous_slide):
		get_viewport().set_input_as_handled()
		go_fast_to_previous_slide()
	elif event.is_action(action_home_slide):
		get_viewport().set_input_as_handled()
		_reset_id()
		go_slowly_to_next_slide()


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.is_pressed():
		_focus_nearest_slide_from(get_global_mouse_position())


func _focus_nearest_slide_from(target_pos: Vector2) -> void:
	var next_slide = _search_nearest_slide_from(target_pos)
	var next_id = slides.find(next_slide)
	next_slide_at(next_id, Focus.Transit.SMOOTH)


func _search_nearest_slide_from(target_pos: Vector2) -> Slide:
	var nearest_slide = current_slide
	var shortest_distance = target_pos.distance_squared_to(nearest_slide.get_center())
	for current_slide in slides:
		var current_distance = target_pos.distance_squared_to(current_slide.get_center())
		if current_distance < shortest_distance:
			nearest_slide = current_slide
			shortest_distance = current_distance

	return nearest_slide


func _on_current_slide_finished() -> void:
	go_slowly_to_next_slide()
