## pack_map.gd — Map PCK Exporter
##
## Editor tool script that exports a map scene (.tscn) as a standalone
## .pck resource pack. The .pck contains only the map scene and its
## direct dependencies (meshes, textures, materials) — no game logic.
##
## Usage:
##   1. Open the map template project (client/map_template/) in Godot.
##   2. Build your map in the scenes/maps/ directory.
##   3. Run this script from the Editor > Tools menu, or via the CLI:
##      godot --headless --script scripts/map_packaging/pack_map.gd --map=res://scenes/maps/my_map.tscn
##
## Output:
##   user://packed_maps/my_map.pck  (ready to upload to the registry server)
##
## The exported .pck can be loaded in-game via:
##   ProjectSettings.load_resource_pack("user://maps/my_map.pck")
##
## Requirements:
##   - Godot 4.2+
##   - The map scene must use only local resources (relative paths within the project)
##   - External dependencies (e.g. shared game assets) should be excluded — the
##     .pck is meant to be additive content, not a full replacement.

tool
extends EditorScript

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

## Output directory relative to user://
const OUTPUT_DIR: String = "packed_maps"

## Godot export mode — packs all dependencies recursively.
const EXPORT_MODE: int = PackedScene.GEN_FLAG_SAVE_NONE

# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------

func _run() -> void:
	# Determine the map scene path
	var scene_path: String = _resolve_scene_path()
	if scene_path.is_empty():
		print("Usage: godot --headless --script pack_map.gd --map=res://scenes/maps/<map_name>.tscn")
		print("       Or run from Editor > Tools > Pack Current Map")
		return

	# Validate the scene exists
	if not ResourceLoader.exists(scene_path):
		push_error("[PackMap] Scene not found: %s" % scene_path)
		return

	# Derive map name from file name
	var map_name: String = scene_path.get_file().trim_suffix(".tscn")
	var output_path: String = "user://%s/%s.pck" % [OUTPUT_DIR, map_name]

	# Ensure output directory exists
	DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("user://%s" % OUTPUT_DIR))

	print("[PackMap] Packaging: %s" % scene_path)
	print("[PackMap] Output:     %s" % output_path)

	# Load the scene
	var scene: PackedScene = load(scene_path)
	if scene == null:
		push_error("[PackMap] Failed to load scene: %s" % scene_path)
		return

	# Generate the .pck via Godot's built-in packer.
	# We do this by creating a temporary PackedScene that encapsulates the map,
	# then using ProjectSettings.save_resource_pack to extract only the needed resources.
	var err: Error = _export_pck(scene, scene_path, output_path)
	if err != OK:
		push_error("[PackMap] Export failed: %s" % error_string(err))
		return

	print("[PackMap] Successfully packed: %s (→ %s)" % [map_name, output_path])
	print("[PackMap] File size: %d bytes" % FileAccess.get_size(output_path))

	# Write a sidecar JSON with metadata
	_write_metadata(map_name, output_path, scene_path)


# ---------------------------------------------------------------------------
# Internal
# ---------------------------------------------------------------------------

## Resolve the --map CLI argument or use the currently open scene.
func _resolve_scene_path() -> String:
	# CLI argument takes precedence
	for arg in OS.get_cmdline_args():
		if arg.begins_with("--map="):
			return arg.trim_prefix("--map=")

	# Fallback: use the currently open scene in the editor
	var current_scene_path: String = ""
	if Engine.is_editor_hint():
		# In editor mode, try to get the current scene from the editor interface
		var editor_interface: EditorInterface = get_editor_interface()
		if editor_interface:
			current_scene_path = editor_interface.get_current_scene().scene_file_path

	return current_scene_path


## Export the scene as a .pck file containing only its direct dependencies.
func _export_pck(scene: PackedScene, scene_path: String, output_path: String) -> Error:
	# Strategy: pack the scene + all its dependencies into a .pck using
	# ResourceSaver. We create a packaging helper that simulates what
	# Godot's export process does for resource packs.

	# Create a collection of all dependencies we need to include
	var deps: Array[String] = _collect_dependencies(scene_path)

	# Add the scene itself
	deps.append(scene_path)

	# We need to strip the "res://" prefix and map to correct paths
	# Create a PackedScene copy with packed resources
	var packed: PackedScene = _build_packed_map(scene_path, deps)
	if packed == null:
		return ERR_FILE_CORRUPT

	# Write the .pck using ProjectSettings.save_resource_pack()
	var files_to_pack: PackedStringArray = PackedStringArray()
	for dep in deps:
		var global_path: String = ProjectSettings.globalize_path(dep)
		if FileAccess.file_exists(global_path):
			files_to_pack.append(dep)

	if files_to_pack.is_empty():
		return ERR_FILE_NOT_FOUND

	var err: Error = ProjectSettings.save_resource_pack(output_path, files_to_pack)
	return err


## Collect all dependencies of a scene recursively.
func _collect_dependencies(scene_path: String) -> Array[String]:
	var deps: Array[String] = []
	var visited: Dictionary = {}
	var pending: Array[String] = [scene_path]

	while not pending.is_empty():
		var current: String = pending.pop_front()
		if current in visited:
			continue
		visited[current] = true

		# Skip external / built-in resources
		if current.begins_with("builtin://") or current.begins_with("uid://"):
			continue

		# Get dependencies from ResourceLoader
		var dep_list: PackedStringArray = ResourceLoader.get_dependencies(current)
		for dep in dep_list:
			if dep not in visited and dep.begins_with("res://"):
				deps.append(dep)
				pending.append(dep)

	return deps


## Build a PackedScene from the map and its dependencies.
func _build_packed_map(scene_path: String, deps: Array[String]) -> PackedScene:
	# Load the scene and pack it
	var scene: PackedScene = load(scene_path)
	if scene == null:
		return null
	return scene


## Write a JSON metadata file alongside the .pck.
func _write_metadata(map_name: String, pck_path: String, scene_path: String) -> void:
	var meta := {
		map_name = map_name,
		pck_path = pck_path,
		source_scene = scene_path,
		godot_version = Engine.get_version_info(),
		packed_at = Time.get_datetime_string_from_system(),
	}

	var meta_path: String = pck_path.trim_suffix(".pck") + ".json"
	var f := FileAccess.open(meta_path, FileAccess.WRITE)
	if f:
		f.store_string(JSON.stringify(meta, "\t", false))
		f.close()
		print("[PackMap] Metadata written: %s" % meta_path)
