#include "Menu.h"
#include "../../Backend/Utilities/Utilities.h"
#include "../../Backend/Config/Settings.h"
#include "../../Backend/Config/Config.h"
#include "../../Backend/Config/MenuParams.h"
#include "../../Backend/Console/Console.h"
#include "../../Overlay/Overlay.h"
#include "../Renderer/Renderer.h"
#include "../../Backend/Main/Main.h"

#include <algorithm>
#include <set>
#include <psapi.h>
#include <shellapi.h>
#include <ShlObj_core.h>

using namespace GamesenseMenuFramework;

// ------------------------------------------------------------------
// Initialize
// ------------------------------------------------------------------
void CMenu::Initialize()
{
	if (m_bInitialized)
		return;

	ui::CreateContext();
	GuiContext* g = Globals::Gui_Ctx;
	g->ItemSpacing = Vec2(0, 6);
	g->MenuAlpha = 1;

	CConfig::get()->LoadDefaults();
	CConfig::get()->AutoLoad();

	// Apply saved console visibility
	Console::Get()->SetVisible(CConfig::get()->b["show_console"]);

	if (CConfig::get()->b["isAdmin"] && !IsUserAnAdmin()) {
		CConfig::get()->AutoSave();
		RestartAsAdmin();
	}

	m_bIsOpened = false;
	m_bInitialized = true;
}

// ------------------------------------------------------------------
// Program control helpers
// ------------------------------------------------------------------
void CMenu::RestartAsAdmin()
{
	ReleaseSingleInstanceMutex();
	wchar_t exePath[MAX_PATH];
	GetModuleFileNameW(NULL, exePath, MAX_PATH);
	SHELLEXECUTEINFOW sei = { sizeof(sei) };
	sei.fMask = SEE_MASK_NOCLOSEPROCESS;
	sei.lpVerb = L"runas";
	sei.lpFile = exePath;
	sei.nShow = SW_SHOW;
	if (ShellExecuteExW(&sei)) {
		ExitProcess(0);
	}
	CConfig::get()->b["isAdmin"] = false;
}

// Launch exePath non-elevated via a temporary scheduled task (/rl LIMITED).
// This is the most reliable de-elevation method — no token APIs or privileges needed.
static bool SchtasksNonElevated(const wchar_t* exePath)
{
	wchar_t cmd[1024];

	// 1. Create a one-shot task that runs with least privilege.
	//    conhost.exe for classic console; --unelevated to fix CWD.
	swprintf_s(cmd, L"schtasks /create /tn \"RcjyDelevate\" /tr \"conhost.exe \\\"%s\\\" --unelevated\" /sc once /st 00:00 /f /rl LIMITED", exePath);

	STARTUPINFOW si = { sizeof(si) };
	si.dwFlags = STARTF_USESHOWWINDOW;
	si.wShowWindow = SW_HIDE;
	PROCESS_INFORMATION pi;

	if (!CreateProcessW(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW,
		NULL, NULL, &si, &pi))
		return false;
	WaitForSingleObject(pi.hProcess, 5000);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// 2. Run the task immediately
	swprintf_s(cmd, L"schtasks /run /tn \"RcjyDelevate\"");
	if (!CreateProcessW(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW,
		NULL, NULL, &si, &pi))
		return false;
	WaitForSingleObject(pi.hProcess, 5000);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// Give the task a moment to start the target process
	Sleep(1000);

	// 3. Delete the task
	swprintf_s(cmd, L"schtasks /delete /tn \"RcjyDelevate\" /f");
	if (!CreateProcessW(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW,
		NULL, NULL, &si, &pi))
		return false;
	WaitForSingleObject(pi.hProcess, 5000);
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	return true;
}

void CMenu::RestartProgram()
{
	ReleaseSingleInstanceMutex();
	wchar_t exePath[MAX_PATH];
	GetModuleFileNameW(NULL, exePath, MAX_PATH);

	if (!SchtasksNonElevated(exePath)) {
		ShellExecuteW(NULL, L"open", exePath, NULL, NULL, SW_SHOW);
	}

	HWND hwnd = reinterpret_cast<HWND>(RLGetWindowHandle());
	WindowGuard::SetAllowClose(hwnd, true);
	COverlay::Get()->Shutdown();
	std::exit(0);
}

