class_name SaveSlotStore
extends RefCounted

const DEFAULT_DIRECTORY := "user://saves"
const DEFAULT_SLOT := "quicksave"
const MAX_SAVE_BYTES := 16 * 1024 * 1024

var save_directory: String
var last_error := ""


func _init(directory: String = DEFAULT_DIRECTORY) -> void:
	save_directory = directory


func save(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
	last_error = ""
	if manager == null or not manager.has_method("serialize_state"):
		return _fail("Save source cannot serialize simulation state")
	if not _is_valid_slot_name(slot_name):
		return _fail("Invalid save slot name")

	var json_text: String = manager.serialize_state()
	if json_text.to_utf8_buffer().size() > MAX_SAVE_BYTES:
		return _fail("Save exceeds the supported size limit")
	if SimulationStateRecord.from_json(json_text) == null:
		return _fail("Simulation produced an invalid save record")
	if not _ensure_directory():
		return false

	var final_path := get_slot_path(slot_name)
	var temporary_path := final_path + ".tmp"
	var backup_path := final_path + ".bak"
	_remove_if_present(temporary_path)

	var file := FileAccess.open(temporary_path, FileAccess.WRITE)
	if file == null:
		return _fail("Could not open temporary save file")
	file.store_string(json_text)
	file.flush()
	file.close()
	if _read_record(temporary_path) == null:
		_remove_if_present(temporary_path)
		return _fail("Temporary save failed validation")

	var had_existing := FileAccess.file_exists(final_path)
	if had_existing:
		_remove_if_present(backup_path)
		if DirAccess.rename_absolute(final_path, backup_path) != OK:
			_remove_if_present(temporary_path)
			return _fail("Could not preserve the previous save")
	if DirAccess.rename_absolute(temporary_path, final_path) != OK:
		if had_existing:
			DirAccess.rename_absolute(backup_path, final_path)
		_remove_if_present(temporary_path)
		return _fail("Could not install the new save")
	_remove_if_present(backup_path)
	return true


func load_into(manager: Node, slot_name: String = DEFAULT_SLOT) -> bool:
	last_error = ""
	if manager == null or not manager.has_method("restore_state"):
		return _fail("Save target cannot restore simulation state")
	if not _is_valid_slot_name(slot_name):
		return _fail("Invalid save slot name")

	var final_path := get_slot_path(slot_name)
	var record := _read_record(final_path)
	if record == null:
		record = _read_record(final_path + ".bak")
	if record == null:
		return _fail("Save slot is missing, invalid, or unsupported")
	if not bool(manager.restore_state(record)):
		return _fail("Simulation refused the validated save record")
	return true


func has_slot(slot_name: String = DEFAULT_SLOT) -> bool:
	if not _is_valid_slot_name(slot_name):
		return false
	var final_path := get_slot_path(slot_name)
	return _read_record(final_path) != null or _read_record(final_path + ".bak") != null


func delete_slot(slot_name: String = DEFAULT_SLOT) -> bool:
	if not _is_valid_slot_name(slot_name):
		return false
	var final_path := get_slot_path(slot_name)
	_remove_if_present(final_path)
	_remove_if_present(final_path + ".tmp")
	_remove_if_present(final_path + ".bak")
	return true


func get_slot_path(slot_name: String = DEFAULT_SLOT) -> String:
	return save_directory.path_join(slot_name + ".json")


func _read_record(path: String) -> SimulationStateRecord:
	if not FileAccess.file_exists(path):
		return null
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		return null
	if file.get_length() > MAX_SAVE_BYTES:
		file.close()
		return null
	var json_text := file.get_as_text()
	file.close()
	return SimulationStateRecord.from_json(json_text)


func _ensure_directory() -> bool:
	var absolute_directory := ProjectSettings.globalize_path(save_directory)
	var error := DirAccess.make_dir_recursive_absolute(absolute_directory)
	if error != OK and error != ERR_ALREADY_EXISTS:
		return _fail("Could not create the save directory")
	return true


func _is_valid_slot_name(slot_name: String) -> bool:
	if slot_name.is_empty() or slot_name.length() > 32:
		return false
	var allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
	for character in slot_name:
		if not allowed.contains(character):
			return false
	return true


func _remove_if_present(path: String) -> void:
	if FileAccess.file_exists(path):
		DirAccess.remove_absolute(path)


func _fail(message: String) -> bool:
	last_error = message
	return false
