extends Node

## Autoload injected into the running game by the Golem MCP plugin.
## - Watches for a capture request file, takes a screenshot, saves it.
## - Captures print() output and errors to a log file for the editor to read.
## Automatically disabled in release builds.

const REQUEST_PATH := "user://golem_capture_request"
const SCREENSHOT_PATH := "user://golem_game_screenshot.jpg"
const LOG_PATH := "user://golem_game_logs.txt"
const INPUT_SEQUENCE_PATH := "user://golem_input_sequence.json"
const INPUT_DONE_PATH := "user://golem_input_done.json"
const POLL_INTERVAL := 0.1  # Check every 100ms
const LOG_FLUSH_INTERVAL := 0.2  # Write logs to disk every 200ms

var _timer: float = 0.0
var _log_timer: float = 0.0
var _active: bool = false
var _log_buffer: PackedStringArray = []
var _log_file: FileAccess

# Input simulation state
var _input_sequence: Array = []
var _input_step_index: int = 0
var _input_step_elapsed: float = 0.0
var _input_active_actions: Dictionary = {}  # action_name -> true
var _input_running: bool = false
var _input_loop_count: int = 1
var _input_loop_current: int = 0
var _input_total_elapsed: float = 0.0
var _input_steps_executed: int = 0


func _ready() -> void:
	_active = OS.is_debug_build()
	if not _active:
		set_process(false)
		return

	# Open log file (truncate on game start)
	_log_file = FileAccess.open(LOG_PATH, FileAccess.WRITE)
	if _log_file:
		_log_file.store_string("")
		_log_file.close()

	# Intercept print output via a custom logger
	# GDScript doesn't have a print hook, but we can redirect via Engine
	# The most reliable way: use a custom LogHandler via the logger API
	# For now, we hook into the message callback
	pass


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		if _log_file:
			_flush_logs()
		_release_all_actions()


func _process(delta: float) -> void:
	# --- Screenshot capture + input sequence poll ---
	_timer += delta
	if _timer >= POLL_INTERVAL:
		_timer = 0.0
		if FileAccess.file_exists(REQUEST_PATH):
			DirAccess.remove_absolute(REQUEST_PATH)
			_capture_screenshot()
		# Check for new input sequence command
		if not _input_running and FileAccess.file_exists(INPUT_SEQUENCE_PATH):
			_start_input_sequence()

	# --- Process active input sequence ---
	if _input_running:
		_process_input_step(delta)

	# --- Flush logs periodically ---
	_log_timer += delta
	if _log_timer >= LOG_FLUSH_INTERVAL:
		_log_timer = 0.0
		_flush_logs()


func _capture_screenshot() -> void:
	var viewport := get_viewport()
	if viewport == null:
		return

	var image := viewport.get_texture().get_image()
	if image == null:
		return

	var max_dim := 1280
	if image.get_width() > max_dim or image.get_height() > max_dim:
		var scale_factor: float = float(max_dim) / float(maxi(image.get_width(), image.get_height()))
		image.resize(
			int(image.get_width() * scale_factor),
			int(image.get_height() * scale_factor),
			Image.INTERPOLATE_BILINEAR
		)

	image.save_jpg(SCREENSHOT_PATH, 0.85)


## Call this from game scripts instead of print() to capture output.
## Example: GolemCapture.game_log("Player hit at position %s" % pos)
func game_log(msg: String) -> void:
	var timestamp := "%.2f" % (Time.get_ticks_msec() / 1000.0)
	_log_buffer.append("[%s] %s" % [timestamp, msg])
	# Also print normally so it shows in Godot's output panel
	print(msg)


func log_error(msg: String) -> void:
	var timestamp := "%.2f" % (Time.get_ticks_msec() / 1000.0)
	_log_buffer.append("[%s] ERROR: %s" % [timestamp, msg])
	push_error(msg)


func _flush_logs() -> void:
	if _log_buffer.size() == 0:
		return

	# Append to log file
	var file := FileAccess.open(LOG_PATH, FileAccess.READ_WRITE)
	if file == null:
		file = FileAccess.open(LOG_PATH, FileAccess.WRITE)
	if file == null:
		return

	file.seek_end()
	for line in _log_buffer:
		file.store_line(line)
	file.close()

	_log_buffer.clear()


