## Save Manager - Secure Game Data Persistence System
##
## Provides secure save/load functionality with tamper detection, race condition protection,
## and permissive validation. Features backup/restore, version migration, and data integrity.
## Implements multi-layered security: checksums, size limits, type validation, and bounds checking.

extends Node

const SAVE_FILE_PATH: String = "user://savegame.save"
const SAVE_FORMAT_VERSION: int = 1
const MAX_GRID_SIZE: int = 15
const MAX_TILE_TYPES: int = 10
const MAX_SCORE: int = 999999999
const MAX_GAMES_PLAYED: int = 100000
const MAX_FILE_SIZE: int = 1048576  # 1MB limit

# Save operation protection - prevents race conditions
var _save_in_progress: bool = false
var _restore_in_progress: bool = false

var game_data: Dictionary = {
	"high_score": 0,
	"current_score": 0,
	"games_played": 0,
	"total_score": 0,
	"grid_state":
	{
		"grid_size": {"x": 8, "y": 8},
		"tile_types_count": 5,
		"active_gem_types": [0, 1, 2, 3, 4],
		"grid_layout": []  # 2D array of tile types
	}
}


func _ready() -> void:
	"""Initialize SaveManager and load existing save data on startup"""
	load_game()


func save_game() -> bool:
	"""Save current game data with race condition protection and error handling"""
	# Prevent concurrent saves
	if _save_in_progress:
		DebugManager.log_warn("Save already in progress, skipping", "SaveManager")
		return false

	_save_in_progress = true
	var result: bool = _perform_save()
	_save_in_progress = false
	return result


func _perform_save() -> bool:
	# Create backup before saving
	_create_backup()

	# Add version and checksum
	var save_data: Dictionary = game_data.duplicate(true)
	save_data["_version"] = SAVE_FORMAT_VERSION
	# Calculate checksum excluding _checksum field
	save_data["_checksum"] = _calculate_checksum(save_data)

	var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
	if save_file == null:
		DebugManager.log_error(
			"Failed to open save file for writing: %s" % SAVE_FILE_PATH, "SaveManager"
		)
		return false

	var json_string: String = JSON.stringify(save_data)

	# Validate JSON creation
	if json_string.is_empty():
		DebugManager.log_error("Failed to serialize save data to JSON", "SaveManager")
		save_file.close()
		return false

	save_file.store_var(json_string)
	save_file.close()

	DebugManager.log_info(
		(
			"Game saved successfully. High score: %d, Current score: %d"
			% [game_data.high_score, game_data.current_score]
		),
		"SaveManager"
	)
	return true


