#include "DruidsMCPBridgeAISetup.h"

#include "DruidsMCPBridge.h"
#include "Dom/JsonObject.h"
#include "Framework/Application/SlateApplication.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/PlatformMisc.h"
#include "HAL/PlatformFilemanager.h"
#include "HAL/PlatformProcess.h"
#include "Misc/DateTime.h"
#include "Misc/FileHelper.h"
#include "Misc/MessageDialog.h"
#include "Misc/Paths.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "Styling/CoreStyle.h"
#include "Brushes/SlateColorBrush.h"
#include "Styling/SlateTypes.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/SWindow.h"
#include "Widgets/Text/STextBlock.h"

#define LOCTEXT_NAMESPACE "DruidsMCPBridgeAISetup"

namespace DruidsMCPBridgeAISetup
{
enum class EClientSetupMode
{
	Cli,
	JsonConfig,
	Manual
};

struct FClientProfile
{
	FString Id;
	FString DisplayName;
	FString TransportLabel;
	FString SetupCommand;
	FString SetupArgsTemplate;
	FString VerifyCommand;
	FString VerifyArgs;
	FString JsonConfigPath;
	FString JsonEndpointKey;
	FString SnippetTemplate;
	FString DocsUrl;
	EClientSetupMode SetupMode = EClientSetupMode::Manual;
};

struct FClientSetupState
{
	FString Status = TEXT("Ready");
	FString Detail;
	FString LastOutput;
};

static FString GetUserHomeDirectory()
{
#if PLATFORM_WINDOWS
	FString Home = FPlatformMisc::GetEnvironmentVariable(TEXT("USERPROFILE"));
	if (Home.IsEmpty())
	{
		const FString HomeDrive = FPlatformMisc::GetEnvironmentVariable(TEXT("HOMEDRIVE"));
		const FString HomePath = FPlatformMisc::GetEnvironmentVariable(TEXT("HOMEPATH"));
		if (!HomeDrive.IsEmpty() && !HomePath.IsEmpty())
		{
			Home = HomeDrive / HomePath;
		}
	}
#else
	FString Home = FPlatformMisc::GetEnvironmentVariable(TEXT("HOME"));
#endif
	if (Home.IsEmpty())
	{
		Home = FPlatformProcess::UserDir();
	}

	return FPaths::ConvertRelativePathToFull(Home);
}

static FString ExpandUserPath(const FString& Path)
{
	if (Path == TEXT("~"))
	{
		return GetUserHomeDirectory();
	}

	if (Path.StartsWith(TEXT("~/")) || Path.StartsWith(TEXT("~\\")))
	{
		return FPaths::Combine(GetUserHomeDirectory(), Path.RightChop(2));
	}

	return FPaths::ConvertRelativePathToFull(Path);
}

static FString QuoteArg(const FString& Value)
{
	if (Value.Contains(TEXT(" ")) || Value.Contains(TEXT("\t")) || Value.Contains(TEXT("\"")))
	{
		FString Escaped = Value.Replace(TEXT("\""), TEXT("\\\""));
		return FString::Printf(TEXT("\"%s\""), *Escaped);
	}

	return Value;
}

static FString FormatTemplate(FString Template, const FString& EndpointUrl)
{
	Template.ReplaceInline(TEXT("{endpoint}"), *EndpointUrl, ESearchCase::CaseSensitive);
	return Template;
}

static bool ExecProcess(const FString& Command, const FString& Args, int32& ReturnCode, FString& StdOut, FString& StdErr)
{
	ReturnCode = INDEX_NONE;
	StdOut.Reset();
	StdErr.Reset();
	return FPlatformProcess::ExecProcess(*Command, *Args, &ReturnCode, &StdOut, &StdErr);
}

static bool ExecCliProcess(const FString& ExecutablePath, const FString& Args, int32& ReturnCode, FString& StdOut, FString& StdErr)
{
#if PLATFORM_WINDOWS
	FString ShellPath = FPlatformMisc::GetEnvironmentVariable(TEXT("ComSpec"));
	if (ShellPath.IsEmpty())
	{
		ShellPath = TEXT("C:\\Windows\\System32\\cmd.exe");
	}

	const FString ShellArgs = FString::Printf(TEXT("/S /C \"%s %s\""), *QuoteArg(ExecutablePath), *Args);
	return ExecProcess(ShellPath, ShellArgs, ReturnCode, StdOut, StdErr);
#else
	return ExecProcess(ExecutablePath, Args, ReturnCode, StdOut, StdErr);
#endif
}

static bool FindExecutableOnPath(const FString& Command, FString& OutExecutablePath)
{
#if PLATFORM_WINDOWS
	const FString LookupCommand = TEXT("where.exe");
	const FString LookupArgs = Command;
#else
	const FString LookupCommand = TEXT("/usr/bin/which");
	const FString LookupArgs = Command;
#endif

	int32 ReturnCode = INDEX_NONE;
	FString StdOut;
	FString StdErr;
	if (!ExecProcess(LookupCommand, LookupArgs, ReturnCode, StdOut, StdErr) || ReturnCode != 0)
	{
		return false;
	}

	TArray<FString> Lines;
	StdOut.ParseIntoArrayLines(Lines, true);
	for (const FString& Line : Lines)
	{
		const FString Candidate = Line.TrimStartAndEnd();
		if (!Candidate.IsEmpty())
		{
			OutExecutablePath = Candidate;
			return true;
		}
	}

	return false;
}

static FString BuildCodexSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("[mcp_servers.druids-unreal]\nurl = \"%s\"\nenabled = true"),
		*EndpointUrl);
}