void CMenu::ExitProgram()
{
	HWND hwnd = reinterpret_cast<HWND>(RLGetWindowHandle());
	WindowGuard::SetAllowClose(hwnd, true);
	COverlay::Get()->Shutdown();
	std::exit(0);
}

// ------------------------------------------------------------------
// Animation & layout
// ------------------------------------------------------------------
bool CMenu::HandleAlpha()
{
	static float alpha = 0;
	float fc = Misc::Utilities->GetDeltaTime() * 255 * 10;

	if (!m_bIsOpened && alpha > 0.f)
		alpha = std::clamp(alpha - fc / 255.f, 0.f, 1.f);

	if (m_bIsOpened && alpha < 1.f)
		alpha = std::clamp(alpha + fc / 255.f, 0.f, 1.f);

	Globals::Gui_Ctx->MenuAlpha = static_cast<int>(floor(alpha * 255));

	if (!m_bIsOpened && alpha == 0.f)
		return false;
	return true;
}

void CMenu::HandleLayout()
{
	static bool layoutLoaded = false;
	if (!layoutLoaded) {
		MenuParams.Load();
		ui::SetNextWindowPos(MenuParams.GetPos());
		layoutLoaded = true;
	}

	static float lastRenderW = 0.f, lastRenderH = 0.f;
	float curRenderW = Render::Draw->Screen.RenderWidth;
	float curRenderH = Render::Draw->Screen.RenderHeight;

	if (lastRenderW > 0.f && lastRenderH > 0.f && (curRenderW != lastRenderW || curRenderH != lastRenderH)) {
		if (MenuParams.Width > curRenderW)
			MenuParams.Width = curRenderW;
		if (MenuParams.Height > curRenderH)
			MenuParams.Height = curRenderH;
		if (MenuParams.PosX + MenuParams.Width < 100.f)
			MenuParams.PosX = curRenderW - MenuParams.Width;
		if (MenuParams.PosX > curRenderW - 100.f)
			MenuParams.PosX = curRenderW - MenuParams.Width;
		if (MenuParams.PosY < -40.f)
			MenuParams.PosY = 0.f;
		if (MenuParams.PosY > curRenderH - 40.f)
			MenuParams.PosY = curRenderH - MenuParams.Height;
		ui::SetNextWindowPos(MenuParams.GetPos());
	}

	lastRenderW = curRenderW;
	lastRenderH = curRenderH;

	ui::SetNextWindowSize(MenuParams.GetSize());
}

void CMenu::SaveLayout()
{
	GamesenseMenuFramework::Vec2 curPos  = ui::GetWindowPos();
	GamesenseMenuFramework::Vec2 curSize = ui::GetWindowSize();
	MenuParams.Save(curPos, curSize);
}

// ------------------------------------------------------------------
// Tab buttons
// ------------------------------------------------------------------
void CMenu::DrawTabButtons()
{
	ui::TabButton("A", &m_nCurrentTab, 0, 7);
	ui::TabButton("G", &m_nCurrentTab, 1, 7);
	ui::TabButton("C", &m_nCurrentTab, 2, 7);
	ui::TabButton("J", &m_nCurrentTab, 3, 7);
	ui::TabButton("D", &m_nCurrentTab, 4, 7);
	ui::TabButton("E", &m_nCurrentTab, 5, 7);
	ui::TabButton("F", &m_nCurrentTab, 6, 7);
	ui::TabButton("H", &m_nCurrentTab, 7, 7);
}

void CMenu::Tab1()
{
	CConfig* cfg = CConfig::get();

	ui::BeginChild("Itme#0", { Vec2(0,0), Vec2(3, 10) });
	{
		static bool prevIsAdmin = IsUserAnAdmin();
		bool isAdmin = IsUserAnAdmin();
		ui::Checkbox("管理员", &isAdmin);
		if (isAdmin != prevIsAdmin) {
			cfg->b["isAdmin"] = isAdmin;
			cfg->Save();
			if (isAdmin)
				RestartAsAdmin();
			else
				RestartProgram();
		}
		prevIsAdmin = isAdmin;
	}
	ui::EndChild();

	ui::BeginChild("Setting#0", { Vec2(6, 0), Vec2(3, 10) });
	ui::EndChild();
}