func load_game() -> void:
	"""Load game data from disk with comprehensive validation and error recovery"""
	if not FileAccess.file_exists(SAVE_FILE_PATH):
		DebugManager.log_info("No save file found, using defaults", "SaveManager")
		return

	# Reset restore flag
	_restore_in_progress = false

	var save_file: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
	if save_file == null:
		DebugManager.log_error(
			"Failed to open save file for reading: %s" % SAVE_FILE_PATH, "SaveManager"
		)
		return

	# Check file size
	var file_size: int = save_file.get_length()
	if file_size > MAX_FILE_SIZE:
		DebugManager.log_error(
			"Save file too large: %d bytes (max %d)" % [file_size, MAX_FILE_SIZE], "SaveManager"
		)
		save_file.close()
		return

	var json_string: Variant = save_file.get_var()
	save_file.close()

	if not json_string is String:
		DebugManager.log_error("Save file contains invalid data type", "SaveManager")
		return

	var json: JSON = JSON.new()
	var parse_result: Error = json.parse(json_string)
	if parse_result != OK:
		DebugManager.log_error(
			"Failed to parse save file JSON: %s" % json.error_string, "SaveManager"
		)
		if not _restore_in_progress:
			var backup_restored = _restore_backup_if_exists()
			if not backup_restored:
				DebugManager.log_warn(
					"JSON parse failed and backup restore failed, using defaults", "SaveManager"
				)
		return

	var loaded_data: Variant = json.data
	if not loaded_data is Dictionary:
		DebugManager.log_error("Save file root is not a dictionary", "SaveManager")
		if not _restore_in_progress:
			var backup_restored = _restore_backup_if_exists()
			if not backup_restored:
				DebugManager.log_warn(
					"Invalid data format and backup restore failed, using defaults", "SaveManager"
				)
		return

	# Validate checksum first
	if not _validate_checksum(loaded_data):
		DebugManager.log_error(
			"Save file checksum validation failed - possible tampering", "SaveManager"
		)
		if not _restore_in_progress:
			var backup_restored = _restore_backup_if_exists()
			if not backup_restored:
				DebugManager.log_warn(
					"Backup restore failed, using default game data", "SaveManager"
				)
		return

	# Handle version migration
	var migrated_data: Variant = _handle_version_migration(loaded_data)
	if migrated_data == null:
		DebugManager.log_error("Save file version migration failed", "SaveManager")
		if not _restore_in_progress:
			var backup_restored = _restore_backup_if_exists()
			if not backup_restored:
				DebugManager.log_warn(
					"Migration failed and backup restore failed, using defaults", "SaveManager"
				)
		return

	# Validate and fix loaded data
	if not _validate_and_fix_save_data(migrated_data):
		DebugManager.log_error(
			"Save file failed validation after migration, using defaults", "SaveManager"
		)
		if not _restore_in_progress:
			var backup_restored = _restore_backup_if_exists()
			if not backup_restored:
				DebugManager.log_warn(
					"Validation failed and backup restore failed, using defaults", "SaveManager"
				)
		return

	# Use migrated data
	loaded_data = migrated_data

	# Safely merge validated data
	_merge_validated_data(loaded_data)

	DebugManager.log_info(
		(
			"Game loaded successfully. High score: %d, Games played: %d"
			% [game_data.high_score, game_data.games_played]
		),
		"SaveManager"
	)


func update_current_score(score: int) -> void:
	# Input validation
	if score < 0:
		DebugManager.log_warn("Negative score rejected: %d" % score, "SaveManager")
		return
	if score > MAX_SCORE:
		DebugManager.log_warn(
			"Score too high, capping at maximum: %d -> %d" % [score, MAX_SCORE], "SaveManager"
		)
		score = MAX_SCORE

	game_data.current_score = score
	if score > game_data.high_score:
		game_data.high_score = score
		DebugManager.log_info("New high score: %d" % score, "SaveManager")


func start_new_game() -> void:
	game_data.current_score = 0
	game_data.games_played += 1
	# Clear saved grid state
	game_data.grid_state.grid_layout = []
	DebugManager.log_info(
		"Started new game #%d (cleared grid state)" % game_data.games_played, "SaveManager"
	)


func finish_game(final_score: int) -> void:
	# Input validation
	if final_score < 0:
		DebugManager.log_warn("Negative final score rejected: %d" % final_score, "SaveManager")
		return
	if final_score > MAX_SCORE:
		DebugManager.log_warn(
			"Final score too high, capping: %d -> %d" % [final_score, MAX_SCORE], "SaveManager"
		)
		final_score = MAX_SCORE

	DebugManager.log_info(
		"Finishing game with score: %d (previous: %d)" % [final_score, game_data.current_score],
		"SaveManager"
	)
	game_data.current_score = final_score

	# Prevent overflow
	var new_total: int = game_data.total_score + final_score
	if new_total < game_data.total_score:  # Overflow check
		DebugManager.log_warn("Total score overflow prevented", "SaveManager")
		game_data.total_score = MAX_SCORE
	else:
		game_data.total_score = new_total

	if final_score > game_data.high_score:
		game_data.high_score = final_score
		DebugManager.log_info("New high score achieved: %d" % final_score, "SaveManager")
	save_game()


func get_high_score() -> int:
	return game_data.high_score


func get_current_score() -> int:
	return game_data.current_score


func get_games_played() -> int:
	return game_data.games_played


func get_total_score() -> int:
	return game_data.total_score


