@tool
extends EditorPlugin

const AUTOLOAD_NAME := "GolemCapture"
const GITHUB_REPO := "Haidy-ID/golem-mcp"
const UPDATE_CHECK_INTERVAL := 86400  # 24h in seconds
const UPDATE_CHECK_FILE := "user://golem_last_update_check.txt"

var _server: GolemServer
var _screenshot_handler: Node
var _scene_handler: Node
var _inspect_handler: Node
var _run_handler: Node
var _script_handler: Node
var _project_handler: Node

var _http_check: HTTPRequest
var _http_download: HTTPRequest
var _update_dialog: AcceptDialog
var _local_version: String
var _remote_version: String
var _download_url: String


func _enter_tree() -> void:
	# Register the game capture autoload
	if not ProjectSettings.has_setting("autoload/" + AUTOLOAD_NAME):
		var script_path := (get_script() as Script).resource_path.get_base_dir() + "/golem_capture.gd"
		add_autoload_singleton(AUTOLOAD_NAME, script_path)

	# Create server
	_server = preload("golem_server.gd").new()
	_server.name = "GolemServer"
	add_child(_server)

	# Create and register handlers
	_screenshot_handler = preload("handlers/screenshot_handler.gd").new()
	_screenshot_handler.name = "ScreenshotHandler"
	_server.add_child(_screenshot_handler)
	_server.screenshot_handler = _screenshot_handler
	_screenshot_handler.register(_server)

	_scene_handler = preload("handlers/scene_handler.gd").new()
	_scene_handler.name = "SceneHandler"
	_server.add_child(_scene_handler)
	_server.scene_handler = _scene_handler
	_scene_handler.register(_server)

	_inspect_handler = preload("handlers/inspect_handler.gd").new()
	_inspect_handler.name = "InspectHandler"
	_server.add_child(_inspect_handler)
	_server.inspect_handler = _inspect_handler
	_inspect_handler.register(_server)

	_run_handler = preload("handlers/run_handler.gd").new()
	_run_handler.name = "RunHandler"
	_server.add_child(_run_handler)
	_server.run_handler = _run_handler
	_run_handler.register(_server)

	_script_handler = preload("handlers/script_handler.gd").new()
	_script_handler.name = "ScriptHandler"
	_server.add_child(_script_handler)
	_server.script_handler = _script_handler
	_script_handler.register(_server)

	_project_handler = preload("handlers/project_handler.gd").new()
	_project_handler.name = "ProjectHandler"
	_server.add_child(_project_handler)
	_server.project_handler = _project_handler
	_project_handler.register(_server)

	print("Golem MCP: Plugin loaded — all handlers registered")

	_local_version = _get_local_version()
	_maybe_check_updates()


func _exit_tree() -> void:
	if _server:
		_server.stop()
		_server.queue_free()
		_server = null

	if _http_check and is_instance_valid(_http_check):
		_http_check.queue_free()
		_http_check = null

	if _http_download and is_instance_valid(_http_download):
		_http_download.queue_free()
		_http_download = null

	if _update_dialog and is_instance_valid(_update_dialog):
		_update_dialog.queue_free()
		_update_dialog = null

	# Remove autoload
	if ProjectSettings.has_setting("autoload/" + AUTOLOAD_NAME):
		remove_autoload_singleton(AUTOLOAD_NAME)

	print("Golem MCP: Plugin unloaded")


# ------------------------------------------------------------------
# Version helpers
# ------------------------------------------------------------------

func _get_local_version() -> String:
	var plugin_dir := (get_script() as Script).resource_path.get_base_dir()
	var version_path := plugin_dir + "/version.txt"
	if FileAccess.file_exists(version_path):
		var f := FileAccess.open(version_path, FileAccess.READ)
		if f:
			return f.get_as_text().strip_edges()

	# Fallback: read plugin.cfg version
	var cfg_path := plugin_dir + "/plugin.cfg"
	var cfg := ConfigFile.new()
	if cfg.load(cfg_path) == OK:
		return cfg.get_value("plugin", "version", "0.0.0")

	return "0.0.0"


