@tool
class_name GolemServer
extends Node

## TCP server that receives JSON-line commands from the Python MCP bridge
## and routes them to the appropriate handler.

signal client_connected
signal client_disconnected

const PORT := 3571
const MAX_MESSAGE_SIZE := 16 * 1024 * 1024  # 16 MB (screenshots can be large)

var _server: TCPServer
var _peer: StreamPeerTCP
var _buffer: String = ""
var _handlers: Dictionary = {}  # tool_name -> Callable
var _command_queue: Array[Dictionary] = []
var _deferred_responses: Array[Dictionary] = []  # {id, check: Callable, timeout, elapsed}

# References set by the plugin
var screenshot_handler: Node
var scene_handler: Node
var inspect_handler: Node
var run_handler: Node
var script_handler: Node
var project_handler: Node


func _ready() -> void:
	_server = TCPServer.new()
	var err := _server.listen(PORT, "127.0.0.1")
	if err != OK:
		push_error("GolemServer: Failed to listen on port %d (error %d)" % [PORT, err])
		return
	print("GolemServer: Listening on port %d" % PORT)


func _process(_delta: float) -> void:
	if _server == null:
		return

	# Accept new connections
	if _server.is_connection_available():
		var incoming := _server.take_connection()
		# Poll once to let the socket settle — detects immediate disconnects
		incoming.poll()
		var incoming_status := incoming.get_status()
		if incoming_status == StreamPeerTCP.STATUS_NONE or incoming_status == StreamPeerTCP.STATUS_ERROR:
			# Ghost connection (port probe, nc -z, etc.) — ignore silently
			incoming.disconnect_from_host()
		else:
			if _peer != null:
				_peer.disconnect_from_host()
			_peer = incoming
			_buffer = ""
			print("GolemServer: Client connected")
			client_connected.emit()

	# Read incoming data
	if _peer == null:
		return

	_peer.poll()
	var status := _peer.get_status()
	if status == StreamPeerTCP.STATUS_NONE or status == StreamPeerTCP.STATUS_ERROR:
		_on_peer_disconnected()
		return

	if status != StreamPeerTCP.STATUS_CONNECTED:
		return

	var available := _peer.get_available_bytes()
	if available > 0:
		var data := _peer.get_data(available)
		if data[0] == OK:
			_buffer += data[1].get_string_from_utf8()
			_process_buffer()

	# Check deferred responses (non-blocking polling)
	var i := _deferred_responses.size() - 1
	while i >= 0:
		var deferred: Dictionary = _deferred_responses[i]
		deferred["elapsed"] += _delta
		var check_fn: Callable = deferred["check"]
		var result = check_fn.call()
		if result != null:
			_send_response(deferred["id"], result)
			_deferred_responses.remove_at(i)
		elif deferred["elapsed"] >= deferred["timeout"]:
			_send_error(deferred["id"], deferred.get("timeout_msg", "Deferred operation timed out"))
			_deferred_responses.remove_at(i)
		i -= 1

	# Process queued commands (one per frame)
	if _command_queue.size() > 0:
		var cmd: Dictionary = _command_queue.pop_front()
		_execute_command(cmd)


func _on_peer_disconnected() -> void:
	print("GolemServer: Client disconnected")
	_peer = null
	_buffer = ""
	client_disconnected.emit()


func _process_buffer() -> void:
	while true:
		var newline_pos := _buffer.find("\n")
		if newline_pos == -1:
			break
		var line := _buffer.substr(0, newline_pos).strip_edges()
		_buffer = _buffer.substr(newline_pos + 1)
		if line.is_empty():
			continue
		var parsed = JSON.parse_string(line)
		if parsed == null:
			push_warning("GolemServer: Invalid JSON: %s" % line)
			continue
		_command_queue.append(parsed)


# ------------------------------------------------------------------
# Command routing
# ------------------------------------------------------------------

func register_handler(tool_name: String, callback: Callable) -> void:
	_handlers[tool_name] = callback


func defer_response(id: String, check_fn: Callable, timeout: float = 5.0, timeout_msg: String = "Operation timed out") -> void:
	"""Register a deferred response. check_fn is called every frame — return a
	Dictionary to send the response, or null to keep waiting."""
	_deferred_responses.append({
		"id": id,
		"check": check_fn,
		"timeout": timeout,
		"timeout_msg": timeout_msg,
		"elapsed": 0.0,
	})


func _execute_command(cmd: Dictionary) -> void:
	var id: String = cmd.get("id", "")
	var tool_name: String = cmd.get("tool", "")
	var args: Dictionary = cmd.get("args", {})

	# Built-in ping
	if tool_name == "ping":
		_send_response(id, {"ok": true, "result": "pong", "type": "text"})
		return

	if not _handlers.has(tool_name):
		_send_error(id, "Unknown tool: %s" % tool_name)
		return

	var handler: Callable = _handlers[tool_name]

	# Call handler — if it returns null or crashes, we still send an error response
	var result = handler.call(args)

	if result == null:
		_send_error(id, "Handler returned null for tool: %s (possible crash)" % tool_name)
	elif result is Dictionary:
		if result.get("_deferred"):
			# Handler wants async polling — register a deferred check
			defer_response(
				id,
				result["_check_fn"],
				result.get("_timeout", 5.0),
				result.get("_timeout_msg", "Operation timed out"),
			)
		else:
			_send_response(id, result)
	else:
		_send_error(id, "Handler returned invalid result for tool: %s" % tool_name)


# ------------------------------------------------------------------
# Response helpers
# ------------------------------------------------------------------

func _send_response(id: String, data: Dictionary) -> void:
	if _peer == null or _peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
		return
	data["id"] = id
	if not data.has("ok"):
		data["ok"] = true
	var json_str := JSON.stringify(data) + "\n"
	var buffer := json_str.to_utf8_buffer()

	# Send in chunks to avoid overwhelming the TCP socket buffer
	var chunk_size := 65536  # 64 KB
	var offset := 0
	while offset < buffer.size():
		if _peer == null or _peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
			push_warning("GolemServer: Connection lost during send")
			return
		var end := mini(offset + chunk_size, buffer.size())
		var chunk := buffer.slice(offset, end)
		var err := _peer.put_data(chunk)
		if err != OK:
			push_warning("GolemServer: put_data failed (error %d)" % err)
			return
		offset = end


func _send_error(id: String, error_msg: String) -> void:
	_send_response(id, {"ok": false, "error": error_msg})


# ------------------------------------------------------------------
# Cleanup
# ------------------------------------------------------------------

func stop() -> void:
	if _peer != null:
		_peer.disconnect_from_host()
		_peer = null
	if _server != null:
		_server.stop()
		_server = null
	print("GolemServer: Stopped")