func save_grid_state(
	grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
) -> void:
	# Input validation
	if not _validate_grid_parameters(grid_size, tile_types_count, active_gem_types, grid_layout):
		DebugManager.log_error("Grid state validation failed, not saving", "SaveManager")
		return

	DebugManager.log_info(
		(
			"Saving grid state: size(%d,%d), types=%d, layout_rows=%d"
			% [grid_size.x, grid_size.y, tile_types_count, grid_layout.size()]
		),
		"SaveManager"
	)

	game_data.grid_state.grid_size = {"x": grid_size.x, "y": grid_size.y}
	game_data.grid_state.tile_types_count = tile_types_count
	game_data.grid_state.active_gem_types = active_gem_types.duplicate()
	game_data.grid_state.grid_layout = grid_layout.duplicate(true)  # Deep copy

	# Debug: Print first rows
	for y in range(min(3, grid_layout.size())):
		var row_str: String = ""
		for x in range(min(8, grid_layout[y].size())):
			row_str += str(grid_layout[y][x]) + " "
		DebugManager.log_debug("Saved row %d: %s" % [y, row_str], "SaveManager")

	save_game()


func get_saved_grid_state() -> Dictionary:
	return game_data.grid_state


func has_saved_grid() -> bool:
	return game_data.grid_state.grid_layout.size() > 0


func clear_grid_state() -> void:
	DebugManager.log_info("Clearing saved grid state", "SaveManager")
	game_data.grid_state.grid_layout = []
	save_game()


func reset_all_progress() -> bool:
	"""Reset all progress and delete save files"""
	DebugManager.log_info("Starting complete progress reset", "SaveManager")

	# Reset game data to defaults
	game_data = {
		"high_score": 0,
		"current_score": 0,
		"games_played": 0,
		"total_score": 0,
		"grid_state":
		{
			"grid_size": {"x": 8, "y": 8},
			"tile_types_count": 5,
			"active_gem_types": [0, 1, 2, 3, 4],
			"grid_layout": []  # 2D array of tile types
		}
	}

	# Delete main save file
	if FileAccess.file_exists(SAVE_FILE_PATH):
		var error: Error = DirAccess.remove_absolute(SAVE_FILE_PATH)
		if error == OK:
			DebugManager.log_info("Main save file deleted successfully", "SaveManager")
		else:
			DebugManager.log_error(
				"Failed to delete main save file: error %d" % error, "SaveManager"
			)

	# Delete backup file
	var backup_path: String = SAVE_FILE_PATH + ".backup"
	if FileAccess.file_exists(backup_path):
		var error: Error = DirAccess.remove_absolute(backup_path)
		if error == OK:
			DebugManager.log_info("Backup save file deleted successfully", "SaveManager")
		else:
			DebugManager.log_error(
				"Failed to delete backup save file: error %d" % error, "SaveManager"
			)

	DebugManager.log_info(
		"Progress reset completed - all scores and save data cleared", "SaveManager"
	)

	# Clear restore flag
	_restore_in_progress = false

	# Create fresh save file with default data
	DebugManager.log_info("Creating fresh save file with default data", "SaveManager")
	save_game()

	return true


# Security and validation helper functions
func _validate_save_data(data: Dictionary) -> bool:
	# Check required fields exist and have correct types
	var required_fields: Array[String] = [
		"high_score", "current_score", "games_played", "total_score", "grid_state"
	]
	for field in required_fields:
		if not data.has(field):
			DebugManager.log_error("Missing required field: %s" % field, "SaveManager")
			return false

	# Validate numeric fields
	if not _is_valid_score(data.get("high_score", 0)):
		DebugManager.log_error("Invalid high_score validation failed", "SaveManager")
		return false
	if not _is_valid_score(data.get("current_score", 0)):
		DebugManager.log_error("Invalid current_score validation failed", "SaveManager")
		return false
	if not _is_valid_score(data.get("total_score", 0)):
		DebugManager.log_error("Invalid total_score validation failed", "SaveManager")
		return false

	# Use safe getter for games_played validation
	var games_played: Variant = data.get("games_played", 0)
	if not (games_played is int or games_played is float):
		DebugManager.log_error(
			"Invalid games_played type: %s (type: %s)" % [str(games_played), typeof(games_played)],
			"SaveManager"
		)
		return false

	# Check for NaN/Infinity in games_played if it's a float
	if games_played is float and (is_nan(games_played) or is_inf(games_played)):
		DebugManager.log_error(
			"Invalid games_played float value: %s" % str(games_played), "SaveManager"
		)
		return false

	var games_played_int: int = int(games_played)
	if games_played_int < 0 or games_played_int > MAX_GAMES_PLAYED:
		DebugManager.log_error(
			"Invalid games_played value: %d (range: 0-%d)" % [games_played_int, MAX_GAMES_PLAYED],
			"SaveManager"
		)
		return false

	# Validate grid state
	var grid_state: Variant = data.get("grid_state", {})
	if not grid_state is Dictionary:
		DebugManager.log_error("Grid state is not a dictionary", "SaveManager")
		return false

	return _validate_grid_state(grid_state)


