#include "LiftoffMediation.h"
#include "LiftoffInterstitialAds.h"
#include "LiftoffWin32DesktopTestApp.h"

#include "UiUtilities.h"
#include "JsonUtilities.h"
#include "EncodeAndDecodeUtils.h"

#include <windows.h>
#include <string>
#include <codecvt>
#include <locale>
#include <algorithm>
#include <exception>
// If you use nlohmann::json, add:
#include <nlohmann/json.hpp>

/// <summary>
/// Routines for loading and playing "Mediation" (a.k.a. "header bidding") ads.
/// Publisher's getting started with Liftoff Ads should probably start
/// instead with "Interstital" ads which you can find in LiftoffInterstitialAds.h/.cpp
/// </summary>

const wchar_t* g_HB_RtbEndpointUrls[] = {
    L"http://internal-aafdd3f38bd3c11ebbed60ad1496429a-33265314.us-west-2.elb.amazonaws.com/bid/t/34dec8a", // E2E
    L"https://rtb-ext-qa.api.vungle.com/bid/t/34dec8a",                                                    // QA
    L"http://internal-aa77d439fc8eb43cf9432345e29937bd-1467361235.us-west-2.elb.amazonaws.com/bid/t/34dec8a", // Newer
    L"https://rtb.api.vungle.com/bid/t/34dec8a",                                                           // Prod (MoPub)
    L"https://rtb.api.vungle.com/bid/t/2ffe4e7"                                                            // Prod (Max)
};

#include "LiftoffMediation.h"
#include "SendSuperTokenToPlatform.h"

const wchar_t* g_HB_AdmExampleText = L"{ \"version\": 2, \"adunit\": \" \" }";

const wchar_t* g_HB_RealtimeTokenExampleText =
L"4:H4sIAAAAAAAAE91S204bQQz9Fz8v6e6KRGHRPiyIh15AolVblYtGZsZMppnMJHNZLsn+O06K1FZI8M6jfY7t42OvIdAqU0zQrMEHZRxa0Ru6g6YuIKq5yJGCQE2OKfAjO23p6sNkVJWj6tBhMj1BActAEuWMlEh+Ti5Ccwn1pr4/W43P23Yjq7CaqskB5rEJslS/g9dcVW8+fpr9nN+9xni7x3thXBcgvbs1WtB9Yg+Nd+z4cXdy3pnu6LM+m/1anD509Vd7odsWhgIU9UbS9nIWnc58JC4gt9d+/8bzklmQePRum+wWFIxE8cVH0TlNliIzbjAlCg8iYs83Joc3lhQ0KWQq4B8Ra4g+O/WXcYs2MsVEEY0i6/ElFpWQGJTAHo3dQs8Iy+69zSzNUk8WmumoHg+71ePux9Yg5RJ3QxOmzK9U1YxrtQz/Jf8syOFiydGkmk7K/WnJadYatq5U+wUwIbItgvd79rMelTAMwxNSEEz3+AIAAA==";