func _parse_semver(v: String) -> Array[int]:
	var clean := v.strip_edges().lstrip("v")
	var parts := clean.split(".")
	var result: Array[int] = []
	for i in range(3):
		if i < parts.size():
			result.append(parts[i].to_int())
		else:
			result.append(0)
	return result


func _is_newer(remote: String, local: String) -> bool:
	var r := _parse_semver(remote)
	var l := _parse_semver(local)
	for i in range(3):
		if r[i] > l[i]:
			return true
		if r[i] < l[i]:
			return false
	return false


# ------------------------------------------------------------------
# Update check (24h interval)
# ------------------------------------------------------------------

func _maybe_check_updates() -> void:
	var now := int(Time.get_unix_time_from_system())

	if FileAccess.file_exists(UPDATE_CHECK_FILE):
		var f := FileAccess.open(UPDATE_CHECK_FILE, FileAccess.READ)
		if f:
			var last_check := f.get_as_text().strip_edges().to_int()
			if now - last_check < UPDATE_CHECK_INTERVAL:
				return

	_do_update_check()

	# Save timestamp
	var f := FileAccess.open(UPDATE_CHECK_FILE, FileAccess.WRITE)
	if f:
		f.store_string(str(now))


func _do_update_check() -> void:
	_http_check = HTTPRequest.new()
	_http_check.name = "GolemUpdateCheck"
	add_child(_http_check)
	_http_check.request_completed.connect(_on_check_completed)

	var url := "https://api.github.com/repos/%s/releases/latest" % GITHUB_REPO
	var headers := ["Accept: application/vnd.github.v3+json", "User-Agent: GolemMCP"]
	var err := _http_check.request(url, headers)
	if err != OK:
		print("Golem MCP: Update check failed (HTTP error %d)" % err)
		_cleanup_http_check()


