@tool
extends Object
class_name AssetLoader

#region Declaration ---
# === CONST ===

## If you want to change the mod extension. Keep in mind that it's still a json under the hood
const MOD_INFOS_EXTENSION = "modinfo"
## If you want to change the localization extension. Keep in mind that it's still a json under the hood
const LOC_EXTENSION = "loc"

## Path to the mods folder : [param res://mods] by default
## [br]
## (is not a const but should be treated as such, so the uppercase)
var ASSETS_PATH

# === VAR ===
var dir:DirAccess
var mod_folders:PackedStringArray
var critical_error := false

var mod_manifests: Dictionary[StringName,ModManifest]
var mod_localizations: Dictionary[StringName,Dictionary] # {mod_id: {locale: {key: value}}}

# === SIGNALS ===
signal loading_finished

#endregion ---


func _init() -> void:
	print_verbose("------ MOD LOADING STARTED ------")
	# Use res://mods/ in editor, executable's mods/ folder in exported builds
	if OS.has_feature("editor"):
		ASSETS_PATH = "res://"+ProjectSettings.get_setting("game/mods/mod_folder_name", "mods")
	else:
		# fallbakc to use mods folder next to executable
		ASSETS_PATH = OS.get_executable_path().get_base_dir() + "/" + ProjectSettings.get_setting("game/mods/mod_folder_name", "mods")

	dir = DirAccess.open(ASSETS_PATH)
	print_debug(ASSETS_PATH)
	if not dir:
		push_error("AssetLoader: Mods folder not found at '%s'" % ASSETS_PATH)
		push_error("AssetLoader:",DirAccess.get_open_error())
		_show_error_popup("Mods folder not found at '%s'" % ASSETS_PATH)
		critical_error = true
		return
	mod_folders = dir.get_directories()
	if mod_folders.is_empty():
		push_error("AssetLoader: Mods folder '%s' is empty — no mods to load." % ASSETS_PATH)
		_show_error_popup("Mods folder '%s' is empty — no mods to load." % ASSETS_PATH)
		critical_error = true
		return

# === PRIVATE ===


## This will show a native MessageBox on Windows,
## a native dialog on macOS, and GTK/QT dialog on Linux.
func _show_error_popup(message: String) -> void:
	OS.alert("AssetLoader:"+message, "AssetLoader:Error")


# === PUBLIC ===
func load_all():
	load_mods()
	load_mods_content()


## Load and unpack all .pck before serialization
func load_mods():
	for mod in mod_folders:
		var mod_name = mod
		var mod_path = ASSETS_PATH + "/" + mod_name
		var manifest_path = mod_path + "/" + mod_name + "." + MOD_INFOS_EXTENSION
		if FileAccess.file_exists(manifest_path):
			var manifest_file := FileAccess.open(manifest_path, FileAccess.READ)
			var manifest: ModManifest = ModManifest.new_from_file(manifest_file)
			if !manifest:continue
			if !mod_manifests.has(manifest.id):
				mod_manifests[manifest.id] = manifest
				manifest.path = mod_path
				print_verbose("Mod manifest is loaded: %s" % manifest.name)
			else:
				push_warning("Another mod as the same id:\n %s will not be loaded" % manifest_path)

		else:
			push_warning("No manifest found in %s" % manifest_path)
	for mod in mod_manifests:
		print("Mod loaded: %s" % mod)


func load_localizations():
	if mod_manifests.size() == 0:
		print_verbose("No mods to load localizations from.")
		return
	for mod_id in mod_manifests:
		var mod_path: String = mod_manifests[mod_id]["path"]
		var mod_name: String = mod_path.get_file()
		mod_localizations[mod_id] = {}
		dir.change_dir(mod_path)
		dir.list_dir_begin()
		var file_name = dir.get_next()
		while file_name != "":
			if file_name.ends_with("." + LOC_EXTENSION):
				var locale = file_name.get_basename().get_extension()
				var loc_path = mod_path + "/" + file_name
				var loc_file = FileAccess.open(loc_path, FileAccess.READ)
				if loc_file:
					var loc_data: Dictionary = JSON.parse_string(loc_file.get_as_text())
					if typeof(loc_data) == TYPE_DICTIONARY:
						mod_localizations[mod_id][locale] = loc_data
						print_verbose("Loaded localization '%s' for mod '%s'" % [locale, mod_id])
					else:
						push_warning("Invalid localization file: %s" % loc_path)
				else:
					push_warning("Failed to open localization file: %s" % loc_path)
			file_name = dir.get_next()
		dir.list_dir_end()


func register_localizations():
	for mod_id in mod_localizations:
		for locale in mod_localizations[mod_id]:
			var translation = Translation.new()
			translation.locale = locale
			for key in mod_localizations[mod_id][locale]:
				translation.add_message(key, mod_localizations[mod_id][locale][key])
			TranslationServer.add_translation(translation)


func load_mods_content():
	if OS.has_feature("editor"):
		# Assets should already be loaded/imported.
		# FIXME: here we should implement something to serialize ? maybe not.
		return
	if mod_manifests.size() == 0:
		print_verbose("No mods to load content from.")
		return
	for mod_id in mod_manifests:
		var modpacks:Array = mod_manifests[mod_id].get_mod_packs()
		for pack in modpacks:
			# Load assets from the PCKs (mounted at res://)
			var pck_path:StringName = mod_manifests[mod_id].path+ "/" + pack + ".pck"
			if dir.file_exists(pck_path):
				if ProjectSettings.load_resource_pack(pck_path):
					print_verbose("Loaded PCK for mod '%s': %s" % [mod_id, pck_path])
					mod_manifests[mod_id]._validated = true
				else:
					push_warning("Failed to load PCK: %s" % pck_path)
			else:
				push_warning("PCK not found: %s" % pck_path)
