# What the people in the museum say. The art server owns it — the guide's script
# is edited in the dashboard and served already resolved into the player's
# language (GET /api/conversations/{code}) — and this is where the game keeps it.
#
# The same script also ships with the game, in assets/dialogue/. That copy is not
# dead weight: it is what the guide says when the server has nothing to say, and
# the game has to be playable with a server that is old, slow or absent. So every
# lookup goes: the fetched script if there is one, else the file.
#
# The two are the same shape bar one thing: the server sends its fixed lines under
# a "lines" object, and the file has them at the top level beside "topics". They
# are flattened to the file's shape on the way in, so dialogue.gd only ever sees
# one shape.
extends Node

signal changed()

# Everyone who has something to say. One so far; a second character is a line
# here and a row in the dashboard.
const SCRIPTS := {"guide": "res://assets/dialogue/guide.json"}

# code -> the script as served, or missing if the server never answered.
var _fetched := {}
# code -> the script as shipped, read once and kept.
var _bundled := {}

func _ready() -> void:
	# A language change refetches the text, exactly as it refetches the UI labels.
	Locale.changed.connect(_on_language_changed)

# Ask the server for every script, in the language settled at boot. Awaited by the
# boot gate, but it never fails it: a script that does not arrive falls back to
# the one in the build, which is a museum that talks rather than a museum that
# stops.
func boot() -> void:
	await reload()

func reload() -> void:
	for code: String in SCRIPTS:
		var served := await Api.fetch_conversation(code, Locale.current)
		if served.is_empty():
			_fetched.erase(code)
			continue
		_fetched[code] = _flatten(served)
	changed.emit()

# One character's script, whatever the game could get: the served one, else the
# one in the build, else nothing at all — which dialogue.gd shows as a man with
# no lines rather than as a crash.
func script_for(code: String) -> Dictionary:
	if _fetched.has(code):
		return _fetched[code]
	return _bundled_script(code)

# Whether what he is saying came off the wire. Only the tests ask; it is the
# difference between "the fallback works" and "the fallback is all there is".
func served(code: String) -> bool:
	return _fetched.has(code)

# --- The two sources -------------------------------------------------------

# The server's shape into the file's: its fixed lines live under "lines", and the
# game reads them at the top level beside the topics.
func _flatten(served: Dictionary) -> Dictionary:
	var out: Dictionary = {}
	var lines: Variant = served.get("lines")
	if lines is Dictionary:
		for key: String in lines:
			out[key] = lines[key]
	out["topics"] = served.get("topics", [])
	return out

func _bundled_script(code: String) -> Dictionary:
	if _bundled.has(code):
		return _bundled[code]
	var path: String = SCRIPTS.get(code, "")
	if path.is_empty() or not FileAccess.file_exists(path):
		push_warning("No script for %s, on the server or in the build" % code)
		_bundled[code] = {}
		return {}
	var parsed: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
	_bundled[code] = parsed if parsed is Dictionary else {}
	return _bundled[code]

func _on_language_changed(_code: String) -> void:
	# Not awaited: the language has already changed everywhere else, and the lines
	# land a moment later. Nobody is mid-conversation — the picker is in Options,
	# which is a screen over a paused museum.
	reload()