# ------------------------------------------------------------------
# Input simulation
# ------------------------------------------------------------------

func _start_input_sequence() -> void:
	var file := FileAccess.open(INPUT_SEQUENCE_PATH, FileAccess.READ)
	if file == null:
		return
	var text := file.get_as_text()
	file.close()
	# Consume the command file immediately
	DirAccess.remove_absolute(INPUT_SEQUENCE_PATH)
	# Remove stale done file
	if FileAccess.file_exists(INPUT_DONE_PATH):
		DirAccess.remove_absolute(INPUT_DONE_PATH)

	var json := JSON.new()
	if json.parse(text) != OK:
		_write_input_done(false, "Invalid JSON: %s" % json.get_error_message())
		return

	var data: Dictionary = json.data
	_input_sequence = data.get("sequence", [])
	_input_loop_count = int(data.get("loop", 1))
	if _input_sequence.is_empty():
		_write_input_done(false, "Empty sequence")
		return

	# Validate all actions up front
	for step in _input_sequence:
		if step is Dictionary and step.has("action"):
			var action_name: String = step["action"]
			if not InputMap.has_action(action_name):
				_write_input_done(false, "Unknown action: %s" % action_name)
				return

	_input_step_index = 0
	_input_step_elapsed = 0.0
	_input_loop_current = 0
	_input_total_elapsed = 0.0
	_input_steps_executed = 0
	_input_running = true
	game_log("Input simulation started: %d steps, %d loop(s)" % [_input_sequence.size(), _input_loop_count])


func _process_input_step(delta: float) -> void:
	_input_total_elapsed += delta

	if _input_step_index >= _input_sequence.size():
		# End of sequence — check loops
		_input_loop_current += 1
		if _input_loop_current < _input_loop_count:
			_input_step_index = 0
			_input_step_elapsed = 0.0
			return
		_finish_input_sequence()
		return

	var step: Dictionary = _input_sequence[_input_step_index]
	_input_step_elapsed += delta

	if step.has("wait"):
		# Wait step — just elapsed time
		var wait_dur: float = float(step["wait"])
		if _input_step_elapsed >= wait_dur:
			_input_step_index += 1
			_input_step_elapsed = 0.0
			_input_steps_executed += 1
		return

	# Action step
	var action_name: String = step.get("action", "")
	if action_name.is_empty():
		_input_step_index += 1
		_input_step_elapsed = 0.0
		_input_steps_executed += 1
		return

	# Toggle mode: pressed true/false with no auto-release
	if step.has("pressed"):
		var pressed: bool = bool(step["pressed"])
		if pressed:
			if not _input_active_actions.has(action_name):
				Input.action_press(action_name)
				_input_active_actions[action_name] = true
		else:
			if _input_active_actions.has(action_name):
				Input.action_release(action_name)
				_input_active_actions.erase(action_name)
		_input_step_index += 1
		_input_step_elapsed = 0.0
		_input_steps_executed += 1
		return

	# Duration mode: press, hold for N seconds, then release
	var duration: float = float(step.get("duration", 0.1))
	if _input_step_elapsed <= delta + 0.001:
		# First frame of this step — press
		Input.action_press(action_name)
		_input_active_actions[action_name] = true

	if _input_step_elapsed >= duration:
		# Done holding — release and advance
		Input.action_release(action_name)
		_input_active_actions.erase(action_name)
		_input_step_index += 1
		_input_step_elapsed = 0.0
		_input_steps_executed += 1


func _finish_input_sequence() -> void:
	_release_all_actions()
	_input_running = false
	var msg := "Input simulation complete: %d steps, %.1fs" % [_input_steps_executed, _input_total_elapsed]
	game_log(msg)
	_write_input_done(true, msg)


func _release_all_actions() -> void:
	for action_name in _input_active_actions:
		if InputMap.has_action(action_name):
			Input.action_release(action_name)
	_input_active_actions.clear()


func _write_input_done(success: bool, message: String) -> void:
	var result := {"ok": success, "message": message, "steps_executed": _input_steps_executed, "elapsed": _input_total_elapsed}
	var file := FileAccess.open(INPUT_DONE_PATH, FileAccess.WRITE)
	if file:
		file.store_string(JSON.stringify(result))
		file.close()