void CMenu::Tab2()
{
	ui::BeginChild("Itme#1", { Vec2(0,0), Vec2(3, 10) });
	ui::EndChild();
	ui::BeginChild("Setting#1", { Vec2(6, 0), Vec2(3, 10) });
	ui::EndChild();
}

void CMenu::RendererTab()
{
	CConfig* cfg = CConfig::get();

	ui::BeginChild("Itme#3", { Vec2(0,0), Vec2(3, 10) });
	{
		ui::Checkbox("显示时间", &cfg->b["time"], false, &cfg->b["time_setting"]);
	}
	ui::EndChild();

	ui::BeginChild("Setting#3", { Vec2(6, 0), Vec2(3, 10) });
	{
		if (cfg->b["time_setting"]) {
			ui::Label("显示时间");
			ui::ColorPicker("菜单颜色", cfg->c["time_text_Color"]);
			ui::Checkbox("文字边框", &cfg->b["text_border"]);
			ui::ColorPicker("文字边框颜色", cfg->c["text_border_Color"]);
			ui::SingleSelect("时间格式", &cfg->i["time_format"], {
				"24H HH:MM:SS", "24H HH:MM", "12H HH:MM:SS", "12H HH:MM",
				"24H H:M:S", "24H H:M", "12H H:M:S", "12H H:M"
			});
			ui::SliderInt("文字大小", &cfg->i["time_text_size"], 10, 100, "%d");
			ui::SliderInt("字符间距", &cfg->i["time_text_character_spacing"], 0, 100, "%d");

			float maxX = Render::Draw->Screen.RenderWidth;
			float maxY = Render::Draw->Screen.RenderHeight;
			int fontSize = cfg->i["time_text_size"];
			float charSpacing = (float)cfg->i["time_text_character_spacing"];
			RLVector2 textSize = FontManager::GetInstance().MeasureText("TimeFont", "00:00:00", fontSize);
			if (textSize.x > 0 && textSize.y > 0) {
				float textW = textSize.x + (charSpacing - 1.0f) * 7;
				float textH = textSize.y;
				if (cfg->b["text_border"]) { textW += 2; textH += 2; }
				maxX = std::max(0.0f, Render::Draw->Screen.RenderWidth - textW);
				maxY = std::max(0.0f, Render::Draw->Screen.RenderHeight - textH);
			}
			ui::SliderFloat("水平位置", &cfg->f["time_x_pos"], 0, maxX, "%0.1f", 1.0f);
			ui::SliderFloat("高度位置", &cfg->f["time_y_pos"], 0, maxY, "%0.1f", 1.0f);
			ui::Label("\n");
		}
	}
	ui::EndChild();
}

void CMenu::Tab4()
{
	CConfig* cfg = CConfig::get();

	ui::BeginChild("Itme#2", { Vec2(0,0), Vec2(3, 10) });
	{
		ui::Checkbox("键盘控制", &cfg->b["keyboard_control"], false, &cfg->b["keyboard_control_setting"]);
	}
	ui::EndChild();

	ui::BeginChild("Setting#2", { Vec2(6, 0), Vec2(3, 10) });
	{
		if (!cfg->b["keyboard_control_setting"]) {
			ui::EndChild();
			return;
		}

		ui::SingleSelect("大写控制", &cfg->i["Caps_control"], { "无", "常开", "常关" });
		ui::SingleSelect("数字控制", &cfg->i["Num_control"], { "无", "常开", "常关" });
		ui::Label("--- 白名单 (窗口程序) ---");

		static std::vector<std::string> windowList;
		static std::vector<const char*> windowListPtrs;
		static int refreshCounter = 0;
		if (++refreshCounter >= 60) {
			refreshCounter = 0;
			RefreshWindowList(windowList, windowListPtrs);
		}

		ui::Label("窗口程序列表 (点击添加):");
		if (ui::BeginListbox("窗口程序列表")) {
			for (size_t i = 0; i < windowList.size(); i++) {
				if (ui::Selectable(windowList[i].c_str(), false)) {
					AddToWhitelist(windowList[i]);
				}
			}
		}
		ui::EndListbox();

		// Whitelist entries (click to remove)
		std::string whitelist(cfg->s["keyboard_whitelist"]);
		if (!whitelist.empty()) {
			ui::Label("白名单列表 (点击删除):");

			std::vector<std::string> entries;
			size_t pos = 0;
			while (pos < whitelist.size()) {
				size_t end = whitelist.find(';', pos);
				if (end == std::string::npos) end = whitelist.size();
				std::string entry = whitelist.substr(pos, end - pos);
				if (!entry.empty()) entries.push_back(entry);
				pos = end + 1;
			}

			if (ui::BeginListbox("白名单列表")) {
				for (size_t i = 0; i < entries.size(); i++) {
					if (ui::Selectable(entries[i].c_str(), false)) {
						RemoveFromWhitelist(entries[i]);
					}
				}
			}
			ui::EndListbox();
		}
		ui::Label("\n");
	}
	ui::EndChild();
}