static FString BuildGeminiSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("{\n  \"mcpServers\": {\n    \"druids-unreal\": {\n      \"httpUrl\": \"%s\",\n      \"trust\": false\n    }\n  }\n}"),
		*EndpointUrl);
}

static FString BuildAntigravitySnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("{\n  \"mcpServers\": {\n    \"druids-unreal\": {\n      \"httpUrl\": \"%s\"\n    }\n  }\n}"),
		*EndpointUrl);
}

static FString BuildClaudeCodeSnippet(const FString& EndpointUrl)
{
	return FString::Printf(TEXT("claude mcp add --transport http druids-unreal %s"), *EndpointUrl);
}

static FString BuildCursorSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("{\n  \"mcpServers\": {\n    \"druids-unreal\": {\n      \"url\": \"%s\"\n    }\n  }\n}"),
		*EndpointUrl);
}

static FString BuildJetBrainsSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("{\n  \"mcpServers\": {\n    \"druids-unreal\": {\n      \"transport\": \"http\",\n      \"url\": \"%s\"\n    }\n  }\n}"),
		*EndpointUrl);
}

static FString BuildBobSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("{\n  \"mcpServers\": {\n    \"druids-unreal\": {\n      \"url\": \"%s\"\n    }\n  }\n}\n\nBeginner checklist:\n1. Make sure Unreal Editor is open and Druids MCP Bridge is running on this machine.\n2. In IBM Bob, open MCP Servers settings.\n3. Add this server at the project level, for example in .bob/mcp.json if your Bob version uses project JSON config.\n4. Restart or refresh Bob so it reloads MCP servers.\n5. This connects directly to the same local Druids MCP Bridge endpoint used by Codex and other clients.\n\nFirst prompt to run in Bob:\nUse Druids MCP Bridge. First call unreal_get_status, then call unreal_get_capabilities. Then call unreal_recommend_tools with my task before deciding what to do. If the visible tool list seems incomplete, search/list tools again before saying Druids cannot do something.\n\nLocal endpoint reminder:\nThis endpoint is local to the user's machine. Bob must run where it can reach the local Unreal Editor endpoint: %s"),
		*EndpointUrl,
		*EndpointUrl);
}

