@tool
extends Node
class_name MCPClient
## WebSocket client for communication with the MCP server.
## Handles connection, reconnection, and message routing.

signal connected
signal disconnected
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)

const DEFAULT_URL := "ws://127.0.0.1:6505"
const RECONNECT_DELAY := 3.0
const MAX_RECONNECT_DELAY := 30.0
const MAX_PACKETS_PER_FRAME := 32

var socket: WebSocketPeer = WebSocketPeer.new()
var server_url: String = DEFAULT_URL
var _is_connected := false
var _reconnect_timer: Timer
var _current_reconnect_delay := RECONNECT_DELAY
var _should_reconnect := true
var _project_path: String
var _initialized := false

func _ready() -> void:
	_project_path = ProjectSettings.globalize_path("res://")

	# Create reconnect timer
	_reconnect_timer = Timer.new()
	_reconnect_timer.one_shot = true
	_reconnect_timer.timeout.connect(_on_reconnect_timer)
	add_child(_reconnect_timer)
	_initialized = true

func _process(_delta: float) -> void:
	if not _initialized:
		return
	if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
		if _is_connected:
			_handle_disconnect()
		return

	socket.poll()

	match socket.get_ready_state():
		WebSocketPeer.STATE_OPEN:
			if not _is_connected:
				_handle_connect()
			var packets_processed := 0
			while socket.get_available_packet_count() > 0 and packets_processed < MAX_PACKETS_PER_FRAME:
				_handle_message(socket.get_packet().get_string_from_utf8())
				packets_processed += 1

		WebSocketPeer.STATE_CLOSING:
			pass  # Wait for close

		WebSocketPeer.STATE_CLOSED:
			if _is_connected:
				_handle_disconnect()

func connect_to_server(url: String = DEFAULT_URL) -> void:
	server_url = url
	_should_reconnect = true
	_current_reconnect_delay = RECONNECT_DELAY
	_attempt_connection()

func disconnect_from_server() -> void:
	_should_reconnect = false
	if _reconnect_timer:
		_reconnect_timer.stop()
	if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
		socket.close()
	_is_connected = false

func _attempt_connection() -> void:
	if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
		socket.close()

	print("[MCP] Connecting to ", server_url, "...")
	var err := socket.connect_to_url(server_url)
	if err != OK:
		push_error("[MCP] Failed to connect: ", err)
		_schedule_reconnect()

func _handle_connect() -> void:
	_is_connected = true
	_current_reconnect_delay = RECONNECT_DELAY  # Reset backoff
	print("[MCP] Connected to server")

	# Send godot_ready message with project info
	_send_message({
		&"type": &"godot_ready",
		&"project_path": _project_path,
	})

	connected.emit()

func _handle_disconnect() -> void:
	_is_connected = false
	print("[MCP] Disconnected from server")
	disconnected.emit()

	if _should_reconnect:
		_schedule_reconnect()

func _schedule_reconnect() -> void:
	if not _reconnect_timer:
		return
	print("[MCP] Reconnecting in ", _current_reconnect_delay, " seconds...")
	_reconnect_timer.start(_current_reconnect_delay)
	# Exponential backoff
	_current_reconnect_delay = min(_current_reconnect_delay * 2, MAX_RECONNECT_DELAY)

func _on_reconnect_timer() -> void:
	_attempt_connection()

func _handle_message(json_string: String) -> void:
	var message = JSON.parse_string(json_string)
	if message == null:
		push_error("[MCP] Failed to parse message: ", json_string)
		return

	var msg_type: String = message.get(&"type", "")
	match msg_type:
		"ping":
			_send_message({&"type": &"pong"})
		"tool_invoke":
			var request_id: String = message.get(&"id", "")
			var tool_name: String = message.get(&"tool", "")
			var args: Dictionary = message.get(&"args", {})
			print("[MCP] Tool request: ", tool_name, " (", request_id, ")")
			tool_requested.emit(request_id, tool_name, args)
		_:
			print("[MCP] Unknown message type: ", msg_type)

func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
	var response := {
		&"type": &"tool_result",
		&"id": request_id,
		&"success": success,
	}

	if success:
		response[&"result"] = result
	else:
		response[&"error"] = error

	_send_message(response)
	print("[MCP] Sent result for ", request_id, " (success=", success, ")")

func _send_message(message: Dictionary) -> void:
	if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
		socket.send_text(JSON.stringify(message))

func is_connected_to_server() -> bool:
	return _is_connected
