extends Node
## Stability sweep 2026-10-02: save robustness. Loads every character save (*.json with a "job") under --dir=<abs path>
## through the real migration path (CharacterState.from_dict, then Life.ensure for the life / housing state), checks
## it makes sense (known class, level, map, items, equipment) and that a save -> load -> save round trip is stable.
##   Godot --headless --path game res://scripts/tools/save_check.tscn -- --dir=C:/saves_copy
## Prints "SAVECHECK ok|BAD <file> ..." and "SAVECHECK done files=N bad=M".

const CharacterState = preload("res://scripts/server/character_state.gd")
const Life = preload("res://scripts/server/life.gd")


func _ready() -> void:
	var dir := ""
	for a in OS.get_cmdline_user_args():
		if a.begins_with("--dir="):
			dir = a.substr(6)
	var files: Array = []
	_collect(dir, files)
	var bad := 0
	var n := 0
	for f in files:
		var txt := FileAccess.get_file_as_string(f)
		var d = JSON.parse_string(txt)
		if not (d is Dictionary) or not d.has("job"):
			continue
		n += 1
		var why: Array = []
		var c = CharacterState.new()
		c.from_dict(d)
		if not GameData.jobs.has(str(c.job)):
			why.append("unknown class %s" % c.job)
		if int(c.base_lv) < 1:
			why.append("base_lv %d" % c.base_lv)
		var unknown: Array = []
		for id in c.inventory:
			if GameData.item(str(id)).is_empty():
				unknown.append(str(id))
		for slot in c.equipment:
			var e = c.equipment[slot]
			var eid := str(e.get("id", "")) if e is Dictionary else str(e)
			if eid != "" and GameData.item(eid).is_empty():
				unknown.append("equip:" + eid)
		if not unknown.is_empty():
			why.append("unknown items %s" % str(unknown))
		Life.ensure(c)
		var d2: Dictionary = c.to_dict()
		var c2 = CharacterState.new()
		c2.from_dict(d2)
		Life.ensure(c2)
		var d3: Dictionary = c2.to_dict()
		# volatile keys (timestamps of "now") do not count
		for k in ["saved_at", "time", "last_login"]:
			d2.erase(k)
			d3.erase(k)
		if JSON.stringify(d2, "", true) != JSON.stringify(d3, "", true):
			why.append("round trip not stable")
		if not why.is_empty():
			bad += 1
		print("SAVECHECK %s %s v%s %s Lv %d map %s %s" % ["ok " if why.is_empty() else "BAD", f.get_file(), str(d.get("version", "?")), c.job,
			int(c.base_lv), str(d.get("map", "")), "; ".join(why)])
	print("SAVECHECK done files=%d bad=%d" % [n, bad])
	get_tree().quit(1 if bad > 0 else 0)


func _collect(dir: String, out: Array) -> void:
	var da := DirAccess.open(dir)
	if da == null:
		return
	for f in da.get_files():
		if f.ends_with(".json"):
			out.append(dir.path_join(f))
	for s in da.get_directories():
		_collect(dir.path_join(s), out)
