class_name Game extends Node
## Wrapper for the game application

@export_file("*.tscn") var start_scene: String = "res://src/ui/menus/title_screen/title_screen.tscn"

var _loading_resources := {}

@onready var viewport: GameViewportContainer = %ViewportContainer
@onready var sfx: GameSFX = %SFX

@onready var content: Node = %Content
@onready var loader_transition: AnimationPlayer = %LoaderTransition
@onready var loading_screen: Control = %LoadingScreen

static var group := "GameGroup"  # hey i'm group

## Typesafe accessor for GameSettings singleton
static var settings: GameSettingsType:
	get:
		return GameSettings


class Promise:
	var _callbacks: Array[Callable] = []
	var _end_callbacks: Array[Callable] = []

	func then(fn: Callable) -> Promise:
		_callbacks.push_back(fn)
		return self

	func finally(fn: Callable) -> Promise:
		_end_callbacks.push_back(fn)
		return self

	func resolve(res: Variant) -> void:
		for fn: Callable in _callbacks + _end_callbacks:
			fn.call(res)


class ScenePromise:
	extends Promise

	func resolve(res: Variant) -> void:
		@warning_ignore("unsafe_cast")
		var instance: Node = (res as PackedScene).instantiate()
		super.resolve(instance)


func _initial_load() -> void:
	queue_scene(start_scene)


func _ready() -> void:
	call_deferred("_initial_load")


## Unload the running scene & queue up a new scene to be loaded in the background.
##
## The loading screen will be shown until the scene is loaded.
func queue_scene(path: String) -> Promise:
	return queue_load(path, ScenePromise.new(), "PackedScene").finally(_finish_scene_load)


## Queue a resource to be loaded in the background.
##
## Returns a `Promise` which can be used to attach callbacks
## which will be called with the resource after it is loaded.
func queue_load(path: String, promise: Promise = null, type_hint: String = "") -> Promise:
	if not promise:
		promise = Promise.new()
	_loading_resources[path] = promise
	ResourceLoader.load_threaded_request(path, type_hint)
	return promise


func _unload_content() -> void:
	for child: Node in content.get_children():
		child.queue_free()


func _finish_scene_load(instance: Node) -> void:
	# Unpause in case the previous scene was paused.
	get_tree().paused = false
	# Reset time scale in case it's been changed.
	Engine.time_scale = 1.0

	content.add_child(instance)
	instance.reparent(content)


func _process(_delta: float) -> void:
	if _loading_resources and not loading_screen.visible:
		loader_transition.play("fade_in")

	if loader_transition.is_playing():
		return

	for key: String in _loading_resources.keys():
		match ResourceLoader.load_threaded_get_status(key):
			ResourceLoader.THREAD_LOAD_LOADED:
				@warning_ignore("unsafe_cast")
				(_loading_resources[key] as Promise).resolve(ResourceLoader.load_threaded_get(key))
				_loading_resources.erase(key)
			ResourceLoader.THREAD_LOAD_FAILED:
				assert(false, "Failed loading resource: " + key)
			ResourceLoader.THREAD_LOAD_INVALID_RESOURCE:
				assert(false, "Can't load invalid resource: " + key)
			_:
				# Continue loading
				pass

	if not _loading_resources and loading_screen.visible:
		loader_transition.play("fade_out")