static FString BuildJulesSnippet(const FString& EndpointUrl)
{
	return FString::Printf(
		TEXT("Jules task packet for Druids MCP Bridge local validation\n\nContext:\n- Jules works through cloud/GitHub-oriented coding tasks and should not assume it can reach the user's local Unreal endpoint.\n- Do not call localhost or 127.0.0.1 from Jules as if it were the user's machine.\n- The user will validate locally in Unreal through Druids MCP Bridge at: %s\n- Existing direct MCP clients such as Codex, Gemini, Antigravity, Bob, Claude, Cursor, and Rider are unchanged.\n\nCopyable Jules task packet:\nGoal:\n<Describe the Unreal-facing feature or content change.>\n\nRepository work for Jules:\n1. Make code/content changes in the GitHub repository only.\n2. Do not assume direct access to Unreal Editor, localhost, 127.0.0.1, or the Druids MCP endpoint.\n3. Do not request a Druids-hosted relay, tunnel, cloud service, token, or secret.\n4. Return changed files, implementation notes, expected Unreal validation steps, and any unsupported assumptions.\n\nLocal validation packet for the Unreal-capable agent:\n1. Open Unreal locally and make sure Druids MCP Bridge is running at: %s\n2. Call unreal_get_status.\n3. Call unreal_get_capabilities.\n4. Call unreal_recommend_tools with the original user task.\n5. Use the recommended Druids tools to validate assets, graphs, materials, PCG output, screenshots, logs, and known unsupported boundaries.\n6. Report what was validated locally versus what Jules only changed in the repository."),
		*EndpointUrl,
		*EndpointUrl);
}

static TArray<FClientProfile> BuildClientProfiles()
{
	TArray<FClientProfile> Profiles;

	Profiles.Add({
		TEXT("codex"),
		TEXT("Codex"),
		TEXT("Streamable HTTP"),
		TEXT("codex"),
		TEXT("mcp add druids-unreal --url {endpoint}"),
		TEXT("codex"),
		TEXT("mcp list"),
		TEXT(""),
		TEXT("url"),
		TEXT(""),
		TEXT("https://developers.openai.com/codex/mcp"),
		EClientSetupMode::Cli
	});

	Profiles.Add({
		TEXT("gemini"),
		TEXT("Gemini CLI"),
		TEXT("Streamable HTTP"),
		TEXT("gemini"),
		TEXT("mcp add --scope user --transport http druids-unreal {endpoint}"),
		TEXT("gemini"),
		TEXT("mcp list"),
		ExpandUserPath(TEXT("~/.gemini/settings.json")),
		TEXT("httpUrl"),
		TEXT(""),
		TEXT("https://geminicli.com/docs/tools/mcp-server/"),
		EClientSetupMode::Cli
	});

	Profiles.Add({
		TEXT("antigravity"),
		TEXT("Google Antigravity"),
		TEXT("Streamable HTTP"),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		ExpandUserPath(TEXT("~/.gemini/config/mcp_config.json")),
		TEXT("httpUrl"),
		TEXT(""),
		TEXT("https://antigravity.google/docs/mcp"),
		EClientSetupMode::JsonConfig
	});

	Profiles.Add({
		TEXT("claude-code"),
		TEXT("Claude Code"),
		TEXT("HTTP"),
		TEXT("claude"),
		TEXT("mcp add --transport http druids-unreal {endpoint}"),
		TEXT("claude"),
		TEXT("mcp list"),
		TEXT(""),
		TEXT("url"),
		TEXT(""),
		TEXT("https://docs.anthropic.com/"),
		EClientSetupMode::Cli
	});

	Profiles.Add({
		TEXT("cursor"),
		TEXT("Cursor"),
		TEXT("HTTP"),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT("url"),
		TEXT(""),
		TEXT("https://docs.cursor.com/"),
		EClientSetupMode::Manual
	});

	Profiles.Add({
		TEXT("jetbrains-rider"),
		TEXT("JetBrains / Rider"),
		TEXT("HTTP"),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT("url"),
		TEXT(""),
		TEXT("https://www.jetbrains.com/help/"),
		EClientSetupMode::Manual
	});

	Profiles.Add({
		TEXT("ibm-bob"),
		TEXT("IBM Bob"),
		TEXT("Manual setup"),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT("url"),
		TEXT(""),
		TEXT("https://www.ibm.com/think/tutorials/mcp-integration-ibm-bob"),
		EClientSetupMode::Manual
	});

	Profiles.Add({
		TEXT("google-jules"),
		TEXT("Google Jules"),
		TEXT("Async / local validation"),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT(""),
		TEXT("https://developers.google.com/jules/api"),
		EClientSetupMode::Manual
	});

	return Profiles;
}