func _validate_and_fix_save_data(data: Dictionary) -> bool:
	"""
	Permissive validation that fixes issues instead of rejecting data entirely.
	Used during migration to preserve as much user data as possible.
	"""
	DebugManager.log_info("Running permissive validation with auto-fix", "SaveManager")

	# Ensure all required fields exist, create defaults if missing
	var required_fields: Array[String] = [
		"high_score", "current_score", "games_played", "total_score", "grid_state"
	]
	for field in required_fields:
		if not data.has(field):
			DebugManager.log_warn(
				"Missing required field '%s', adding default value" % field, "SaveManager"
			)
			match field:
				"high_score", "current_score", "total_score":
					data[field] = 0
				"games_played":
					data[field] = 0
				"grid_state":
					data[field] = {
						"grid_size": {"x": 8, "y": 8},
						"tile_types_count": 5,
						"active_gem_types": [0, 1, 2, 3, 4],
						"grid_layout": []
					}

	# Fix numeric fields - clamp to valid ranges instead of rejecting
	for field in ["high_score", "current_score", "total_score"]:
		var value: Variant = data.get(field, 0)
		if not (value is int or value is float):
			DebugManager.log_warn("Invalid type for %s, converting to 0" % field, "SaveManager")
			data[field] = 0
		else:
			var numeric_value: int = int(value)
			if numeric_value < 0:
				DebugManager.log_warn("Negative %s fixed to 0" % field, "SaveManager")
				data[field] = 0
			elif numeric_value > MAX_SCORE:
				DebugManager.log_warn("%s too high, clamped to maximum" % field, "SaveManager")
				data[field] = MAX_SCORE
			else:
				data[field] = numeric_value

	# Fix games_played
	var games_played: Variant = data.get("games_played", 0)
	if not (games_played is int or games_played is float):
		DebugManager.log_warn("Invalid games_played type, converting to 0", "SaveManager")
		data["games_played"] = 0
	else:
		var games_played_int: int = int(games_played)
		if games_played_int < 0:
			data["games_played"] = 0
		elif games_played_int > MAX_GAMES_PLAYED:
			data["games_played"] = MAX_GAMES_PLAYED
		else:
			data["games_played"] = games_played_int

	# Fix grid_state - ensure it exists and has basic structure
	var grid_state: Variant = data.get("grid_state", {})
	if not grid_state is Dictionary:
		DebugManager.log_warn("Invalid grid_state, creating default", "SaveManager")
		data["grid_state"] = {
			"grid_size": {"x": 8, "y": 8},
			"tile_types_count": 5,
			"active_gem_types": [0, 1, 2, 3, 4],
			"grid_layout": []
		}
	else:
		# Fix grid_state fields if they're missing or invalid
		if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
			DebugManager.log_warn("Invalid grid_size, using default", "SaveManager")
			grid_state["grid_size"] = {"x": 8, "y": 8}

		if not grid_state.has("tile_types_count") or not grid_state.tile_types_count is int:
			DebugManager.log_warn("Invalid tile_types_count, using default", "SaveManager")
			grid_state["tile_types_count"] = 5

		if not grid_state.has("active_gem_types") or not grid_state.active_gem_types is Array:
			DebugManager.log_warn("Invalid active_gem_types, using default", "SaveManager")
			grid_state["active_gem_types"] = [0, 1, 2, 3, 4]

		if not grid_state.has("grid_layout") or not grid_state.grid_layout is Array:
			DebugManager.log_warn("Invalid grid_layout, clearing saved grid", "SaveManager")
			grid_state["grid_layout"] = []

	DebugManager.log_info(
		"Permissive validation completed - data has been fixed and will be loaded", "SaveManager"
	)
	return true