func _on_check_completed(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
	_cleanup_http_check()

	if result != HTTPRequest.RESULT_SUCCESS or code != 200:
		# Silent fail — no network or no releases yet
		return

	var json := JSON.new()
	if json.parse(body.get_string_from_utf8()) != OK:
		return

	var data: Dictionary = json.data
	if not data.has("tag_name"):
		return

	_remote_version = data["tag_name"]
	if not _is_newer(_remote_version, _local_version):
		return

	# Find zip asset
	_download_url = ""
	if data.has("assets") and data["assets"] is Array:
		for asset in data["assets"]:
			if asset is Dictionary and asset.get("name", "").ends_with(".zip"):
				_download_url = asset["browser_download_url"]
				break

	if _download_url.is_empty():
		# Fallback: use zipball_url
		_download_url = data.get("zipball_url", "")

	if _download_url.is_empty():
		return

	_show_update_dialog()


func _cleanup_http_check() -> void:
	if _http_check and is_instance_valid(_http_check):
		_http_check.queue_free()
		_http_check = null


# ------------------------------------------------------------------
# Update dialog
# ------------------------------------------------------------------

func _show_update_dialog() -> void:
	_update_dialog = AcceptDialog.new()
	_update_dialog.title = "Golem MCP Update"
	_update_dialog.dialog_text = "Golem MCP %s available (current: %s)\n\nUpdate now?" % [_remote_version, _local_version]
	_update_dialog.ok_button_text = "Update"
	_update_dialog.add_cancel_button("Later")
	_update_dialog.confirmed.connect(_on_update_confirmed)
	EditorInterface.get_base_control().add_child(_update_dialog)
	_update_dialog.popup_centered()


func _on_update_confirmed() -> void:
	if _update_dialog and is_instance_valid(_update_dialog):
		_update_dialog.queue_free()
		_update_dialog = null
	_download_update()


# ------------------------------------------------------------------
# Download and extract
# ------------------------------------------------------------------

func _download_update() -> void:
	_http_download = HTTPRequest.new()
	_http_download.name = "GolemUpdateDownload"
	_http_download.download_file = OS.get_user_data_dir() + "/golem_update.zip"
	add_child(_http_download)
	_http_download.request_completed.connect(_on_download_completed)

	var headers := ["User-Agent: GolemMCP"]
	var err := _http_download.request(_download_url, headers)
	if err != OK:
		print("Golem MCP: Download failed (HTTP error %d)" % err)
		_cleanup_http_download()


func _on_download_completed(result: int, code: int, _headers: PackedStringArray, _body: PackedByteArray) -> void:
	_cleanup_http_download()

	if result != HTTPRequest.RESULT_SUCCESS or (code != 200 and code != 302):
		print("Golem MCP: Download failed (result=%d, code=%d)" % [result, code])
		return

	var zip_path: String = OS.get_user_data_dir() + "/golem_update.zip"
	if not FileAccess.file_exists(zip_path):
		print("Golem MCP: Downloaded file not found")
		return

	_extract_update(zip_path)


func _extract_update(zip_path: String) -> void:
	var reader := ZIPReader.new()
	if reader.open(zip_path) != OK:
		print("Golem MCP: Cannot open update zip")
		return

	var plugin_dir := (get_script() as Script).resource_path.get_base_dir()
	var project_root := ProjectSettings.globalize_path("res://")
	var skills_dir := project_root + ".claude/commands/"
	var files := reader.get_files()
	var updated_count := 0

	for file_path in files:
		if file_path.ends_with("/"):
			continue

		var content := reader.read_file(file_path)
		if content.is_empty():
			continue

		# Normalize: strip top-level directory from zip
		var parts := file_path.split("/", false)
		if parts.size() < 2:
			continue

		# Remove first part (e.g. "golem-mcp-v0.2.0/")
		var relative := "/".join(parts.slice(1))

		# Route files to correct destinations
		var dest_path := ""
		if relative.begins_with("plugin/"):
			var sub := relative.substr(7)  # strip "plugin/"
			dest_path = plugin_dir + "/" + sub
		elif relative.begins_with(".claude/commands/"):
			var sub := relative.substr(17)  # strip ".claude/commands/"
			dest_path = skills_dir + sub
		elif relative == "version.txt":
			dest_path = plugin_dir + "/version.txt"
		else:
			continue

		# Ensure parent directory exists
		var global_dest := ProjectSettings.globalize_path(dest_path) if dest_path.begins_with("res://") else dest_path
		var dir := DirAccess.open("res://")
		var parent := dest_path.get_base_dir()
		if not DirAccess.dir_exists_absolute(parent if not parent.begins_with("res://") else ProjectSettings.globalize_path(parent)):
			DirAccess.make_dir_recursive_absolute(parent if not parent.begins_with("res://") else ProjectSettings.globalize_path(parent))

		var f := FileAccess.open(dest_path, FileAccess.WRITE)
		if f:
			f.store_buffer(content)
			updated_count += 1

	reader.close()

	# Cleanup zip
	DirAccess.remove_absolute(zip_path)

	if updated_count > 0:
		print("Golem MCP: Updated %d files to %s" % [updated_count, _remote_version])
		_show_reload_dialog()
	else:
		print("Golem MCP: Update extracted 0 files — zip structure may be unexpected")


func _show_reload_dialog() -> void:
	var dialog := AcceptDialog.new()
	dialog.title = "Golem MCP Updated"
	dialog.dialog_text = "Golem MCP updated to %s.\n\nPlease reload the project for changes to take effect.\n(Project > Reload Current Project)" % _remote_version
	dialog.ok_button_text = "OK"
	EditorInterface.get_base_control().add_child(dialog)
	dialog.popup_centered()


func _cleanup_http_download() -> void:
	if _http_download and is_instance_valid(_http_download):
		_http_download.queue_free()
		_http_download = null