static FString BuildSnippet(const FClientProfile& Profile, const FString& EndpointUrl)
{
	if (Profile.Id == TEXT("codex"))
	{
		return BuildCodexSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("gemini"))
	{
		return BuildGeminiSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("antigravity"))
	{
		return BuildAntigravitySnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("claude-code"))
	{
		return BuildClaudeCodeSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("cursor"))
	{
		return BuildCursorSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("jetbrains-rider"))
	{
		return BuildJetBrainsSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("ibm-bob"))
	{
		return BuildBobSnippet(EndpointUrl);
	}
	if (Profile.Id == TEXT("google-jules"))
	{
		return BuildJulesSnippet(EndpointUrl);
	}

	return FormatTemplate(Profile.SnippetTemplate, EndpointUrl);
}

static bool UpdateJsonMcpConfig(const FClientProfile& Profile, const FString& EndpointUrl, FString& OutDetail)
{
	const FString ConfigPath = ExpandUserPath(Profile.JsonConfigPath);
	const FString ConfigDirectory = FPaths::GetPath(ConfigPath);
	IFileManager::Get().MakeDirectory(*ConfigDirectory, true);

	FString ExistingJson;
	TSharedPtr<FJsonObject> Root = MakeShared<FJsonObject>();
	if (FPaths::FileExists(ConfigPath))
	{
		if (!FFileHelper::LoadFileToString(ExistingJson, *ConfigPath))
		{
			OutDetail = FString::Printf(TEXT("Could not read %s"), *ConfigPath);
			return false;
		}

		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ExistingJson);
		if (!FJsonSerializer::Deserialize(Reader, Root) || !Root.IsValid())
		{
			OutDetail = FString::Printf(TEXT("Config malformed; left untouched: %s"), *ConfigPath);
			return false;
		}

		const FString BackupPath = FString::Printf(
			TEXT("%s.druids-backup-%s"),
			*ConfigPath,
			*FDateTime::Now().ToString(TEXT("%Y%m%d-%H%M%S")));
		if (IFileManager::Get().Copy(*BackupPath, *ConfigPath, true, true) != COPY_OK)
		{
			OutDetail = FString::Printf(TEXT("Could not create backup %s"), *BackupPath);
			return false;
		}
	}

	TSharedPtr<FJsonObject> McpServers;
	if (Root->HasTypedField<EJson::Object>(TEXT("mcpServers")))
	{
		McpServers = Root->GetObjectField(TEXT("mcpServers"));
	}
	else
	{
		McpServers = MakeShared<FJsonObject>();
		Root->SetObjectField(TEXT("mcpServers"), McpServers);
	}

	TSharedPtr<FJsonObject> Server = MakeShared<FJsonObject>();
	const FString EndpointKey = Profile.JsonEndpointKey.IsEmpty() ? TEXT("url") : Profile.JsonEndpointKey;
	Server->SetStringField(EndpointKey, EndpointUrl);
	McpServers->SetObjectField(TEXT("druids-unreal"), Server);

	FString OutputJson;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputJson);
	if (!FJsonSerializer::Serialize(Root.ToSharedRef(), Writer))
	{
		OutDetail = TEXT("Could not serialize updated JSON config");
		return false;
	}

	const FString TempPath = ConfigPath + TEXT(".tmp");
	if (!FFileHelper::SaveStringToFile(OutputJson, *TempPath))
	{
		OutDetail = FString::Printf(TEXT("Could not write temp config %s"), *TempPath);
		return false;
	}

	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	if (FPaths::FileExists(ConfigPath) && !PlatformFile.DeleteFile(*ConfigPath))
	{
		OutDetail = FString::Printf(TEXT("Could not replace %s"), *ConfigPath);
		return false;
	}

	if (!PlatformFile.MoveFile(*ConfigPath, *TempPath))
	{
		OutDetail = FString::Printf(TEXT("Could not move temp config into place: %s"), *ConfigPath);
		return false;
	}

	OutDetail = FString::Printf(TEXT("Updated %s. Restart or refresh the AI client."), *ConfigPath);
	return true;
}