func _validate_grid_state(grid_state: Dictionary) -> bool:
	# Check grid size
	if not grid_state.has("grid_size") or not grid_state.grid_size is Dictionary:
		DebugManager.log_error("Invalid grid_size in save data", "SaveManager")
		return false

	var size: Variant = grid_state.grid_size
	if not size.has("x") or not size.has("y"):
		return false

	var width: Variant = size.x
	var height: Variant = size.y
	if not width is int or not height is int:
		return false
	if width < 3 or height < 3 or width > MAX_GRID_SIZE or height > MAX_GRID_SIZE:
		DebugManager.log_error("Grid size out of bounds: %dx%d" % [width, height], "SaveManager")
		return false

	# Check tile types
	var tile_types: Variant = grid_state.get("tile_types_count", 0)
	if not tile_types is int or tile_types < 3 or tile_types > MAX_TILE_TYPES:
		DebugManager.log_error("Invalid tile_types_count: %s" % str(tile_types), "SaveManager")
		return false

	# Validate active_gem_types if present
	var active_gems: Variant = grid_state.get("active_gem_types", [])
	if not active_gems is Array:
		DebugManager.log_error("active_gem_types is not an array", "SaveManager")
		return false

	# If active_gem_types exists, validate its contents
	if active_gems.size() > 0:
		for i in range(active_gems.size()):
			var gem_type: Variant = active_gems[i]
			if not gem_type is int:
				DebugManager.log_error(
					"active_gem_types[%d] is not an integer: %s" % [i, str(gem_type)], "SaveManager"
				)
				return false
			if gem_type < 0 or gem_type >= tile_types:
				DebugManager.log_error(
					"active_gem_types[%d] out of range: %d" % [i, gem_type], "SaveManager"
				)
				return false

	# Validate grid layout if present
	var layout: Variant = grid_state.get("grid_layout", [])
	if not layout is Array:
		DebugManager.log_error("grid_layout is not an array", "SaveManager")
		return false

	if layout.size() > 0:
		return _validate_grid_layout(layout, width, height, tile_types)

	return true


func _validate_grid_layout(
	layout: Array, expected_width: int, expected_height: int, max_tile_type: int
) -> bool:
	if layout.size() != expected_height:
		DebugManager.log_error(
			"Grid layout height mismatch: %d vs %d" % [layout.size(), expected_height],
			"SaveManager"
		)
		return false

	for y in range(layout.size()):
		var row: Variant = layout[y]
		if not row is Array:
			DebugManager.log_error("Grid layout row %d is not an array" % y, "SaveManager")
			return false
		if row.size() != expected_width:
			DebugManager.log_error(
				"Grid layout row %d width mismatch: %d vs %d" % [y, row.size(), expected_width],
				"SaveManager"
			)
			return false

		for x in range(row.size()):
			var tile_type: Variant = row[x]
			if not tile_type is int:
				DebugManager.log_error(
					"Grid tile [%d][%d] is not an integer: %s" % [y, x, str(tile_type)],
					"SaveManager"
				)
				return false
			if tile_type < -1 or tile_type >= max_tile_type:
				DebugManager.log_error(
					"Grid tile [%d][%d] type out of range: %d" % [y, x, tile_type], "SaveManager"
				)
				return false

	return true


