#pragma once

#include "NativeSockets.h"

#include <winsock2.h>

#include <atomic>
#include <filesystem>
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

class JsonValue;

class ControlServer
{
public:
	struct Callbacks
	{
		std::function<std::string()> getStateJson;
		std::function<bool(const std::string&, std::string&)> addLayer;
		std::function<bool(const std::string&, std::string&)> removeLayer;
		std::function<bool(const std::string&, int, std::string&)> moveLayer;
		std::function<bool(const std::string&, std::size_t, std::string&)> moveLayerToIndex;
		std::function<bool(const std::string&, bool, std::string&)> setLayerBypass;
		std::function<bool(const std::string&, const std::string&, std::string&)> setLayerShader;
		std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)> updateLayerParameter;
		std::function<bool(const std::string&, std::string&)> resetLayerParameters;
		std::function<bool(const std::string&, std::string&)> saveStackPreset;
		std::function<bool(const std::string&, std::string&)> loadStackPreset;
		std::function<bool(std::string&)> reloadShader;
		std::function<bool(std::string&)> requestScreenshot;
	};

	ControlServer();
	~ControlServer();

	bool Start(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, unsigned short preferredPort, const Callbacks& callbacks, std::string& error);
	void Stop();
	void BroadcastState();
	void RequestBroadcastState();

	unsigned short GetPort() const { return mPort; }

private:
	struct ClientConnection
	{
		UniqueSocket socket;
		bool websocket = false;
	};

	struct HttpRequest
	{
		std::string method;
		std::string path;
		std::map<std::string, std::string> headers;
		std::string body;
	};

	struct HttpResponse
	{
		std::string status;
		std::string contentType;
		std::string body;
		bool broadcastState = false;
	};

	void ServerLoop();
	bool HandleHttpClient(UniqueSocket clientSocket);
	bool TryAcceptClient();
	bool SendHttpResponse(SOCKET clientSocket, const HttpResponse& response);
	bool SendHttpResponse(SOCKET clientSocket, const std::string& status, const std::string& contentType, const std::string& body);
	bool HandleHttpRequest(UniqueSocket clientSocket, const std::string& request);
	bool HandleWebSocketUpgrade(UniqueSocket clientSocket, const HttpRequest& request);
	HttpResponse RouteHttpRequest(const HttpRequest& request);
	HttpResponse ServeGetRequest(const HttpRequest& request) const;
	HttpResponse ServeUiAsset(const std::string& relativePath) const;
	HttpResponse ServeDocsAsset(const std::string& relativePath) const;
	HttpResponse ServeOpenApiSpec() const;
	HttpResponse ServeSwaggerDocs() const;
	HttpResponse HandleApiPost(const HttpRequest& request);
	bool InvokePostRoute(const std::string& path, const JsonValue& root, std::string& actionError);
	bool SendWebSocketText(SOCKET clientSocket, const std::string& payload);
	void BroadcastStateLocked();
	std::string LoadUiAsset(const std::string& relativePath, std::string& contentType) const;
	std::string LoadTextFile(const std::filesystem::path& path) const;
	std::string BuildJsonResponse(bool success, const std::string& error = std::string()) const;
	static std::string Base64Encode(const unsigned char* data, DWORD dataLength);
	static std::string ComputeWebSocketAcceptKey(const std::string& clientKey);
	static std::string GetHeaderValue(const HttpRequest& request, const std::string& headerName);
	static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request);

private:
	std::filesystem::path mUiRoot;
	std::filesystem::path mDocsRoot;
	Callbacks mCallbacks;
	UniqueSocket mListenSocket;
	unsigned short mPort;
	std::thread mThread;
	std::atomic<bool> mRunning;
	std::atomic<bool> mBroadcastPending;
	mutable std::mutex mMutex;
	std::vector<ClientConnection> mClients;
};