void CMenu::SettingTab()
{
	CConfig* cfg = CConfig::get();

	ui::BeginChild("Settings", { Vec2(0,0), Vec2(3, 10) });
	{
		ui::Label("菜单按键");
		ui::MultiKeyBind("menu_key", &cfg->i["menu_keystyle"], GuiFlags_NoKeyBindMenu);
		ui::Label("菜单颜色");
		ui::ColorPicker("菜单颜色", cfg->c["MenuColor"]);
		ui::Checkbox("开机自启", &cfg->b["autostart"]);
		ui::Checkbox("锁定菜单布局", &cfg->b["lock_menu_layout"]);
		ui::Checkbox("隐藏任务栏图标", &cfg->b["hide_taskbar_icon"]);
		{
			static bool prevShow = cfg->b["show_console"];
			ui::Checkbox("显示控制台", &cfg->b["show_console"]);
			if (cfg->b["show_console"] != prevShow) {
				HWND hCon = GetConsoleWindow();
				if (hCon)
					ShowWindow(hCon, cfg->b["show_console"] ? SW_SHOW : SW_HIDE);
				prevShow = cfg->b["show_console"];
				cfg->Save();
			}
		}
	}
	ui::EndChild();

	ui::BeginChild("其他", { Vec2(6, 0), Vec2(3, 10) });
	{
		if (ui::Button("重启程序")) {
			RestartProgram();
		}

		if (ui::Button("关闭程序")) {
			ExitProgram();
		}
	}
	ui::EndChild();
}

void CMenu::Tab6()
{
	ui::BeginChild("Itme#5", { Vec2(0,0), Vec2(3, 10) });
	ui::EndChild();
	ui::BeginChild("Setting#5", { Vec2(6, 0), Vec2(3, 10) });
	ui::EndChild();
}

void CMenu::Tab7()
{
	ui::BeginChild("Itme#6", { Vec2(0,0), Vec2(3, 10) });
	ui::EndChild();
	ui::BeginChild("Setting#6", { Vec2(6, 0), Vec2(3, 10) });
	ui::EndChild();
}

void CMenu::ConfigTab()
{
	CConfig* cfg = CConfig::get();

	ui::BeginChild("参数", { Vec2(0,0), Vec2(3, 10) });

	if (ui::BeginListbox("参数列表")) {
		for (auto config : CConfig::get()->List) {
			if (ui::Selectable(config.c_str(), CConfig::get()->Current == config.c_str()))
				strcpy_s(cfg->s["config_name"], config.c_str());
		}
	}
	ui::EndListbox();

	ui::InputText("参数输入框", cfg->s["config_name"]);

	bool hasName = (cfg->s["config_name"][0] != 0);

	if (ui::Button("加载") && hasName)
		CConfig::get()->Load();

	if (ui::Button("保存") && hasName)
		CConfig::get()->Save();

	if (ui::Button("删除") && hasName)
		CConfig::get()->Delete();

	if (ui::Button("重置"))
		CConfig::get()->LoadDefaults();

	ui::EndChild();

	ui::BeginChild("Setting#0", { Vec2(6, 0), Vec2(3, 10) });
	ui::EndChild();
}