func _validate_grid_parameters(
	grid_size: Vector2i, tile_types_count: int, active_gem_types: Array, grid_layout: Array
) -> bool:
	# Validate grid size
	if (
		grid_size.x < 3
		or grid_size.y < 3
		or grid_size.x > MAX_GRID_SIZE
		or grid_size.y > MAX_GRID_SIZE
	):
		DebugManager.log_error(
			(
				"Invalid grid size: %dx%d (min 3x3, max %dx%d)"
				% [grid_size.x, grid_size.y, MAX_GRID_SIZE, MAX_GRID_SIZE]
			),
			"SaveManager"
		)
		return false

	# Validate tile types count
	if tile_types_count < 3 or tile_types_count > MAX_TILE_TYPES:
		DebugManager.log_error(
			"Invalid tile types count: %d (min 3, max %d)" % [tile_types_count, MAX_TILE_TYPES],
			"SaveManager"
		)
		return false

	# Validate active gem types
	if active_gem_types.size() != tile_types_count:
		DebugManager.log_error(
			(
				"Active gem types size mismatch: %d vs %d"
				% [active_gem_types.size(), tile_types_count]
			),
			"SaveManager"
		)
		return false

	# Validate grid layout
	return _validate_grid_layout(grid_layout, grid_size.x, grid_size.y, tile_types_count)


func _is_valid_score(score: Variant) -> bool:
	# Accept both int and float, but convert to int for validation
	if not (score is int or score is float):
		DebugManager.log_error(
			"Score is not a number: %s (type: %s)" % [str(score), typeof(score)], "SaveManager"
		)
		return false

	# Check for NaN and infinity values
	if score is float:
		if is_nan(score) or is_inf(score):
			DebugManager.log_error(
				"Score contains invalid float value (NaN/Inf): %s" % str(score), "SaveManager"
			)
			return false

	var score_int = int(score)
	if score_int < 0 or score_int > MAX_SCORE:
		DebugManager.log_error("Score out of bounds: %d" % score_int, "SaveManager")
		return false
	return true


func _merge_validated_data(loaded_data: Dictionary) -> void:
	# Safely merge only validated fields, converting floats to ints for scores
	for key in ["high_score", "current_score", "total_score"]:
		if loaded_data.has(key):
			# Use safe numeric conversion
			game_data[key] = _safe_get_numeric_value(loaded_data, key, 0)

	# Games played should always be an integer
	if loaded_data.has("games_played"):
		game_data["games_played"] = _safe_get_numeric_value(loaded_data, "games_played", 0)

	# Merge grid state carefully
	var loaded_grid: Variant = loaded_data.get("grid_state", {})
	if loaded_grid is Dictionary:
		for grid_key in ["grid_size", "tile_types_count", "active_gem_types", "grid_layout"]:
			if loaded_grid.has(grid_key):
				game_data.grid_state[grid_key] = loaded_grid[grid_key]


func _calculate_checksum(data: Dictionary) -> String:
	# Calculate deterministic checksum EXCLUDING the checksum field itself
	var data_copy: Dictionary = data.duplicate(true)
	data_copy.erase("_checksum")  # Remove checksum before calculation

	# Create deterministic checksum using sorted keys to ensure consistency
	var checksum_string: String = _create_deterministic_string(data_copy)
	return str(checksum_string.hash())


func _create_deterministic_string(data: Dictionary) -> String:
	# Create a deterministic string representation by processing keys in sorted order
	var keys: Array = data.keys()
	keys.sort()  # Ensure consistent ordering

	var parts: Array[String] = []
	for key in keys:
		var value: Variant = data[key]
		var key_str: String = str(key)
		var value_str: String = ""

		if value is Dictionary:
			value_str = _create_deterministic_string(value)
		elif value is Array:
			value_str = _create_deterministic_array_string(value)
		else:
			# CRITICAL FIX: Normalize numeric values to prevent JSON serialization type issues
			value_str = _normalize_value_for_checksum(value)

		parts.append(key_str + ":" + value_str)

	return "{" + ",".join(parts) + "}"


func _create_deterministic_array_string(arr: Array) -> String:
	# Create deterministic string representation of arrays
	var parts: Array[String] = []
	for item in arr:
		if item is Dictionary:
			parts.append(_create_deterministic_string(item))
		elif item is Array:
			parts.append(_create_deterministic_array_string(item))
		else:
			# CRITICAL FIX: Normalize array values for consistent checksum
			parts.append(_normalize_value_for_checksum(item))

	return "[" + ",".join(parts) + "]"


