## CrashReporter — 全局崩溃/异常日志 Autoload
##
## 职责：
##   1. 捕获 NOTIFICATION_CRASH（引擎级崩溃信号）并写入 user://crash_log.txt
##   2. 捕获 NOTIFICATION_WM_CLOSE_REQUEST（正常退出），记录最后一帧状态
##   3. 游戏启动时检查上次是否有未完成的崩溃日志并上报 EventBus（供 HUD 展示提示）
##
## 注意：此 Autoload 必须排在所有其他 Autoload 之前注册（project.godot [autoload] 顺序），
## 确保其他系统崩溃时本 Reporter 已初始化。
##
## 使用方式：
##   CrashReporter.log_error("模块名", "错误描述")  → 写入 crash_log.txt（不崩溃）
##   CrashReporter.log_fatal("模块名", "致命错误")  → 写入后 crash（开发模式下）

extends Node

const LOG_PATH: String = "user://crash_log.txt"
const MAX_LOG_BYTES: int = 512 * 1024  # 512KB 上限，防止无限增长

var _session_start_time: float = 0.0
var _last_wave: int = 0       # 由 WaveManager 每波更新，崩溃时记录当前波次

# ── 生命周期 ────────────────────────────────────────────────────────────────

func _ready() -> void:
	_session_start_time = Time.get_unix_time_from_system()
	_rotate_log_if_oversized()
	_check_previous_crash()

func _notification(what: int) -> void:
	match what:
		NOTIFICATION_CRASH:
			_write_crash_entry("CRASH", "引擎级崩溃（NOTIFICATION_CRASH）")
		NOTIFICATION_WM_CLOSE_REQUEST:
			_write_crash_entry("SHUTDOWN", "正常退出")
			get_tree().quit()

# ── 公共接口 ────────────────────────────────────────────────────────────────

## 记录非致命错误（push_error 级别，不终止运行）
func log_error(module: String, message: String) -> void:
	push_error("[%s] %s" % [module, message])
	_append_log("ERROR", module, message)

## 记录致命错误（开发模式下触发断言失败，发布模式下仅记录）
func log_fatal(module: String, message: String) -> void:
	_write_crash_entry("FATAL", "[%s] %s" % [module, message])
	assert(false, "[CrashReporter] Fatal: %s — %s" % [module, message])

## 由 WaveManager 调用，更新崩溃时记录的波次信息
func set_current_wave(wave: int) -> void:
	_last_wave = wave

# ── 内部实现 ─────────────────────────────────────────────────────────────────

func _write_crash_entry(level: String, reason: String) -> void:
	var elapsed: float = Time.get_unix_time_from_system() - _session_start_time
	var entry: String = (
		"\n=== %s @ %s (session +%.1fs, wave=%d) ===\n%s\n" % [
			level,
			Time.get_datetime_string_from_system(),
			elapsed,
			_last_wave,
			reason
		]
	)
	_append_log_raw(entry)

func _append_log(level: String, module: String, message: String) -> void:
	var line: String = "[%s][%s] %s — %s\n" % [
		Time.get_time_string_from_system(),
		level, module, message
	]
	_append_log_raw(line)

func _append_log_raw(text: String) -> void:
	var f := FileAccess.open(LOG_PATH, FileAccess.READ_WRITE)
	if f == null:
		f = FileAccess.open(LOG_PATH, FileAccess.WRITE)
	if f == null:
		push_warning("CrashReporter: 无法打开 %s" % LOG_PATH)
		return
	f.seek_end(0)
	f.store_string(text)

func _rotate_log_if_oversized() -> void:
	if not FileAccess.file_exists(LOG_PATH):
		return
	if FileAccess.get_file_as_bytes(LOG_PATH).size() > MAX_LOG_BYTES:
		var archived: String = LOG_PATH.replace(".txt", "_prev.txt")
		DirAccess.rename_absolute(
			ProjectSettings.globalize_path(LOG_PATH),
			ProjectSettings.globalize_path(archived)
		)

func _check_previous_crash() -> void:
	## 检查上次是否有 CRASH 或 FATAL 记录，若有则在下次启动时通过 EventBus 通知
	if not FileAccess.file_exists(LOG_PATH):
		return
	var content: String = FileAccess.get_file_as_string(LOG_PATH)
	if "=== CRASH" in content or "=== FATAL" in content:
		# 延迟到第一帧，确保 EventBus 已初始化
		call_deferred("_emit_crash_detected")

func _emit_crash_detected() -> void:
	if Engine.has_singleton("EventBus"):
		# EventID 暂用 0（特殊保留 ID），HUD 订阅后展示"上次游戏异常退出"提示
		EventBus.emit(0, {"source": "crash_reporter", "log_path": LOG_PATH})