// ------------------------------------------------------------------
// Whitelist helpers
// ------------------------------------------------------------------
void CMenu::RefreshWindowList(std::vector<std::string>& windowList, std::vector<const char*>& windowListPtrs)
{
	windowList.clear();
	windowListPtrs.clear();

	std::set<std::string> seen;
	HWND hwnd = GetTopWindow(NULL);
	while (hwnd) {
		if (IsWindowVisible(hwnd)) {
			DWORD pid = 0;
			GetWindowThreadProcessId(hwnd, &pid);
			if (pid != 0) {
				HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
				if (hProc) {
					char exeName[MAX_PATH] = "";
					DWORD size = MAX_PATH;
					if (QueryFullProcessImageNameA(hProc, 0, exeName, &size)) {
						const char* base = strrchr(exeName, '\\');
						std::string name = base ? (base + 1) : exeName;
						if (seen.find(name) == seen.end()) {
							seen.insert(name);
							windowList.push_back(name);
						}
					}
					CloseHandle(hProc);
				}
			}
		}
		hwnd = GetNextWindow(hwnd, GW_HWNDNEXT);
	}

	std::sort(windowList.begin(), windowList.end(), [](const std::string& a, const std::string& b) {
		return _stricmp(a.c_str(), b.c_str()) < 0;
	});

	for (auto& s : windowList)
		windowListPtrs.push_back(s.c_str());
}

void CMenu::AddToWhitelist(const std::string& name)
{
	CConfig* cfg = CConfig::get();
	std::string whitelist(cfg->s["keyboard_whitelist"]);

	// Check if already exists
	size_t pos = 0;
	while (pos < whitelist.size()) {
		size_t end = whitelist.find(';', pos);
		if (end == std::string::npos) end = whitelist.size();
		std::string entry = whitelist.substr(pos, end - pos);
		if (_stricmp(entry.c_str(), name.c_str()) == 0)
			return;
		pos = end + 1;
	}

	std::string updated = whitelist.empty() ? name : (whitelist + ";" + name);
	strncpy_s(cfg->s["keyboard_whitelist"], 255, updated.c_str(), 254);
}

void CMenu::RemoveFromWhitelist(const std::string& name)
{
	CConfig* cfg = CConfig::get();
	std::string whitelist(cfg->s["keyboard_whitelist"]);

	// Parse entries and rebuild without the removed one
	std::vector<std::string> entries;
	size_t pos = 0;
	while (pos < whitelist.size()) {
		size_t end = whitelist.find(';', pos);
		if (end == std::string::npos) end = whitelist.size();
		std::string entry = whitelist.substr(pos, end - pos);
		if (!entry.empty()) entries.push_back(entry);
		pos = end + 1;
	}

	std::string newWhitelist;
	for (size_t j = 0; j < entries.size(); j++) {
		if (_stricmp(entries[j].c_str(), name.c_str()) != 0) {
			if (!newWhitelist.empty()) newWhitelist += ";";
			newWhitelist += entries[j];
		}
	}
	strncpy_s(cfg->s["keyboard_whitelist"], 255, newWhitelist.c_str(), 254);
}

// ------------------------------------------------------------------
// Main Draw
// ------------------------------------------------------------------
void CMenu::Draw()
{
	if (!HandleAlpha())
		return;

	HandleLayout();

	ui::Begin("Rcjy", GuiFlags_None);

	DrawTabButtons();

	switch (m_nCurrentTab) {
		case 0: Tab1();       	break;
		case 1: Tab2();       	break;
		case 2: RendererTab();	break;
		case 3: Tab4();       	break;
		case 4: SettingTab(); 	break;
		case 5: Tab6();    	  	break;
		case 6: Tab7(); 	  	break;
		case 7: ConfigTab();  	break;
	}

	ui::End();

	SaveLayout();
}

// ------------------------------------------------------------------
// Public accessors
// ------------------------------------------------------------------
bool CMenu::IsMenuOpened()
{
	return m_bIsOpened;
}

void CMenu::SetMenuOpened(bool v)
{
	// When opening the menu, cancel any active QuickEdit selection so log
	// output (e.g. config load/save) doesn't get stuck on the console.
	if (v)
		Console::Get()->CancelSelectionIfActive();

	m_bIsOpened = v;
}

RLColor CMenu::GetMenuColor()
{
	GuiContext* g = Globals::Gui_Ctx;
	CConfig* cfg = CConfig::get();
	return RLColor{
		(unsigned char)(cfg->c["MenuColor"][0]),
		(unsigned char)(cfg->c["MenuColor"][1]),
		(unsigned char)(cfg->c["MenuColor"][2]),
		(unsigned char)(std::min(cfg->c["MenuColor"][3], g->MenuAlpha))
	};
}