static bool ConfirmJsonConfigWrite(const FClientProfile& Profile, const FString& EndpointUrl)
{
	const FString ConfigPath = ExpandUserPath(Profile.JsonConfigPath);
	const FString Preview = FString::Printf(
		TEXT("Druids MCP Bridge will update only the `druids-unreal` MCP server entry in:\n\n%s\n\nA timestamped backup will be created first.\n\nEntry:\n%s"),
		*ConfigPath,
		*BuildSnippet(Profile, EndpointUrl));

	return FMessageDialog::Open(EAppMsgType::OkCancel, FText::FromString(Preview)) == EAppReturnType::Ok;
}

class SDruidsMCPBridgeAISetupWindow : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SDruidsMCPBridgeAISetupWindow) {}
		SLATE_ARGUMENT(FDruidsMCPBridgeAISetup::FEnsureBridgeRunning, EnsureBridgeRunning)
		SLATE_ARGUMENT(FDruidsMCPBridgeAISetup::FGetEndpointUrl, GetEndpointUrl)
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs)
	{
		EnsureBridgeRunning = InArgs._EnsureBridgeRunning;
		GetEndpointUrl = InArgs._GetEndpointUrl;
		Profiles = BuildClientProfiles();
		InitializePrimaryButtonStyle();

		for (const FClientProfile& Profile : Profiles)
		{
			States.Add(Profile.Id, MakeShared<FClientSetupState>());
		}

		RefreshEndpoint();
		RefreshDetectedStatus();

		ChildSlot
		[
			SNew(SVerticalBox)
			+ SVerticalBox::Slot()
			.AutoHeight()
			.Padding(12.0f, 10.0f)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Title", "Connect AI Assistant"))
				.Font(FCoreStyle::GetDefaultFontStyle("Bold", 18))
			]
			+ SVerticalBox::Slot()
			.AutoHeight()
			.Padding(12.0f, 0.0f, 12.0f, 8.0f)
			[
				SNew(STextBlock)
				.Text(LOCTEXT("Intro", "Start the local MCP bridge, then configure an AI client to use the Druids Unreal tools. Automatic setup only writes verified local MCP config entries and never stores secrets."))
				.AutoWrapText(true)
			]
			+ SVerticalBox::Slot()
			.AutoHeight()
			.Padding(12.0f, 0.0f, 12.0f, 12.0f)
			[
				SNew(SHorizontalBox)
				+ SHorizontalBox::Slot()
				.FillWidth(1.0f)
				[
					SAssignNew(EndpointTextBox, SEditableTextBox)
					.IsReadOnly(true)
					.Text(this, &SDruidsMCPBridgeAISetupWindow::GetEndpointText)
				]
				+ SHorizontalBox::Slot()
				.AutoWidth()
				.Padding(8.0f, 0.0f, 0.0f, 0.0f)
				[
					SNew(SButton)
					.Text(LOCTEXT("CopyEndpoint", "Copy Endpoint"))
					.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::CopyEndpoint)
				]
				+ SHorizontalBox::Slot()
				.AutoWidth()
				.Padding(8.0f, 0.0f, 0.0f, 0.0f)
				[
					SNew(SButton)
					.Text(LOCTEXT("Refresh", "Refresh"))
					.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::Refresh)
				]
			]
			+ SVerticalBox::Slot()
			.FillHeight(1.0f)
			.Padding(12.0f, 0.0f, 12.0f, 12.0f)
			[
				SNew(SScrollBox)
				+ SScrollBox::Slot()
				[
					BuildClientList()
				]
			]
		];
	}