func _normalize_value_for_checksum(value: Variant) -> String:
	"""
	CRITICAL FIX: Normalize values for consistent checksum calculation
	This prevents JSON serialization type conversion from breaking checksums
	"""
	if value == null:
		return "null"
	elif value is bool:
		return str(value)
	elif value is int:
		# Convert to int string format to match JSON deserialized floats
		return str(int(value))
	elif value is float:
		# Convert float to int if it's a whole number (handles JSON conversion)
		if value == int(value):
			return str(int(value))
		else:
			# For actual floats, use consistent precision
			return "%.10f" % value
	elif value is String:
		return value
	else:
		return str(value)


func _validate_checksum(data: Dictionary) -> bool:
	# Validate checksum to detect tampering
	if not data.has("_checksum"):
		DebugManager.log_warn("No checksum found in save data", "SaveManager")
		return true  # Allow saves without checksum for backward compatibility

	var stored_checksum: Variant = data["_checksum"]
	var calculated_checksum: String = _calculate_checksum(data)
	var is_valid: bool = stored_checksum == calculated_checksum

	if not is_valid:
		# MIGRATION COMPATIBILITY: If this is a version 1 save file, it might have the old checksum bug
		# Try to be more lenient with existing saves to prevent data loss
		var data_version: Variant = data.get("_version", 0)
		if data_version <= 1:
			(
				DebugManager
				. log_warn(
					(
						"Checksum mismatch in v%d save file - may be due to JSON serialization issue (stored: %s, calculated: %s)"
						% [data_version, stored_checksum, calculated_checksum]
					),
					"SaveManager"
				)
			)
			(
				DebugManager
				. log_info(
					"Allowing load for backward compatibility - checksum will be recalculated on next save",
					"SaveManager"
				)
			)
			# Mark for checksum regeneration by removing the invalid one
			data.erase("_checksum")
			return true
		else:
			DebugManager.log_error(
				(
					"Checksum mismatch - stored: %s, calculated: %s"
					% [stored_checksum, calculated_checksum]
				),
				"SaveManager"
			)
			return false

	return is_valid


func _safe_get_numeric_value(data: Dictionary, key: String, default_value: float) -> int:
	"""Safely extract and convert numeric values with comprehensive validation"""
	var value: Variant = data.get(key, default_value)

	# Type validation
	if not (value is float or value is int):
		DebugManager.log_warn(
			(
				"Non-numeric value for %s: %s, using default %s"
				% [key, str(value), str(default_value)]
			),
			"SaveManager"
		)
		return int(default_value)

	# NaN/Infinity validation for floats
	if value is float:
		if is_nan(value) or is_inf(value):
			DebugManager.log_warn(
				(
					"Invalid float value for %s: %s, using default %s"
					% [key, str(value), str(default_value)]
				),
				"SaveManager"
			)
			return int(default_value)

	# Convert to integer and validate bounds
	var int_value: int = int(value)

	# Apply bounds checking based on field type
	if key in ["high_score", "current_score", "total_score"]:
		if int_value < 0 or int_value > MAX_SCORE:
			DebugManager.log_warn(
				"Score %s out of bounds: %d, using default" % [key, int_value], "SaveManager"
			)
			return int(default_value)
	elif key == "games_played":
		if int_value < 0 or int_value > MAX_GAMES_PLAYED:
			DebugManager.log_warn(
				"Games played out of bounds: %d, using default" % int_value, "SaveManager"
			)
			return int(default_value)

	return int_value


func _handle_version_migration(data: Dictionary) -> Variant:
	"""Handle save data version migration and compatibility"""
	var data_version: Variant = data.get("_version", 0)  # Default to version 0 for old saves

	if data_version == SAVE_FORMAT_VERSION:
		# Current version, no migration needed
		DebugManager.log_info(
			"Save file is current version (%d)" % SAVE_FORMAT_VERSION, "SaveManager"
		)
		return data
	elif data_version > SAVE_FORMAT_VERSION:
		# Future version - cannot handle
		DebugManager.log_error(
			(
				"Save file version (%d) is newer than supported (%d)"
				% [data_version, SAVE_FORMAT_VERSION]
			),
			"SaveManager"
		)
		return null
	else:
		# Older version - migrate
		DebugManager.log_info(
			"Migrating save data from version %d to %d" % [data_version, SAVE_FORMAT_VERSION],
			"SaveManager"
		)
		return _migrate_save_data(data, data_version)