/// <summary>
/// To get started with loading and playing a "mediated" (a.k.a. "Header Bidding") ad, 
///	you will first need a supertoken 
/// </summary>
void HandleGetSuperTokenButton(HWND hWnd)
{
	// Combo with placements (Header Bidding tab)
	HWND hComboPlacements = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_PLACEMENTS);
	if (!hComboPlacements) {
		MessageBox(hWnd, L"Placements control not found.", L"Get Super Token", MB_OK | MB_ICONERROR);
		return;
	}

	int selIndex = (int)SendMessage(hComboPlacements, CB_GETCURSEL, 0, 0);
	if (selIndex == CB_ERR) {
		MessageBox(hWnd, L"Please select a placement.", L"Get Super Token", MB_OK | MB_ICONERROR);
		return;
	}

	// Get the placement text
	int placementLen = (int)SendMessage(hComboPlacements, CB_GETLBTEXTLEN, selIndex, 0);
	std::wstring wPlacement(placementLen + 1, L'\0');
	SendMessage(hComboPlacements, CB_GETLBTEXT, selIndex, (LPARAM)wPlacement.data());
	if (!wPlacement.empty() && wPlacement.back() == L'\0') wPlacement.pop_back();

	std::string placementUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wPlacement);

	// Ensure SDK instance is available
	if (g_sdkInstance == nullptr) {
		MessageBox(hWnd, L"SDK instance is null!", L"Get Super Token", MB_OK | MB_ICONERROR);
		return;
	}

	try {
		// Call into LiftoffAds -> MediationController -> GetMediationSuperTokenV4
		std::string token = g_sdkInstance->GetMediationSuperToken(placementUtf8);

		// Put token into the Realtime Ads Report edit control
		std::wstring wtoken = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(token);
		HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
		if (hEditReport) {
			SetWindowText(hEditReport, wtoken.c_str());
		}
		else {
			// fallback to message box
			MessageBox(hWnd, wtoken.c_str(), L"Super Token", MB_OK | MB_ICONINFORMATION);
		}
	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Get Super Token Error", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
///	You can then use the supertoken (which is currently stored in the Realtime Ads Report edit control)
/// to query the Vungle/Liftoff RTB endpoint to get an ADM ("Ad Markup") response.  If you get a successful response 
/// (because the Vungle/Liftoff server AI "decided" to bid on the request), you can then use that ADM to load
/// and play a mediated ad.
/// </summary>
/// <param name="hWnd"></param>
void HandleGetAdmButton(HWND hWnd)
{
	// Combo with placements
	HWND hComboPlacements = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_PLACEMENTS);
	if (!hComboPlacements) {
		MessageBox(hWnd, L"Placements control not found.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	int sel = (int)SendMessage(hComboPlacements, CB_GETCURSEL, 0, 0);
	if (sel == CB_ERR) {
		MessageBox(hWnd, L"Please select a placement.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	// Get the placement text
	int placementLen = (int)SendMessage(hComboPlacements, CB_GETLBTEXTLEN, sel, 0);
	std::wstring wPlacement(placementLen + 1, L'\0');
	SendMessage(hComboPlacements, CB_GETLBTEXT, sel, (LPARAM)wPlacement.data());
	if (!wPlacement.empty() && wPlacement.back() == L'\0') wPlacement.pop_back();
	std::string placementUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wPlacement);

	// Get App ID from edit control
	WCHAR wAppId[MAX_LOADSTRING] = { 0 };
	GetWindowText(hEditAppId, wAppId, MAX_LOADSTRING);
	std::string appIdUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wAppId);
	if (appIdUtf8.empty()) {
		MessageBox(hWnd, L"App ID cannot be empty.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	// Get IP/address from the edit control (use ID_HB_EDIT_IP_FOR_ADM)
	HWND hEditIp = GetDlgItem(hTabPages[4], ID_HB_EDIT_IP_FOR_ADM);
	if (!hEditIp) {
		MessageBox(hWnd, L"IP input control not found.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}
	WCHAR wIp[256] = { 0 };
	GetWindowText(hEditIp, wIp, _countof(wIp));
	std::string ipUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wIp);
	if (ipUtf8.empty()) {
		MessageBox(hWnd, L"IP address cannot be empty.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	// Get token from the Realtime Ads Report edit (ID_HB_EDIT_REALTIME_ADS_REPORT)
	HWND hEditToken = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (!hEditToken) {
		MessageBox(hWnd, L"Token input control not found.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}
	int tokenLen = GetWindowTextLength(hEditToken);
	std::wstring wToken;
	if (tokenLen > 0) {
		wToken.assign(tokenLen + 1, L'\0');
		GetWindowText(hEditToken, &wToken[0], tokenLen + 1);
		if (!wToken.empty() && wToken.back() == L'\0') wToken.pop_back();
	}
	std::string tokenUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wToken);
	if (tokenUtf8.empty()) {
		MessageBox(hWnd, L"Token cannot be empty.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	// Get endpoint from ID_HB_EDIT_RTB_ENDPOINT
	HWND hEditEndpoint = GetDlgItem(hTabPages[4], ID_HB_EDIT_RTB_ENDPOINT);
	if (!hEditEndpoint) {
		MessageBox(hWnd, L"RTB endpoint control not found.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}
	int endpointLen = GetWindowTextLength(hEditEndpoint);
	std::wstring wEndpoint;
	if (endpointLen > 0) {
		wEndpoint.assign(endpointLen + 1, L'\0');
		GetWindowText(hEditEndpoint, &wEndpoint[0], endpointLen + 1);
		if (!wEndpoint.empty() && wEndpoint.back() == L'\0') wEndpoint.pop_back();
	}
	std::string endpointUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wEndpoint);
	if (endpointUtf8.empty()) {
		MessageBox(hWnd, L"RTB endpoint cannot be empty.", L"Get ADM", MB_OK | MB_ICONERROR);
		return;
	}

	try {
		// Call the (stub) GetAdmFromRtbToken with all required parameters.
		std::string result = GetAdmFromRtbToken(appIdUtf8, placementUtf8, ipUtf8, tokenUtf8, endpointUtf8);

		// Show the result in the realtime ads report edit control instead of a message box
		std::wstring wResult = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(result);
		HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
		if (hEditReport) {
			SetWindowText(hEditReport, wResult.c_str());
		}
		else {
			// fallback to message box if edit control not found
			MessageBox(hWnd, wResult.c_str(), L"Get ADM Result", MB_OK | MB_ICONINFORMATION);
		}
	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Get ADM Error", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
/// Once you have the ADM response from the RTB endpoint, you can extract the "adm" field from the JSON
/// which you will need later to load and play the mediated ad.
/// </summary>
/// <param name="hWnd"></param>
void HandleGetWinningAdm(HWND hWnd)
{
	// Get the report edit control on the Header Bidding tab
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (!hEditReport) {
		return;
	}

	// Read wide text from the edit control
	int len = GetWindowTextLength(hEditReport);
	std::wstring wtext;
	if (len > 0) {
		wtext.assign(len + 1, L'\0');
		GetWindowText(hEditReport, &wtext[0], len + 1);
		// Trim possible trailing null
		if (!wtext.empty() && wtext.back() == L'\0') {
			wtext.pop_back();
		}
	}

	// Convert to UTF-8 for JSON parsing
	std::string input = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wtext);

	try {
		// Parse JSON (ConvertStringToJson returns json or null on error)
		json parsed = ConvertStringToJson(input);
		std::string admValue;

		// Recursive search for key "adm"
		std::function<bool(const json&)> findAdm;
		findAdm = [&](const json& node) -> bool {
			if (node.is_object()) {
				for (auto it = node.begin(); it != node.end(); ++it) {
					if (it.key() == "adm" && it.value().is_string()) {
						admValue = it.value().get<std::string>();
						return true;
					}
					// If value is object/array, recurse
					if (it.value().is_structured()) {
						if (findAdm(it.value())) return true;
					}
				}
			}
			else if (node.is_array()) {
				for (const auto& el : node) {
					if (findAdm(el)) return true;
				}
			}
			return false;
			};

		if (!parsed.is_null()) {
			findAdm(parsed);
		}

		// Fallback: naive text search if JSON parsing failed or key not found
		if (admValue.empty()) {
			size_t pos = input.find("\"adm\"");
			if (pos != std::string::npos) {
				size_t colon = input.find(':', pos);
				if (colon != std::string::npos) {
					// find the first quote after colon
					size_t firstQuote = input.find('"', colon);
					if (firstQuote != std::string::npos) {
						size_t secondQuote = input.find('"', firstQuote + 1);
						if (secondQuote != std::string::npos && secondQuote > firstQuote + 1) {
							admValue = input.substr(firstQuote + 1, secondQuote - firstQuote - 1);
						}
					}
				}
			}
		}

		if (admValue.empty()) {
			MessageBox(hWnd, L"Could not locate an \"adm\" field in the provided JSON.", L"Get Winning Adm", MB_OK | MB_ICONERROR);
			return;
		}

		// Put the extracted adm value back into the edit control (as wide string)
		std::wstring wadm = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(admValue);
		SetWindowText(hEditReport, wadm.c_str());
	}
	catch (const std::exception& ex) {
		std::string msg = ex.what();
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(msg);
		MessageBox(hWnd, wmsg.c_str(), L"Get Winning Adm Error", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
/// Using the "winning adm" you extracted from the RTB response above, you can now load a mediated ad
/// </summary>
void HandleLoadAdFromAdm(HWND hWnd)
{
	// Ensure SDK instance is available
	if (g_sdkInstance == nullptr) {
		MessageBox(hWnd, L"SDK instance is null!", L"Load Mediated Ad", MB_OK | MB_ICONERROR);
		return;
	}

	// Get placement from the placements combo on the Header Bidding tab
	HWND hComboPlacements = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_PLACEMENTS);
	if (!hComboPlacements) {
		MessageBox(hWnd, L"Placements control not found.", L"Load Mediated Ad", MB_OK | MB_ICONERROR);
		return;
	}
	int sel = (int)SendMessage(hComboPlacements, CB_GETCURSEL, 0, 0);
	if (sel == CB_ERR) {
		MessageBox(hWnd, L"Please select a placement.", L"Load Mediated Ad", MB_OK | MB_ICONERROR);
		return;
	}
	int placementLen = (int)SendMessage(hComboPlacements, CB_GETLBTEXTLEN, sel, 0);
	std::wstring wPlacement(placementLen + 1, L'\0');
	SendMessage(hComboPlacements, CB_GETLBTEXT, sel, (LPARAM)wPlacement.data());
	if (!wPlacement.empty() && wPlacement.back() == L'\0') wPlacement.pop_back();
	std::string placementUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wPlacement);

	// Read the ADM / realtime token from the report edit control
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (!hEditReport) {
		MessageBox(hWnd, L"Token input control not found.", L"Load Mediated Ad", MB_OK | MB_ICONERROR);
		return;
	}
	int len = GetWindowTextLength(hEditReport);
	std::wstring wToken;
	if (len > 0) {
		wToken.assign(len + 1, L'\0');
		GetWindowText(hEditReport, &wToken[0], len + 1);
		if (!wToken.empty() && wToken.back() == L'\0') wToken.pop_back();
	}
	std::string tokenUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wToken);

	if (tokenUtf8.empty()) {
		MessageBox(hWnd, L"Token cannot be empty.", L"Load Mediated Ad", MB_OK | MB_ICONERROR);
		return;
	}

	try {
		AdLoadCallback callback;
		callback.OnAdLoadFailure = OnAdLoadFailure;
		callback.OnAdLoadSuccess = OnAdLoadSuccess;

		// Call into the SDK to load the mediated ad
		bool loaded = g_sdkInstance->LoadMediatedAd(placementUtf8, callback, tokenUtf8);

		if (!loaded) {
			std::wstring msg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes("LoadMediatedAd returned false for placement: " + placementUtf8);
			MessageBox(hWnd, msg.c_str(), L"Load Mediated Ad", MB_OK | MB_ICONINFORMATION);
		}
		else {
			std::wstring msg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes("LoadMediatedAd initiated for placement: " + placementUtf8);
			// MessageBox(hWnd, msg.c_str(), L"Load Mediated Ad", MB_OK | MB_ICONINFORMATION);
		}
	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Load Mediated Ad Error", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
/// Once the ad is loaded, you can play it using the same "winning adm" you used to load it.
/// </summary>
/// <param name="hWnd"></param>
void HandlePlayAdFromAdm(HWND hWnd)
{
	try {
		// Ensure SDK instance is available
		if (g_sdkInstance == nullptr) {
			MessageBox(hWnd, L"SDK instance is null!", L"Play Mediated Ad", MB_OK | MB_ICONERROR);
			return;
		}

		// Get placement from the placements combo on the Header Bidding tab
		HWND hComboPlacements = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_PLACEMENTS);
		if (!hComboPlacements) {
			MessageBox(hWnd, L"Placements control not found.", L"Play Mediated Ad", MB_OK | MB_ICONERROR);
			return;
		}
		int sel = (int)SendMessage(hComboPlacements, CB_GETCURSEL, 0, 0);
		if (sel == CB_ERR) {
			MessageBox(hWnd, L"Please select a placement.", L"Play Mediated Ad", MB_OK | MB_ICONERROR);
			return;
		}
		int placementLen = (int)SendMessage(hComboPlacements, CB_GETLBTEXTLEN, sel, 0);
		std::wstring wPlacement(placementLen + 1, L'\0');
		SendMessage(hComboPlacements, CB_GETLBTEXT, sel, (LPARAM)wPlacement.data());
		if (!wPlacement.empty() && wPlacement.back() == L'\0') wPlacement.pop_back();
		std::string placementUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wPlacement);

		// Read the ADM / realtime token (admarkup) from the report edit control
		HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
		if (!hEditReport) {
			MessageBox(hWnd, L"Token input control not found.", L"Play Mediated Ad", MB_OK | MB_ICONERROR);
			return;
		}
		int len = GetWindowTextLength(hEditReport);
		std::wstring wToken;
		if (len > 0) {
			wToken.assign(len + 1, L'\0');
			GetWindowText(hEditReport, &wToken[0], len + 1);
			if (!wToken.empty() && wToken.back() == L'\0') wToken.pop_back();
		}
		std::string tokenUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wToken);

		if (tokenUtf8.empty()) {
			MessageBox(hWnd, L"Ad markup cannot be empty.", L"Play Mediated Ad", MB_OK | MB_ICONERROR);
			return;
		}

		// Prepare callbacks
		AdPlayCallback playCallback;
		playCallback.OnAdStart = OnAdStart;
		playCallback.OnAdEnd = OnAdEnd;
		playCallback.OnAdPlayFailure = OnAdPlayFailure;
		playCallback.OnAdPlayRewarded = OnAdPlayRewarded;
		playCallback.OnAdPlayClick = OnAdPlayClick;

		// Default AdConfig
		AdConfig config;


		// Call SDK: Play a mediated ad (header-bidding) synchronously
		LiftoffAdPlayInfo info = g_sdkInstance->PlayMediatedAd(config, placementUtf8, playCallback, tokenUtf8);

		// Report errors to user if play failed
		if (!info.Success) {
			std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(info.ErrorMessage);
			MessageBox(hWnd, wmsg.c_str(), L"Play Mediated Ad Error", MB_OK | MB_ICONERROR);
		}

		// Update local placement state and UI
		placementLoadState.Set(placementUtf8, g_sdkInstance->IsAdPlayable(placementUtf8));
		PostMessage(hWnd, WM_ENABLE_OR_DISABLE_PLAY_BUTTON, 0, 0);
	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Play Mediated Ad Exception", MB_OK | MB_ICONERROR);
	}
}

void HandlePlayAdFromAdmInNewWindow(HWND hWnd)
{
	try
	{
		if (g_sdkInstance == nullptr) {
			MessageBox(hWnd, L"SDK instance is null!", L"Play Mediated Ad in New Window", MB_OK | MB_ICONERROR);
			return;
		}
		HWND hNewWindow = NULL;
		hNewWindow = CreateWindowW(
			szOtherWindowClass,        // class name of the new window
			L"Another Window (Mediation)",         // window title (if visible)
			WS_OVERLAPPEDWINDOW | WS_VISIBLE, // style: child + visible (must use WS_CHILD for parented windows)
			CW_USEDEFAULT, CW_USEDEFAULT,
			500, 400,                  // width, height
			hMainWnd,                  // parent window handle
			nullptr,                   // menu (or child ID if WS_CHILD)
			hInst,
			nullptr);
		HWND hComboPlacements = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_PLACEMENTS);
		if (!hComboPlacements) {
			MessageBox(hWnd, L"Placements control not found.", L"Play Mediated Ad in New Window", MB_OK | MB_ICONERROR);
			return;
		}
		int sel = (int)SendMessage(hComboPlacements, CB_GETCURSEL, 0, 0);
		if (sel == CB_ERR) {
			MessageBox(hWnd, L"Please select a placement.", L"Play Mediated Ad in New Window", MB_OK | MB_ICONERROR);
			return;
		}
		int placementLen = (int)SendMessage(hComboPlacements, CB_GETLBTEXTLEN, sel, 0);
		std::wstring wPlacement(placementLen + 1, L'\0');
		SendMessage(hComboPlacements, CB_GETLBTEXT, sel, (LPARAM)wPlacement.data());
		if (!wPlacement.empty() && wPlacement.back() == L'\0') wPlacement.pop_back();
		std::string placementUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wPlacement);

		// Read the ADM / realtime token (admarkup) from the report edit control
		HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
		if (!hEditReport) {
			MessageBox(hWnd, L"Token input control not found.", L"Play Mediated Ad in New Window", MB_OK | MB_ICONERROR);
			return;
		}
		int len = GetWindowTextLength(hEditReport);
		std::wstring wToken;
		if (len > 0) {
			wToken.assign(len + 1, L'\0');
			GetWindowText(hEditReport, &wToken[0], len + 1);
			if (!wToken.empty() && wToken.back() == L'\0') wToken.pop_back();
		}
		std::string tokenUtf8 = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wToken);

		if (tokenUtf8.empty()) {
			MessageBox(hWnd, L"Ad markup cannot be empty.", L"Play Mediated Ad in New Window", MB_OK | MB_ICONERROR);
			return;
		}

		// Prepare callbacks
		AdPlayCallback playCallback;
		playCallback.OnAdStart = OnAdStart;
		playCallback.OnAdEnd = OnAdEnd;
		playCallback.OnAdPlayFailure = OnAdPlayFailure;
		playCallback.OnAdPlayRewarded = OnAdPlayRewarded;
		playCallback.OnAdPlayClick = OnAdPlayClick;

		// Default AdConfig
		AdConfig config = AdConfig();
		// Set the hPublisherWnd to the new window
		config.hPublisherWnd = hNewWindow;

		// Call SDK: Play a mediated ad (header-bidding) synchronously
		LiftoffAdPlayInfo info = g_sdkInstance->PlayMediatedAd(config, placementUtf8, playCallback, tokenUtf8);

		// Report errors to user if play failed
		if (!info.Success) {
			std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(info.ErrorMessage);
			MessageBox(hWnd, wmsg.c_str(), L"Play Mediated Ad in New Window Error", MB_OK | MB_ICONERROR);
		}

		// Update local placement state and UI
		placementLoadState.Set(placementUtf8, g_sdkInstance->IsAdPlayable(placementUtf8));
		PostMessage(hWnd, WM_ENABLE_OR_DISABLE_PLAY_BUTTON, 0, 0);

	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Play Mediated Ad in New Window Exception", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
/// You can use this to decode a supertoken you have obtained from GetMediationSuperTokenV4 if you
/// want to inspect its contents. This is not required for normal operation.
/// </summary>
/// <param name="hWnd"></param>
void HandleDecodeSuperToken(HWND hWnd) {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (hEditReport) {
		int len = GetWindowTextLength(hEditReport);
		std::wstring wtext;
		if (len > 0) {
			wtext.assign(len + 1, L'\0');
			GetWindowText(hEditReport, &wtext[0], len + 1);
			// Trim possible trailing null
			if (!wtext.empty() && wtext.back() == L'\0') {
				wtext.pop_back();
			}
		}

		// Convert to UTF-8 for the decoder
		std::string encoded = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wtext);

		try {
			std::string decoded;

			// Ensure format matches C# behavior: must start with "4:"
			if (encoded.rfind("4:", 0) != 0) {
				decoded = "Bad format for supertoken!";
			}
			else {
				// Remove the "4:" prefix before decoding
				encoded = encoded.substr(2);

				// Decode + ungzip
				decoded = EncodeAndDecodeUtils::UnEncodeAndUnGzipString(encoded);

				// Optionally normalize EOLs (FixupGzipEolCraziness is available)
				decoded = EncodeAndDecodeUtils::FixupGzipEolCraziness(decoded);
			}

			// Convert back to wide string and place into the edit control
			std::wstring wdecoded = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(decoded);
			SetWindowText(hEditReport, wdecoded.c_str());
		}
		catch (const std::exception& ex) {
			// Show error to user
			std::string msg = ex.what();
			std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(msg);
			MessageBox(hWnd, wmsg.c_str(), L"Decode Error", MB_OK | MB_ICONERROR);
		}
	}
}

/// <summary>
/// Handles the Encode Token button click by reading text from a report edit control, normalizing line endings, encoding the text with Gzip and Base64, and updating the control with the encoded result. Displays an error message if encoding fails.
/// This is not required for normal operation.
/// </summary>
void HandleEncodeTokenButton(HWND hWnd) {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (hEditReport) {
		int len = GetWindowTextLength(hEditReport);
		std::wstring wtext;
		if (len > 0) {
			wtext.assign(len + 1, L'\0');
			GetWindowText(hEditReport, &wtext[0], len + 1);
			// Trim possible trailing null
			if (!wtext.empty() && wtext.back() == L'\0') {
				wtext.pop_back();
			}
		}

		// Convert to UTF-8
		std::string json = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wtext);

		// Normalize CRLF -> LF and remaining CR -> LF (match C# behavior)
		size_t pos = 0;
		while ((pos = json.find("\r\n", pos)) != std::string::npos) {
			json.replace(pos, 2, "\n");
			pos += 1;
		}
		pos = 0;
		while ((pos = json.find('\r', pos)) != std::string::npos) {
			json.replace(pos, 1, "\n");
			pos += 1;
		}

		try {
			// Gzip + Base64 encode and prepend "4:"
			std::string encoded = EncodeAndDecodeUtils::GzipAndBase64EncodeString(json);
			std::string result = std::string("4:") + encoded;

			// Convert back to wide string and set into edit control
			std::wstring wresult = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(result);
			SetWindowText(hEditReport, wresult.c_str());
		}
		catch (const std::exception& ex) {
			std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
			MessageBox(hWnd, wmsg.c_str(), L"Encode Error", MB_OK | MB_ICONERROR);
		}
	}
}

/// <summary>
/// Use this to decode an "winning ADM" ("Winning Ad Markup") you have obtained from the RTB endpoint if you
/// want to inspect its contents. This is not required for normal operation.
/// </summary>
void HandleDecodeAdmButton(HWND hWnd)
{
	// Get the report edit control on the Header Bidding tab
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (!hEditReport) {
		return;
	}

	// Read wide text from the edit control
	int len = GetWindowTextLength(hEditReport);
	std::wstring wtext;
	if (len > 0) {
		wtext.assign(len + 1, L'\0');
		GetWindowText(hEditReport, &wtext[0], len + 1);
		// Trim possible trailing null
		if (!wtext.empty() && wtext.back() == L'\0') {
			wtext.pop_back();
		}
	}

	// Convert to UTF-8 for JSON parsing / decoding
	std::string input = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wtext);

	try {
		// Try to parse JSON and extract the "adunit" field.
		// ConvertStringToJson returns a nlohmann::json (null on parse error).
		json parsed = ConvertStringToJson(input);

		std::string adunit;

		if (!parsed.is_null() && parsed.is_object() && parsed.contains("adunit") && !parsed["adunit"].is_null()) {
			try {
				adunit = parsed["adunit"].get<std::string>();
			}
			catch (...) {
				// Fallthrough to fallback parsing below if typed extraction fails.
			}
		}

		// Fallbacks: try to extract using naive search (handles slightly malformed JSON)
		if (adunit.empty()) {
			size_t pos = input.find("\"adunit\"");
			if (pos != std::string::npos) {
				size_t colon = input.find(':', pos);
				if (colon != std::string::npos) {
					// find the first quote after colon
					size_t firstQuote = input.find('"', colon);
					if (firstQuote != std::string::npos) {
						size_t secondQuote = input.find('"', firstQuote + 1);
						if (secondQuote != std::string::npos && secondQuote > firstQuote + 1) {
							adunit = input.substr(firstQuote + 1, secondQuote - firstQuote - 1);
						}
					}
				}
			}
		}

		// As a last resort, if the whole input looks like a base64/gzip token, treat entire input as adunit
		if (adunit.empty()) {
			// Heuristic: contains base64 characters or '=' padding
			bool looksLikeBase64 = false;
			if (!input.empty()) {
				int base64CharsFound = 0;
				for (char c : input) {
					if (std::isalnum(static_cast<unsigned char>(c)) || c == '+' || c == '/' || c == '=') base64CharsFound++;
				}
				if (base64CharsFound >= (int)std::max<size_t>(1, input.size() / 2)) looksLikeBase64 = true;
			}
			if (looksLikeBase64) {
				adunit = input;
			}
		}

		if (adunit.empty()) {
			MessageBox(hWnd, L"Could not locate an \"adunit\" field in the provided JSON.", L"Decode ADM", MB_OK | MB_ICONERROR);
			return;
		}

		// Decode + ungzip
		std::string decoded = EncodeAndDecodeUtils::UnEncodeAndUnGzipString(adunit);

		// Normalize EOLs / blank lines similar to token decoding
		decoded = EncodeAndDecodeUtils::FixupGzipEolCraziness(decoded);

		// Convert back to wide
		std::wstring wdecoded = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(decoded);

		// Call FixAndSanitizeText to fix up wdecoded (remove \uXXXX escapes like "\u0026" and collapse multiple line breaks)
		std::wstring sanitized = FixAndSanitizeText(wdecoded);

		// Set sanitized text into the report edit box
		SetWindowText(hEditReport, sanitized.c_str());
	}
	catch (const std::exception& ex) {
		std::string msg = ex.what();
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(msg);
		MessageBox(hWnd, wmsg.c_str(), L"Decode ADM Error", MB_OK | MB_ICONERROR);
	}
}

/// <summary>
/// Use this to encode a JSON adunit you want to use as an "winning ADM" for loading/playing a mediated ad.
/// This is not required for normal operation.
/// </summary>
void HandleEncodeAdmButton(HWND hWnd)
{
	// Get the report edit control on the Header Bidding tab
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (!hEditReport) {
		return;
	}

	// Read wide text from the edit control
	int len = GetWindowTextLength(hEditReport);
	std::wstring wtext;
	if (len > 0) {
		wtext.assign(len + 1, L'\0');
		GetWindowText(hEditReport, &wtext[0], len + 1);
		// Trim possible trailing null
		if (!wtext.empty() && wtext.back() == L'\0') {
			wtext.pop_back();
		}
	}

	// Convert to UTF-8 for encoding
	std::string json = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(wtext);

	// Normalize CRLF -> LF and remaining CR -> LF (match other encode code)
	size_t pos = 0;
	while ((pos = json.find("\r\n", pos)) != std::string::npos) {
		json.replace(pos, 2, "\n");
		pos += 1;
	}
	pos = 0;
	while ((pos = json.find('\r', pos)) != std::string::npos) {
		json.replace(pos, 1, "\n");
		pos += 1;
	}

	try {
		// Gzip + Base64 encode
		std::string encoded = EncodeAndDecodeUtils::GzipAndBase64EncodeString(json);

		// Build the JSON-like ADM with the encoded adunit
		std::string adm = std::string("{ \"version\": 2, \"adunit\": \"") + encoded + "\" }";

		// Convert back to wide string and place into the edit control
		std::wstring wadm = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(adm);
		SetWindowText(hEditReport, wadm.c_str());
	}
	catch (const std::exception& ex) {
		std::wstring wmsg = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(ex.what());
		MessageBox(hWnd, wmsg.c_str(), L"Encode ADM Error", MB_OK | MB_ICONERROR);
	}
}


void GetSelectedRtbEndpoint() {
	HWND hCombo = GetDlgItem(hTabPages[4], ID_HB_COMBOBOX_RTB_ENDPOINTS);
	HWND hEdit = GetDlgItem(hTabPages[4], ID_HB_EDIT_RTB_ENDPOINT);
	int sel = (int)SendMessage(hCombo, CB_GETCURSEL, 0, 0);
	if (sel >= 0 && sel < (int)(sizeof(g_HB_RtbEndpointUrls) / sizeof(g_HB_RtbEndpointUrls[0]))) {
		SetWindowText(hEdit, g_HB_RtbEndpointUrls[sel]);
	}
}

void SetAdmExampleText() {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (hEditReport) {
		SetWindowText(hEditReport, g_HB_AdmExampleText);
	}
}

void SetRealtimeTokenExampleText() {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	if (hEditReport) {
		SetWindowText(hEditReport, g_HB_RealtimeTokenExampleText);
	}
}

void ClearRealtimeReport() {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);
	SetWindowText(hEditReport, L"");
}

void CopyRealtimeReportToClipboard() {
	HWND hEditReport = GetDlgItem(hTabPages[4], ID_HB_EDIT_REALTIME_ADS_REPORT);

	int len = GetWindowTextLength(hEditReport);
	if (len > 0) {
		std::wstring text(len + 1, L'\0');
		GetWindowText(hEditReport, &text[0], len + 1);

		if (OpenClipboard(hTabPages[4])) {
			EmptyClipboard();
			HGLOBAL hGlob = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(wchar_t));
			if (hGlob) {
				wchar_t* pGlob = (wchar_t*)GlobalLock(hGlob);
				if (pGlob) {
					wcscpy_s(pGlob, len + 1, text.c_str());
					GlobalUnlock(hGlob);
					SetClipboardData(CF_UNICODETEXT, hGlob);
				}
			}
			CloseClipboard();
		}
	}
}