private:
	void RefreshEndpoint()
	{
		if (GetEndpointUrl.IsBound())
		{
			EndpointUrl = GetEndpointUrl.Execute();
		}

		if (EndpointUrl.IsEmpty())
		{
			EndpointUrl = TEXT("http://127.0.0.1:8765/mcp");
		}
	}

	void RefreshDetectedStatus()
	{
		for (const FClientProfile& Profile : Profiles)
		{
			TSharedPtr<FClientSetupState> State = States.FindRef(Profile.Id);
			if (!State.IsValid())
			{
				continue;
			}

			if (Profile.SetupMode == EClientSetupMode::Cli)
			{
				FString ExecutablePath;
				if (FindExecutableOnPath(Profile.SetupCommand, ExecutablePath))
				{
					State->Status = TEXT("Ready");
					State->Detail = FString::Printf(TEXT("Found %s at %s"), *Profile.SetupCommand, *ExecutablePath);
				}
				else if (!Profile.JsonConfigPath.IsEmpty())
				{
					State->Status = TEXT("Manual step required");
					State->Detail = FString::Printf(TEXT("%s was not found on PATH. Use the snippet or config file fallback."), *Profile.SetupCommand);
				}
				else
				{
					State->Status = TEXT("CLI not found");
					State->Detail = FString::Printf(TEXT("%s was not found on PATH. Copy the setup text instead."), *Profile.SetupCommand);
				}
			}
			else if (Profile.SetupMode == EClientSetupMode::JsonConfig)
			{
				State->Status = TEXT("Ready");
				State->Detail = FString::Printf(TEXT("Will update %s after creating a backup."), *ExpandUserPath(Profile.JsonConfigPath));
			}
			else
			{
				State->Status = TEXT("Manual step required");
				State->Detail = TEXT("Automatic setup is not enabled for this client yet. Copy the snippet into its MCP settings.");
			}
		}
	}

	TSharedRef<SWidget> BuildClientList()
	{
		TSharedRef<SVerticalBox> ClientList = SNew(SVerticalBox);
		for (const FClientProfile& Profile : Profiles)
		{
			ClientList->AddSlot()
			.AutoHeight()
			.Padding(0.0f, 0.0f, 0.0f, 10.0f)
			[
				BuildClientRow(Profile)
			];
		}

		return ClientList;
	}

	TSharedRef<SWidget> BuildClientRow(const FClientProfile& Profile)
	{
		return SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
			.Padding(10.0f)
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.AutoHeight()
				[
					SNew(SHorizontalBox)
					+ SHorizontalBox::Slot()
					.FillWidth(1.0f)
					[
						SNew(STextBlock)
						.Text(FText::FromString(Profile.DisplayName))
						.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
					]
					+ SHorizontalBox::Slot()
					.AutoWidth()
					[
						SNew(STextBlock)
						.Text(FText::FromString(Profile.TransportLabel))
					]
				]
				+ SVerticalBox::Slot()
				.AutoHeight()
				.Padding(0.0f, 6.0f, 0.0f, 0.0f)
				[
					SNew(STextBlock)
					.Text(this, &SDruidsMCPBridgeAISetupWindow::GetClientStatusText, Profile.Id)
					.AutoWrapText(true)
				]
				+ SVerticalBox::Slot()
				.AutoHeight()
				.Padding(0.0f, 8.0f, 0.0f, 0.0f)
				[
					SNew(SHorizontalBox)
					+ SHorizontalBox::Slot()
					.AutoWidth()
					[
						SNew(SButton)
						.ButtonStyle(&PrimaryButtonStyle)
						.ForegroundColor(FSlateColor(FLinearColor::White))
						.Text(LOCTEXT("OneClickSetup", "One Click Setup"))
						.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::SetupClient, Profile)
					]
					+ SHorizontalBox::Slot()
					.FillWidth(1.0f)
					[
						SNew(SSpacer)
					]
					+ SHorizontalBox::Slot()
					.AutoWidth()
					.Padding(8.0f, 0.0f, 0.0f, 0.0f)
					[
						SNew(SButton)
						.Text(LOCTEXT("CopySnippet", "Copy Setup Text"))
						.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::CopyClientSnippet, Profile)
					]
					+ SHorizontalBox::Slot()
					.AutoWidth()
					.Padding(8.0f, 0.0f, 0.0f, 0.0f)
					[
						SNew(SButton)
						.Text(LOCTEXT("OpenConfigFolder", "Open Config Folder"))
						.IsEnabled(!Profile.JsonConfigPath.IsEmpty())
						.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::OpenConfigFolder, Profile)
					]
					+ SHorizontalBox::Slot()
					.AutoWidth()
					.Padding(8.0f, 0.0f, 0.0f, 0.0f)
					[
						SNew(SButton)
						.Text(LOCTEXT("CopyDocs", "Copy Docs URL"))
						.OnClicked(this, &SDruidsMCPBridgeAISetupWindow::CopyDocsUrl, Profile)
					]
				]
			];
	}

	void InitializePrimaryButtonStyle()
	{
		PrimaryButtonStyle = FCoreStyle::Get().GetWidgetStyle<FButtonStyle>("Button");
		PrimaryButtonStyle
			.SetNormal(PrimaryNormalBrush)
			.SetHovered(PrimaryHoveredBrush)
			.SetPressed(PrimaryPressedBrush);
	}

	FText GetEndpointText() const
	{
		return FText::FromString(EndpointUrl);
	}

	FText GetClientStatusText(FString ProfileId) const
	{
		TSharedPtr<FClientSetupState> State = States.FindRef(ProfileId);
		if (!State.IsValid())
		{
			return LOCTEXT("UnknownStatus", "Status unavailable");
		}

		if (State->Detail.IsEmpty())
		{
			return FText::FromString(State->Status);
		}

		return FText::FromString(FString::Printf(TEXT("%s: %s"), *State->Status, *State->Detail));
	}

	FReply Refresh()
	{
		RefreshEndpoint();
		RefreshDetectedStatus();
		return FReply::Handled();
	}

	FReply CopyEndpoint() const
	{
		FPlatformApplicationMisc::ClipboardCopy(*EndpointUrl);
		return FReply::Handled();
	}

	FReply CopyDocsUrl(FClientProfile Profile) const
	{
		FPlatformApplicationMisc::ClipboardCopy(*Profile.DocsUrl);
		return FReply::Handled();
	}

	FReply CopyClientSnippet(FClientProfile Profile) const
	{
		FPlatformApplicationMisc::ClipboardCopy(*BuildSnippet(Profile, EndpointUrl));
		return FReply::Handled();
	}

	FReply OpenConfigFolder(FClientProfile Profile) const
	{
		if (!Profile.JsonConfigPath.IsEmpty())
		{
			const FString Folder = FPaths::GetPath(ExpandUserPath(Profile.JsonConfigPath));
			IFileManager::Get().MakeDirectory(*Folder, true);
			FPlatformProcess::LaunchFileInDefaultExternalApplication(*Folder);
		}

		return FReply::Handled();
	}

	FReply SetupClient(FClientProfile Profile)
	{
		if (EnsureBridgeRunning.IsBound())
		{
			EnsureBridgeRunning.Execute();
			RefreshEndpoint();
		}

		TSharedPtr<FClientSetupState> State = States.FindRef(Profile.Id);
		if (!State.IsValid())
		{
			return FReply::Handled();
		}

		if (Profile.SetupMode == EClientSetupMode::Cli)
		{
			FString ExecutablePath;
			if (FindExecutableOnPath(Profile.SetupCommand, ExecutablePath))
			{
				const FString Args = FormatTemplate(Profile.SetupArgsTemplate, EndpointUrl);
				int32 ReturnCode = INDEX_NONE;
				FString StdOut;
				FString StdErr;
				if (ExecCliProcess(ExecutablePath, Args, ReturnCode, StdOut, StdErr) && ReturnCode == 0)
				{
					State->Status = TEXT("Needs restart");
					State->Detail = FString::Printf(TEXT("Configured with `%s %s`. Restart or refresh the AI client."), *Profile.SetupCommand, *Args);
					State->LastOutput = StdOut;
					return FReply::Handled();
				}

				State->Status = TEXT("Manual step required");
				State->Detail = FString::Printf(TEXT("CLI setup failed. Copy setup text instead. Exit code: %d"), ReturnCode);
				State->LastOutput = StdErr.IsEmpty() ? StdOut : StdErr;
				return FReply::Handled();
			}

			if (!Profile.JsonConfigPath.IsEmpty())
			{
				if (!ConfirmJsonConfigWrite(Profile, EndpointUrl))
				{
					State->Status = TEXT("Manual step required");
					State->Detail = TEXT("Config write canceled. Copy setup text instead.");
					return FReply::Handled();
				}

				FString Detail;
				if (UpdateJsonMcpConfig(Profile, EndpointUrl, Detail))
				{
					State->Status = TEXT("Needs restart");
				}
				else
				{
					State->Status = Detail.Contains(TEXT("malformed"), ESearchCase::IgnoreCase) ? TEXT("Config malformed") : TEXT("Manual step required");
				}
				State->Detail = Detail;
				return FReply::Handled();
			}

			State->Status = TEXT("CLI not found");
			State->Detail = FString::Printf(TEXT("%s was not found on PATH. Copy setup text instead."), *Profile.SetupCommand);
			return FReply::Handled();
		}

		if (Profile.SetupMode == EClientSetupMode::JsonConfig)
		{
			if (!ConfirmJsonConfigWrite(Profile, EndpointUrl))
			{
				State->Status = TEXT("Manual step required");
				State->Detail = TEXT("Config write canceled. Copy setup text instead.");
				return FReply::Handled();
			}

			FString Detail;
			if (UpdateJsonMcpConfig(Profile, EndpointUrl, Detail))
			{
				State->Status = TEXT("Needs restart");
			}
			else
			{
				State->Status = Detail.Contains(TEXT("malformed"), ESearchCase::IgnoreCase) ? TEXT("Config malformed") : TEXT("Manual step required");
			}
			State->Detail = Detail;
			return FReply::Handled();
		}

		State->Status = TEXT("Manual step required");
		State->Detail = TEXT("Copied setup text is the supported path for this client in this version.");
		FPlatformApplicationMisc::ClipboardCopy(*BuildSnippet(Profile, EndpointUrl));
		return FReply::Handled();
	}

	FDruidsMCPBridgeAISetup::FEnsureBridgeRunning EnsureBridgeRunning;
	FDruidsMCPBridgeAISetup::FGetEndpointUrl GetEndpointUrl;
	TArray<FClientProfile> Profiles;
	TMap<FString, TSharedPtr<FClientSetupState>> States;
	FString EndpointUrl;
	TSharedPtr<SEditableTextBox> EndpointTextBox;
	FSlateColorBrush PrimaryNormalBrush = FSlateColorBrush(FLinearColor(0.04f, 0.36f, 0.78f));
	FSlateColorBrush PrimaryHoveredBrush = FSlateColorBrush(FLinearColor(0.05f, 0.46f, 0.95f));
	FSlateColorBrush PrimaryPressedBrush = FSlateColorBrush(FLinearColor(0.03f, 0.25f, 0.58f));
	FButtonStyle PrimaryButtonStyle;
};
}

void FDruidsMCPBridgeAISetup::OpenWindow(FEnsureBridgeRunning EnsureBridgeRunning, FGetEndpointUrl GetEndpointUrl)
{
	TSharedRef<SWindow> Window = SNew(SWindow)
		.Title(LOCTEXT("WindowTitle", "Druids MCP Bridge - Connect AI Assistant"))
		.ClientSize(FVector2D(760.0f, 620.0f))
		.SupportsMaximize(false)
		.SupportsMinimize(false);

	Window->SetContent(
		SNew(DruidsMCPBridgeAISetup::SDruidsMCPBridgeAISetupWindow)
		.EnsureBridgeRunning(EnsureBridgeRunning)
		.GetEndpointUrl(GetEndpointUrl));

	FSlateApplication::Get().AddWindow(Window);
}

#undef LOCTEXT_NAMESPACE