func _migrate_save_data(data: Dictionary, from_version: int) -> Dictionary:
	"""Migrate save data from older versions to current format"""
	var migrated_data: Dictionary = data.duplicate(true)

	# Migration from version 0 (no version field) to version 1
	if from_version < 1:
		# Add new fields that didn't exist in version 0
		if not migrated_data.has("total_score"):
			migrated_data["total_score"] = 0
			DebugManager.log_info("Added total_score field during migration", "SaveManager")

		if not migrated_data.has("grid_state"):
			migrated_data["grid_state"] = {
				"grid_size": {"x": 8, "y": 8},
				"tile_types_count": 5,
				"active_gem_types": [0, 1, 2, 3, 4],
				"grid_layout": []
			}
			DebugManager.log_info("Added grid_state structure during migration", "SaveManager")

		# Ensure all numeric values are within bounds after migration
		for score_key in ["high_score", "current_score", "total_score"]:
			if migrated_data.has(score_key):
				var score_value: Variant = migrated_data[score_key]
				if score_value is float or score_value is int:
					var int_score: int = int(score_value)
					if int_score < 0 or int_score > MAX_SCORE:
						DebugManager.log_warn(
							(
								"Clamping %s during migration: %d -> %d"
								% [score_key, int_score, clamp(int_score, 0, MAX_SCORE)]
							),
							"SaveManager"
						)
						migrated_data[score_key] = clamp(int_score, 0, MAX_SCORE)

	# Future migrations would go here
	# if from_version < 2:
	#     # Migration logic for version 2

	# Update version number
	migrated_data["_version"] = SAVE_FORMAT_VERSION

	# Recalculate checksum after migration
	migrated_data["_checksum"] = _calculate_checksum(migrated_data)

	DebugManager.log_info("Save data migration completed successfully", "SaveManager")
	return migrated_data


func _create_backup() -> void:
	# Create backup of current save file
	if FileAccess.file_exists(SAVE_FILE_PATH):
		var backup_path: String = SAVE_FILE_PATH + ".backup"
		var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.READ)
		var backup: FileAccess = FileAccess.open(backup_path, FileAccess.WRITE)
		if original and backup:
			backup.store_var(original.get_var())
			backup.close()
		if original:
			original.close()


func _restore_backup_if_exists() -> bool:
	var backup_path: String = SAVE_FILE_PATH + ".backup"
	if not FileAccess.file_exists(backup_path):
		DebugManager.log_warn("No backup file found for recovery", "SaveManager")
		return false

	DebugManager.log_info("Attempting to restore from backup", "SaveManager")

	# Validate backup file size before attempting restore
	var backup_file: FileAccess = FileAccess.open(backup_path, FileAccess.READ)
	if backup_file == null:
		DebugManager.log_error("Failed to open backup file for reading", "SaveManager")
		return false

	var backup_size: int = backup_file.get_length()
	if backup_size > MAX_FILE_SIZE:
		DebugManager.log_error("Backup file too large: %d bytes" % backup_size, "SaveManager")
		backup_file.close()
		return false

	# Attempt to restore backup
	var backup_data: Variant = backup_file.get_var()
	backup_file.close()

	if backup_data == null:
		DebugManager.log_error("Backup file contains no data", "SaveManager")
		return false

	# Create new save file from backup
	var original: FileAccess = FileAccess.open(SAVE_FILE_PATH, FileAccess.WRITE)
	if original == null:
		DebugManager.log_error("Failed to create new save file from backup", "SaveManager")
		return false

	original.store_var(backup_data)
	original.close()

	DebugManager.log_info("Backup restored successfully to main save file", "SaveManager")
	# Note: The restored file will be loaded on the next game restart
	# We don't recursively load here to prevent infinite loops
	return true
