/* Copyright 2021 Aristocratos (jakob@qvantnet.com)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

indent = tab
tab-size = 4
*/

#include "btop_menu.hpp"

#include "btop_config.hpp"
#include "btop_draw.hpp"
#include "btop_log.hpp"
#include "btop_shared.hpp"
#include "btop_theme.hpp"
#include "btop_tools.hpp"

#include <errno.h>
#include <signal.h>

#include <array>
#include <cmath>
#include <filesystem>
#include <iostream>
#include <unordered_map>
#include <utility>

#include <fmt/format.h>

using std::array;
using std::ceil;
using std::max;
using std::min;
using std::ref;
using std::views::iota;

using namespace Tools;

namespace fs = std::filesystem;

namespace Menu {

   atomic<bool> active (false);
   string bg;
   bool redraw{true};
   int currentMenu = -1;
   msgBox messageBox;
   int signalToSend{};
   int signalKillRet{};

   const array<string, 32> P_Signals = {
	   "0",
#ifdef __linux__
#if defined(__hppa__)
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "SIGSTKFLT", "SIGFPE",
		"SIGKILL", "SIGBUS", "SIGSEGV", "SIGXCPU",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1",
		"SIGUSR2", "SIGCHLD", "SIGPWR", "SIGVTALRM",
		"SIGPROF", "SIGIO", "SIGWINCH", "SIGSTOP",
		"SIGTSTP", "SIGCONT", "SIGTTIN", "SIGTTOU",
		"SIGURG", "SIGXFSZ", "SIGSYS"
#elif defined(__mips__)
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
		"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGUSR1",
		"SIGUSR2", "SIGCHLD", "SIGPWR", "SIGWINCH",
		"SIGURG", "SIGIO", "SIGSTOP", "SIGTSTP",
		"SIGCONT", "SIGTTIN", "SIGTTOU", "SIGVTALRM",
		"SIGPROF", "SIGXCPU", "SIGXFSZ"
#elif defined(__alpha__)
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
		"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
		"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
		"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
		"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
		"SIGPWR", "SIGUSR1", "SIGUSR2"
#elif defined (__sparc__)
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
		"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
		"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
		"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
		"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
		"SIGLOST", "SIGUSR1", "SIGUSR2"
#else
		"SIGHUP", "SIGINT",	"SIGQUIT",	"SIGILL",
		"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE",
		"SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT",
		"SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP",
		"SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
		"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
		"SIGIO", "SIGPWR", "SIGSYS"
#endif
#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__APPLE__)
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "SIGEMT", "SIGFPE",
		"SIGKILL", "SIGBUS", "SIGSEGV", "SIGSYS",
		"SIGPIPE", "SIGALRM", "SIGTERM", "SIGURG",
		"SIGSTOP", "SIGTSTP", "SIGCONT", "SIGCHLD",
		"SIGTTIN", "SIGTTOU", "SIGIO", "SIGXCPU",
		"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH",
		"SIGINFO", "SIGUSR1", "SIGUSR2"
#else
		"SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
		"SIGTRAP", "SIGABRT", "7", "SIGFPE",
		"SIGKILL", "10", "SIGSEGV", "12",
		"SIGPIPE", "SIGALRM", "SIGTERM", "16",
		"17", "18", "19", "20",
		"21", "22", "23", "24",
		"25", "26", "27", "28",
		"29", "30", "31"
#endif
	};

  std::unordered_map<string, Input::Mouse_loc> mouse_mappings;

   const array<array<string, 3>, 3> menu_normal = {
		array<string, 3>{
			"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
			"│ │├─┘ │ ││ ││││└─┐",
			"└─┘┴   ┴ ┴└─┘┘└┘└─┘"
		},
		{
			"┬ ┬┌─┐┬  ┌─┐",
			"├─┤├┤ │  ├─┘",
			"┴ ┴└─┘┴─┘┴  "
		},
		{
			"┌─┐ ┬ ┬ ┬┌┬┐",
			"│─┼┐│ │ │ │ ",
			"└─┘└└─┘ ┴ ┴ "
		}
	};

	const array<array<string, 3>, 3> menu_selected = {
		array<string, 3>{
			"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
			"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
			"╚═╝╩   ╩ ╩╚═╝╝╚╝╚═╝"
		},
		{
			"╦ ╦╔═╗╦  ╔═╗",
			"╠═╣╠╣ ║  ╠═╝",
			"╩ ╩╚═╝╩═╝╩  "
		},
		{
			"╔═╗ ╦ ╦ ╦╔╦╗ ",
			"║═╬╗║ ║ ║ ║  ",
			"╚═╝╚╚═╝ ╩ ╩  "
		}
	};

	const array<int, 3> menu_width = {19, 12, 12};

	const vector<array<string, 2>> help_text = {
		{"Mouse 1", "Clicks buttons and selects in process list."},
		{"Mouse scroll", "Scrolls any scrollable list/text under cursor."},
		{"Esc, m", "Toggles main menu."},
		{"p", "Cycle view presets forwards."},
		{"shift + p", "Cycle view presets backwards."},
		{"1", "Toggle CPU box."},
		{"2", "Toggle MEM box."},
		{"3", "Toggle NET box."},
		{"4", "Toggle PROC box."},
		{"5", "Toggle GPU box."},
		{"d", "Toggle disks view in MEM box."},
		{"F2, o", "Shows options."},
		{"F1, ?, h", "Shows this window."},
		{"ctrl + z", "Sleep program and put in background."},
		{"ctrl + r", "Reloads config file from disk."},
		{"q, ctrl + c", "Quits program."},
		{"+, -", "Add/Subtract 100ms to/from update timer."},
		{"Up, Down", "Select in process list."},
		{"Enter", "Show detailed information for selected process."},
		{"Spacebar", "Expand/collapse the selected process in tree view."},
		{"C", "Expand/collapse the selected process' children."},
		{"Pg Up, Pg Down", "Jump 1 page in process list."},
		{"Home, End", "Jump to first or last page in process list."},
		{"Left, Right", "Select previous/next sorting column."},
		{"b, n", "Select previous/next network device."},
		{"i", "Toggle disks io mode with big graphs."},
		{"z", "Toggle totals reset for current network device"},
		{"a", "Toggle auto scaling for the network graphs."},
		{"y", "Toggle synced scaling mode for network graphs."},
		{"f, /", "To enter a process filter. Start with ! for regex."},
		{"F", "Follow selected process."},
		{"u", "Pause process list."},
		{"delete", "Clear any entered filter."},
		{"c", "Toggle per-core cpu usage of processes."},
		{"r", "Reverse sorting order in processes box."},
		{"e", "Toggle processes tree view."},
		{"E", "Collapse/expand all processes in tree view."},
		{"%", "Toggles memory display mode in processes box."},
		{"Selected +, -", "Expand/collapse the selected process in tree view."},
		{"Selected t", "Terminate selected process with SIGTERM - 15."},
		{"Selected k", "Kill selected process with SIGKILL - 9."},
		{"Selected s", "Select or enter signal to send to process."},
		{"Selected N", "Select new nice value for selected process."},
		{"", " "},
		{"", "For bug reporting and project updates, visit:"},
		{"", "https://github.com/aristocratos/btop"},
	};

	const vector<vector<vector<string>>> categories = {
		{
			{"color_theme",
				"Set color theme.",
				"",
				"Choose from all theme files in (usually)",
				"\"/usr/[local/]share/btop/themes\" and",
				"\"~/.config/btop/themes\".",
				"",
				"\"Default\" for builtin default theme.",
				"\"TTY\" for builtin 16-color theme.",
				"",
				"For theme updates see:",
				"https://github.com/aristocratos/btop"},
			{"theme_background",
				"If the theme set background should be shown.",
				"",
				"Set to False if you want terminal background",
				"transparency."},
			{"truecolor",
				"Sets if 24-bit truecolor should be used.",
				"",
				"Will convert 24-bit colors to 256 color",
				"(6x6x6 color cube) if False.",
				"",
				"Set to False if your terminal doesn't have",
				"truecolor support and can't convert to",
				"256-color."},
			{"force_tty",
				"TTY mode.",
				"",
				"Set to true to force tty mode regardless",
				"if a real tty has been detected or not.",
				"",
				"Will force 16-color mode and TTY theme,",
				"set all graph symbols to \"tty\" and swap",
				"out other non tty friendly symbols."},
			{"vim_keys",
				"Enable vim keys.",
				"Set to True to enable \"h,j,k,l\" keys for",
				"directional control in lists.",
				"",
				"Conflicting keys for",
				"h (help) and k (kill)",
				"is accessible while holding shift."},
			{"disable_mouse",
				"Disable all mouse events."},
			{"disable_presets",
				"Disable the presets.",
				"",
				"\"Off\" All presets are enabled.",
				"",
				"\"Default\" preset is disabled.",
				"",
				"\"Custom\" presets are disabled.",
				"",
				"\"All\" presets are disabled."},
			{"presets",
				"Define presets for the layout of the boxes.",
				"",
				"Preset 0 is always all boxes shown with",
				"default settings.",
				"Max 9 presets.",
				"",
				"Format: \"box_name:P:G,box_name:P:G\"",
				"P=(0 or 1) for alternate positions.",
				"G=graph symbol to use for box.",
				"",
				"Use whitespace \" \" as separator between",
				"different presets.",
				"",
				"Example:",
				"\"mem:0:tty,proc:1:default cpu:0:braille\""},
			{"shown_boxes",
				"Manually set which boxes to show.",
				"",
				"Available values are \"cpu mem net proc\".",
			#ifdef GPU_SUPPORT
				"Or \"gpu0\" through \"gpu5\" for GPU boxes.",
			#endif
				"Separate values with whitespace.",
				"",
				"Toggle between presets with key \"p\"."},
			{"update_ms",
				"Update time in milliseconds.",
				"",
				"Recommended 2000 ms or above for better",
				"sample times for graphs.",
				"",
				"Min value: 100 ms",
				"Max value: 86400000 ms = 24 hours."},
			{"rounded_corners",
				"Rounded corners on boxes.",
				"",
				"True or False",
				"",
				"Is always False if TTY mode is ON."},
			{"terminal_sync",
				"Output synchronization.",
				"",
				"Use terminal synchronized output sequences",
				"to reduce flickering on supported terminals.",
				"",
				"True or False."},
			{"graph_symbol",
				"Default symbols to use for graph creation.",
				"",
				"\"braille\", \"block\" or \"tty\".",
				"",
				"\"braille\" offers the highest resolution but",
				"might not be included in all fonts.",
				"",
				"\"block\" has half the resolution of braille",
				"but uses more common characters.",
				"",
				"\"tty\" uses only 3 different symbols but will",
				"work with most fonts.",
				"",
				"Note that \"tty\" only has half the horizontal",
				"resolution of the other two,",
				"so will show a shorter historical view."},
			{"clock_format",
				"Draw a clock at top of screen.",
				"(Only visible if cpu box is enabled!)",
				"",
				"Formatting according to strftime, empty",
				"string to disable.",
				"",
				"Custom formatting options:",
				"\"/host\" = hostname",
				"\"/user\" = username",
				"\"/uptime\" = system uptime",
				"",
				"Examples of strftime formats:",
				"\"%X\" = locale HH:MM:SS",
				"\"%H\" = 24h hour, \"%I\" = 12h hour",
				"\"%M\" = minute, \"%S\" = second",
				"\"%d\" = day, \"%m\" = month, \"%y\" = year"},
			{"base_10_sizes",
				"Use base 10 for bits and bytes sizes.",
				"",
				"Uses KB = 1000 instead of KiB = 1024,",
				"MB = 1000KB instead of MiB = 1024KiB,",
				"and so on.",
				"",
				"True or False."},
			{"background_update",
				"Update main ui when menus are showing.",
				"",
				"True or False.",
				"",
				"Set this to false if the menus is flickering",
				"too much for a comfortable experience."},
			{"show_battery",
				"Show battery stats.",
				"(Only visible if cpu box is enabled!)",
				"",
				"Show battery stats in the top right corner",
				"if a battery is present."},
			{"selected_battery",
				"Select battery.",
				"",
				"Which battery to use if multiple are present.",
				"Can be both batteries and UPS.",
				"",
				"\"Auto\" for auto detection."},
			{"show_battery_watts",
				"Show battery power.",
				"",
				"Show discharge power when discharging.",
				"Show charging power when charging."},
			{"log_level",
				"Set loglevel for error.log",
				"",
				"\"ERROR\", \"WARNING\", \"INFO\" and \"DEBUG\".",
				"",
				"The level set includes all lower levels,",
				"i.e. \"DEBUG\" will show all logging info."},
			{"save_config_on_exit",
				"Save config on exit.",
				"",
				"Automatically save current settings to",
				"config file on exit.",
				"",
				"When this is toggled from True to False",
				"a save is immediately triggered.",
				"This way a manual save can be done by",
				"toggling this setting on and off again."}
		},
		{
			{"cpu_bottom",
				"Cpu box location.",
				"",
				"Show cpu box at bottom of screen instead",
				"of top."},
			{"graph_symbol_cpu",
				"Graph symbol to use for graphs in cpu box.",
				"",
				"\"default\", \"braille\", \"block\" or \"tty\".",
				"",
				"\"default\" for the general default symbol.",},
			{"cpu_graph_upper",
				"Cpu upper graph.",
				"",
				"Sets the CPU/GPU stat shown in upper half of",
				"the CPU graph.",
				"",
				"CPU:",
				"\"total\" = Total cpu usage. (Auto)",
				"\"user\" = User mode cpu usage.",
				"\"system\" = Kernel mode cpu usage.",
				"+ more depending on kernel.",
		#ifdef GPU_SUPPORT
				"",
				"GPU:",
				"\"gpu-totals\" = GPU usage split by device.",
				"\"gpu-vram-totals\" = VRAM usage split by GPU.",
				"\"gpu-pwr-totals\" = Power usage split by GPU.",
				"\"gpu-average\" = Avg usage of all GPUs.",
				"\"gpu-vram-total\" = VRAM usage of all GPUs.",
				"\"gpu-pwr-total\" = Power usage of all GPUs.",
				"Not all stats are supported on all devices."
		#endif
				},
			{"cpu_graph_lower",
				"Cpu lower graph.",
				"",
				"Sets the CPU/GPU stat shown in lower half of",
				"the CPU graph.",
				"",
				"CPU:",
				"\"total\" = Total cpu usage.",
				"\"user\" = User mode cpu usage.",
				"\"system\" = Kernel mode cpu usage.",
				"+ more depending on kernel.",
		#ifdef GPU_SUPPORT
				"",
				"GPU:",
				"\"gpu-totals\" = GPU usage split/device. (Auto)",
				"\"gpu-vram-totals\" = VRAM usage split by GPU.",
				"\"gpu-pwr-totals\" = Power usage split by GPU.",
				"\"gpu-average\" = Avg usage of all GPUs.",
				"\"gpu-vram-total\" = VRAM usage of all GPUs.",
				"\"gpu-pwr-total\" = Power usage of all GPUs.",
				"Not all stats are supported on all devices."
		#endif
				},
			{"cpu_invert_lower",
					"Toggles orientation of the lower CPU graph.",
					"",
					"True or False."},
			{"cpu_single_graph",
					"Completely disable the lower CPU graph.",
					"",
					"Shows only upper CPU graph and resizes it",
					"to fit to box height.",
					"",
					"True or False."},
		#ifdef GPU_SUPPORT
			{"show_gpu_info",
					"Show gpu info in cpu box.",
					"",
					"Toggles gpu stats in cpu box and the",
					"gpu graph (if \"cpu_graph_lower\" is set to",
					"\"Auto\").",
					"",
					"\"Auto\" to show when no gpu box is shown.",
					"\"On\" to always show.",
					"\"Off\" to never show."},
		#endif
			{"check_temp",
				"Enable cpu temperature reporting.",
				"",
				"True or False."},
			{"cpu_sensor",
				"Cpu temperature sensor.",
				"",
				"Select the sensor that corresponds to",
				"your cpu temperature.",
				"",
				"Set to \"Auto\" for auto detection."},
			{"show_coretemp",
				"Show temperatures for cpu cores.",
				"",
				"Only works if check_temp is True and",
				"the system is reporting core temps."},
			{"cpu_core_map",
				"Custom mapping between core and coretemp.",
				"",
				"Can be needed on certain cpus to get correct",
				"temperature for correct core.",
				"",
				"Use lm-sensors or similar to see which cores",
				"are reporting temperatures on your machine.",
				"",
				"Format: \"X:Y\"",
				"X=core with wrong temp.",
				"Y=core with correct temp.",
				"Use space as separator between multiple",
				"entries.",
				"",
				"Example: \"4:0 5:1 6:3\""},
			{"temp_scale",
				"Which temperature scale to use.",
				"",
				"Celsius, default scale.",
				"",
				"Fahrenheit, the american one.",
				"",
				"Kelvin, 0 = absolute zero, 1 degree change",
				"equals 1 degree change in Celsius.",
				"",
				"Rankine, 0 = absolute zero, 1 degree change",
				"equals 1 degree change in Fahrenheit."},
			{"show_cpu_freq",
				"Show CPU frequency.",
				"",
				"Can cause slowdowns on systems with many",
				"cores and certain kernel versions."},
		#ifdef __linux__
			{"freq_mode",
				"How the CPU frequency will be displayed.",
				"",
				"First, get the frequency from the first",
				"core.",
				"",
				"Range, show the lowest and the highest",
				"frequency.",
				"",
				"Lowest, the lowest frequency.",
				"",
				"Highest, the highest frequency.",
				"",
				"Average, sum and divide."},
		#endif
			{"custom_cpu_name",
				"Custom cpu model name in cpu percentage box.",
				"",
				"Empty string to disable."},
			{"show_uptime",
				"Shows the system uptime in the CPU box.",
				"",
				"Can also be shown in the clock by using",
				"\"/uptime\" in the formatting.",
				"",
				"True or False."},
			{"show_cpu_watts",
				"Shows the CPU power consumption in watts.",
				"",
				"Requires running `make setcap` or",
				"`make setuid` or running with sudo.",
				"",
				"True or False."},
		},
	#ifdef GPU_SUPPORT
		{
			{"nvml_measure_pcie_speeds",
				"Measure PCIe throughput on NVIDIA cards.",
				"",
				"May impact performance on certain cards.",
				"",
				"True or False."},
			{"rsmi_measure_pcie_speeds",
				"Measure PCIe throughput on AMD cards.",
				"",
				"May impact performance on certain cards.",
				"",
				"True or False."},
			{"graph_symbol_gpu",
				"Graph symbol to use for graphs in gpu box.",
				"",
				"\"default\", \"braille\", \"block\" or \"tty\".",
				"",
				"\"default\" for the general default symbol.",},
			{"gpu_mirror_graph",
				"Horizontally mirror the GPU graph.",
				"",
				"True or False."},
			{"shown_gpus",
				"Manually set which gpu vendors to show.",
				"",
				"Available values are",
				"\"nvidia\", \"amd\", \"intel\",",
				"and \"apple\".",
				"Separate values with whitespace.",
				"",
				"A restart is required to apply changes."},
			{"custom_gpu_name0",
				"Custom gpu0 model name in gpu stats box.",
				"",
				"Empty string to disable."},
			{"custom_gpu_name1",
				"Custom gpu1 model name in gpu stats box.",
				"",
				"Empty string to disable."},
			{"custom_gpu_name2",
				"Custom gpu2 model name in gpu stats box.",
				"",
				"Empty string to disable."},
			{"custom_gpu_name3",
				"Custom gpu3 model name in gpu stats box.",
				"",
				"Empty string to disable."},
			{"custom_gpu_name4",
				"Custom gpu4 model name in gpu stats box.",
				"",
				"Empty string to disable."},
			{"custom_gpu_name5",
				"Custom gpu5 model name in gpu stats box.",
				"",
				"Empty string to disable."},
		},
	#endif
		{
			{"mem_below_net",
				"Mem box location.",
				"",
				"Show mem box below net box instead of above."},
			{"graph_symbol_mem",
				"Graph symbol to use for graphs in mem box.",
				"",
				"\"default\", \"braille\", \"block\" or \"tty\".",
				"",
				"\"default\" for the general default symbol.",},
			{"mem_graphs",
				"Show graphs for memory values.",
				"",
				"True or False."},
			{"show_disks",
				"Split memory box to also show disks.",
				"",
				"True or False."},
			{"show_io_stat",
				"Toggle IO activity graphs.",
				"",
				"Show small IO graphs that for disk activity",
				"(disk busy time) when not in IO mode.",
				"",
				"True or False."},
			{"io_mode",
				"Toggles io mode for disks.",
				"",
				"Shows big graphs for disk read/write speeds",
				"instead of used/free percentage meters.",
				"",
				"True or False."},
			{"io_graph_combined",
				"Toggle combined read and write graphs.",
				"",
				"Only has effect if \"io mode\" is True.",
				"",
				"True or False."},
			{"io_graph_speeds",
				"Set top speeds for the io graphs.",
				"",
				"Manually set which speed in MiB/s that",
				"equals 100 percent in the io graphs.",
				"(100 MiB/s by default).",
				"",
				"Format: \"device:speed\" separate disks with",
				"whitespace \" \".",
				"",
				"Example: \"/dev/sda:100, /dev/sdb:20\"."},
			{"show_swap",
				"If swap memory should be shown in memory box.",
				"",
				"True or False."},
			{"swap_disk",
				"Show swap as a disk.",
				"",
				"Ignores show_swap value above.",
				"Inserts itself after first disk."},
			{"only_physical",
				"Filter out non physical disks.",
				"",
				"Set this to False to include network disks,",
				"RAM disks and similar.",
				"",
				"True or False."},
			{"use_fstab",
				"(Linux) Read disks list from /etc/fstab.",
				"",
				"This also disables only_physical.",
				"",
				"True or False."},
			{"zfs_hide_datasets",
				"(Linux) Hide ZFS datasets in disks list.",
				"",
				"Setting this to True will hide all datasets,",
				"and only show ZFS pools.",
				"",
				"(IO stats will be calculated per-pool)",
				"",
				"True or False."},
			{"disk_free_priv",
				"(Linux) Type of available disk space.",
				"",
				"Set to true to show how much disk space is",
				"available for privileged users.",
				"",
				"Set to false to show available for normal",
				"users."},
			{"disks_filter",
				"Optional filter for shown disks.",
				"",
				"Should be full path of a mountpoint.",
				"Separate multiple values with",
				"whitespace \" \".",
				"",
				"Only disks matching the filter will be shown.",
				"Prepend \033[3mexclude=\033[23m to only show disks ",
				"not matching the filter.",
				"",
				"Examples:",
				"/boot /home/user",
				"exclude=/boot /home/user"},
			{"zfs_arc_cached",
				"(Linux) Count ZFS ARC as cached memory.",
				"",
				"Add ZFS ARC used to cached memory and",
				"ZFS ARC available to available memory.",
				"These are otherwise reported by the Linux",
				"kernel as used memory.",
				"",
				"True or False."},
		},
		{
			{"graph_symbol_net",
				"Graph symbol to use for graphs in net box.",
				"",
				"\"default\", \"braille\", \"block\" or \"tty\".",
				"",
				"\"default\" for the general default symbol.",},
			{"swap_upload_download",
				"Swap the positions of the upload and download",
				"graphs.",
				"",
				"This allows for a more \"intuitive\" view",
				"with download being down, on the bottom."},
			{"net_download",
				"Fixed network graph download value.",
				"",
				"Value in Mebibits, default \"100\".",
				"",
				"Can be toggled with auto button."},
			{"net_upload",
				"Fixed network graph upload value.",
				"",
				"Value in Mebibits, default \"100\".",
				"",
				"Can be toggled with auto button."},
			{"net_auto",
				"Start in network graphs auto rescaling mode.",
				"",
				"Ignores any values set above at start and",
				"rescales down to 10Kibibytes at the lowest.",
				"",
				"True or False."},
			{"net_sync",
				"Network scale sync.",
				"",
				"Syncs the scaling for download and upload to",
				"whichever currently has the highest scale.",
				"",
				"True or False."},
			{"net_iface",
				"Network Interface.",
				"",
				"Manually set the starting Network Interface.",
				"",
				"Will otherwise automatically choose the NIC",
				"with the highest total download since boot."},
		    {"base_10_bitrate",
			    "Base 10 bitrate",
			    "",
			    "True:  Use SI prefixes for bitrates",
			    "       (1000Kbps = 1Mbps)",
			    "False: Use binary prefixes for bitrates",
			    "       (1024Kibps = 1Mibps)",
			    "Auto:  Use the General -> Base 10 Sizes",
			    "       setting for bitrates",
			    "",
			    "True, False, or Auto",},
		},
		{
			{"proc_left",
				"Proc box location.",
				"",
				"Show proc box on left side of screen",
				"instead of right."},
			{"graph_symbol_proc",
				"Graph symbol to use for graphs in proc box.",
				"",
				"\"default\", \"braille\", \"block\" or \"tty\".",
				"",
				"\"default\" for the general default symbol.",},
			{"proc_sorting",
				"Processes sorting option.",
				"",
				"Possible values:",
				"\"pid\", \"program\", \"arguments\", \"threads\",",
				"\"user\", \"memory\", \"cpu lazy\" and",
				"\"cpu direct\".",
				"",
				"\"cpu lazy\" updates top process over time.",
				"\"cpu direct\" updates top process",
				"directly."},
			{"proc_reversed",
				"Reverse processes sorting order.",
				"",
				"True or False."},
			{"proc_tree",
				"Processes tree view.",
				"",
				"Set true to show processes grouped by",
				"parents with lines drawn between parent",
				"and child process."},
			{"proc_aggregate",
				"Aggregate child's resources in parent.",
				"",
				"In tree-view, include all child resources",
				"with the parent even while expanded."},
			{"proc_colors",
				"Enable colors in process view.",
				"",
				"True or False."},
			{"proc_gradient",
				"Enable process view gradient fade.",
				"",
				"Fades from top or current selection.",
				"Max fade value is equal to current themes",
				"\"inactive_fg\" color value."},
			{"proc_per_core",
				"Process usage per core.",
				"",
				"If process cpu usage should be of the core",
				"it's running on or usage of the total",
				"available cpu power.",
				"",
				"If true and process is multithreaded",
				"cpu usage can reach over 100%."},
			{"proc_mem_bytes",
				"Show memory as bytes in process list.",
				" ",
				"Will show percentage of total memory",
				"if False."},
			{"keep_dead_proc_usage",
				"Cpu and Mem usage for dead processes",
				"",
				"Set true if process should preserve the cpu",
				"and memory usage of when it died while",
				"paused."},
			{"proc_cpu_graphs",
				"Show cpu graph for each process.",
				"",
				"True or False"},
			{"proc_filter_kernel",
				"(Linux) Filter kernel processes from output.",
				"",
				"Set to 'True' to filter out internal",
				"processes started by the Linux kernel."},
			{"proc_follow_detailed",
				"Follow selected process with detailed view",
				"",
				"If set to 'True' then when opening the",
				"detailed view, the process will be",
				"followed in the list. Pressing enter",
				"again will close the detailed view",
				"and stop following the process."},
		}
	};

	msgBox::msgBox() {}
	msgBox::msgBox(int width, int boxtype, const vector<string>& content, const std::string_view title)
	: width(width), boxtype(boxtype) {
		auto tty_mode = Config::getB("tty_mode");
		auto rounded = Config::getB("rounded_corners");
		const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up);
		const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up);
		const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down);
		const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down);
		height = content.size() + 7;
		x = Term::width / 2 - width / 2;
		y = Term::height/2 - height/2;
		if (boxtype == 2) selected = 1;


		button_left = left_up + Symbols::h_line * 6 + Mv::l(7) + Mv::d(2) + left_down + Symbols::h_line * 6 + Mv::l(7) + Mv::u(1) + Symbols::v_line;
		button_right = Symbols::v_line + Mv::l(7) + Mv::u(1) + Symbols::h_line * 6 + right_up + Mv::l(7) + Mv::d(2) + Symbols::h_line * 6 + right_down + Mv::u(2);

		box_contents = Draw::createBox(x, y, width, height, Theme::c("hi_fg"), true, title) + Mv::d(1);
		for (const auto& line : content) {
			box_contents += Mv::save + Mv::r(max((size_t)0, (width / 2) - (Fx::uncolor(line).size() / 2) - 1)) + line + Mv::restore + Mv::d(1);
		}
	}

	string msgBox::operator()() {
		string out;
		int pos = width / 2 - (boxtype == 0 ? 6 : 14);
		const auto first_color = (selected == 0 ? Theme::c("hi_fg") : Theme::c("div_line"));
		out = Mv::d(1) + Mv::r(pos) + Fx::b + first_color + button_left + (selected == 0 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
			+ (boxtype == 0 ? "    Ok    " : "    Yes    ") + first_color + button_right;
		mouse_mappings["button1"] = Input::Mouse_loc{y + height - 4, x + pos + 1, 3, 12 + (boxtype > 0 ? 1 : 0)};
		if (boxtype > 0) {
			const auto second_color = (selected == 1 ? Theme::c("hi_fg") : Theme::c("div_line"));
			out += Mv::r(2) + second_color + button_left + (selected == 1 ? Theme::c("title") : Theme::c("main_fg") + Fx::ub)
				+ "    No    " + second_color + button_right;
			mouse_mappings["button2"] = Input::Mouse_loc{y + height - 4, x + pos + 15 + (boxtype > 0 ? 1 : 0), 3, 12};
		}
		return box_contents + out + Fx::reset;
	}

	//? Process input
	int msgBox::input(const string& key) {
		if (key.empty()) return Invalid;

		if (is_in(key, "escape", "backspace", "q") or key == "button2") {
			return No_Esc;
		}
		else if (key == "button1" or (boxtype == 0 and str_to_upper(key) == "O")) {
			return Ok_Yes;
		}
		else if (is_in(key, "enter", "space")) {
			return selected + 1;
		}
		else if (boxtype == 0) {
			return Invalid;
		}
		else if (str_to_upper(key) == "Y") {
			return Ok_Yes;
		}
		else if (str_to_upper(key) == "N") {
			return No_Esc;
		}
		else if (is_in(key, "right", "tab")) {
			if (++selected > 1) selected = 0;
			return Select;
		}
		else if (is_in(key, "left", "shift_tab")) {
			if (--selected < 0) selected = 1;
			return Select;
		}

		return Invalid;
	}

	void msgBox::clear() {
		box_contents.clear();
		box_contents.shrink_to_fit();
		button_left.clear();
		button_left.shrink_to_fit();
		button_right.clear();
		button_right.shrink_to_fit();
		mouse_mappings.erase("button1");
		mouse_mappings.erase("button2");
	}

	enum menuReturnCodes {
		NoChange,
		Changed,
		Closed,
		Switch
	};

	static int signalChoose(const string& key) {
		static std::optional<int> s_pid;
		static int x{};
		static int y{};
		static int selected_signal = -1;

		if (!s_pid) {
			s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
		}

		if (bg.empty()) selected_signal = -1;
		auto& out = Global::overlay;
		int retval = Changed;

		if (redraw) {
			x = Term::width/2 - 40;
			y = Term::height/2 - 9;
			bg = Draw::createBox(x + 2, y, 78, 19, Theme::c("hi_fg"), true, "signals");
			bg += Mv::to(y+2, x+3) + Theme::c("title") + Fx::b + cjust("Send signal to PID " + to_string(s_pid.value()) + " ("
				+ uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 30) + ")", 76);
		}
		else if (is_in(key, "escape", "q")) {
			goto MenuClosing;
		}
		else if (key.starts_with("button_")) {
			if (int new_select = stoi(key.substr(7)); new_select == selected_signal)
				goto ChooseEntering;
			else
				selected_signal = new_select;
		}
		else if (is_in(key, "enter", "space") and selected_signal >= 0) {
			ChooseEntering:
			signalKillRet = 0;
			if (s_pid < 1) {
				signalKillRet = ESRCH;
				menuMask.set(SignalReturn);
			}
			else if (kill(s_pid.value(), selected_signal) != 0) {
				signalKillRet = errno;
				menuMask.set(SignalReturn);
			}
			goto MenuClosing;
		}
		else if (key.size() == 1 and isdigit(key.at(0)) and selected_signal < 10) {
			selected_signal = std::min(std::stoi((selected_signal < 1 ? key : to_string(selected_signal) + key)), 64);
		}
		else if (key == "backspace" and selected_signal != -1) {
			selected_signal = (selected_signal < 10 ? -1 : selected_signal / 10);
		}
		else if (is_in(key, "up", "k") and selected_signal != 16) {
			if (selected_signal == 1) selected_signal = 31;
			else if (selected_signal < 6) selected_signal += 25;
			else {
				bool offset = (selected_signal > 16);
				selected_signal -= 5;
				if (selected_signal <= 16 and offset) selected_signal--;
			}
		}
		else if (is_in(key, "down", "j")) {
			if (selected_signal == 31) selected_signal = 1;
			else if (selected_signal < 1 or selected_signal == 16) selected_signal = 1;
			else if (selected_signal > 26) selected_signal -= 25;
			else {
				bool offset = (selected_signal < 16);
				selected_signal += 5;
				if (selected_signal >= 16 and offset) selected_signal++;
				if (selected_signal > 31) selected_signal = 31;
			}
		}
		else if (is_in(key, "left", "h") and selected_signal > 0 and selected_signal != 16) {
			if (--selected_signal < 1) selected_signal = 31;
			else if (selected_signal == 16) selected_signal--;
		}
		else if (is_in(key, "right", "l") and selected_signal <= 31 and selected_signal != 16) {
			if (++selected_signal > 31) selected_signal = 1;
			else if (selected_signal == 16) selected_signal++;
		}
		else {
			retval = NoChange;
		}

		if (retval == Changed) {
			int cy = y+4, cx = x+4;
			out = bg + Mv::to(cy++, x+3) + Theme::c("main_fg") + Fx::ub
				+ rjust("Enter signal number: ", 48) + Theme::c("hi_fg") + (selected_signal >= 0 ? to_string(selected_signal) : "") + Theme::c("main_fg") + Fx::bl + "█" + Fx::ubl;

			auto sig_str = to_string(selected_signal);
			for (int count = 0, i = 0; const auto& sig : P_Signals) {
				if (count == 0 or count == 16) { count++; continue; }
				if (i++ % 5 == 0) { ++cy; cx = x+4; }
				out += Mv::to(cy, cx);
				if (count == selected_signal) out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b + ljust(to_string(count), 3) + ljust('(' + sig + ')', 12) + Fx::reset;
				else out += Theme::c("hi_fg") + ljust(to_string(count), 3) + Theme::c("main_fg") + ljust('(' + sig + ')', 12);
				if (redraw) mouse_mappings["button_" + to_string(count)] = {cy, cx, 1, 15};
				count++;
				cx += 15;
			}

			cy++;
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust( "↑ ↓ ← →", 33, true) + Theme::c("main_fg") + Fx::ub + " | To choose signal.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("0-9", 33) + Theme::c("main_fg") + Fx::ub + " | Enter manually.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ENTER", 33) + Theme::c("main_fg") + Fx::ub + " | To send signal.";
			mouse_mappings["enter"] = {cy, x, 1, 73};
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ESC or \"q\"", 33) + Theme::c("main_fg") + Fx::ub + " | To abort.";
			mouse_mappings["escape"] = {cy, x, 1, 73};

			out += Fx::reset;
		}

		return (redraw ? Changed : retval);

		MenuClosing:
			s_pid.reset();
			return Closed;
	}

	static int sizeError(const string& key) {
		if (redraw) {
			vector<string> cont_vec {
				Fx::b + Theme::g("used")[100] + "Error:" + Theme::c("main_fg") + Fx::ub,
				"Terminal size too small to" + Fx::reset,
				"display menu or box!" + Fx::reset };

			messageBox = Menu::msgBox{45, 0, cont_vec, "error"};
			Global::overlay = messageBox();
		}

		auto ret = messageBox.input(key);
		if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) {
			messageBox.clear();
			return Closed;
		}
		else if (redraw) {
			return Changed;
		}
		return NoChange;
	}

	static int signalSend(const string& key) {
		static std::optional<int> s_pid;

		if (!s_pid) {
			s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
		}

		if (redraw) {
			atomic_wait(Runner::active);
			auto& p_name = (s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name"));
			vector<string> cont_vec = {
				Fx::b + Theme::c("main_fg") + "Send signal: " + Fx::ub + Theme::c("hi_fg") + to_string(signalToSend)
				+ (signalToSend > 0 and signalToSend <= 32 ? Theme::c("main_fg") + " (" + P_Signals.at(signalToSend) + ')' : ""),

				Fx::b + Theme::c("main_fg") + "To PID: " + Fx::ub + Theme::c("hi_fg") + to_string(s_pid.value()) + Theme::c("main_fg") + " ("
				+ uresize(p_name, 16) + ')' + Fx::reset,
			};
			messageBox = Menu::msgBox{50, 1, cont_vec, (signalToSend > 1 and signalToSend <= 32 and signalToSend != 17 ? P_Signals.at(signalToSend) : "signal")};
			Global::overlay = messageBox();
		}
		auto ret = messageBox.input(key);
		if (ret == msgBox::Ok_Yes) {
			signalKillRet = 0;
			if (kill(s_pid.value(), signalToSend) != 0) {
				signalKillRet = errno;
				menuMask.set(SignalReturn);
			}
			messageBox.clear();
			goto MenuClosing;
		}
		else if (ret == msgBox::No_Esc) {
			messageBox.clear();
			goto MenuClosing;
		}
		else if (ret == msgBox::Select) {
			Global::overlay = messageBox();
			return Changed;
		}
		else if (redraw) {
			return Changed;
		}
		return NoChange;

		MenuClosing:
			s_pid.reset();
			return Closed;
	}

	static int signalReturn(const string& key) {
		if (redraw) {
			vector<string> cont_vec;
			cont_vec.push_back(Fx::b + Theme::g("used")[100] + "Failure:" + Theme::c("main_fg") + Fx::ub);
			if (signalKillRet == EINVAL) {
				cont_vec.push_back("Unsupported signal!" + Fx::reset);
			}
			else if (signalKillRet == EPERM) {
				cont_vec.push_back("Insufficient permissions to send signal!" + Fx::reset);
			}
			else if (signalKillRet == ESRCH) {
				cont_vec.push_back("Process not found!" + Fx::reset);
			}
			else {
				cont_vec.push_back("Unknown error! (errno: " + to_string(signalKillRet) + ')' + Fx::reset);
			}

			messageBox = Menu::msgBox{50, 0, cont_vec, "error"};
			Global::overlay = messageBox();
		}

		auto ret = messageBox.input(key);
		if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) {
			messageBox.clear();
			return Closed;
		}
		else if (redraw) {
			return Changed;
		}
		return NoChange;
	}

	static int mainMenu(const string& key) {
		enum MenuItems { Options, Help, Quit };
		static int y{};
		static int selected{};
		static vector<string> colors_selected;
		static vector<string> colors_normal;
		auto tty_mode = Config::getB("tty_mode");
		if (bg.empty()) selected = 0;
		int retval = Changed;

		if (redraw) {
			y = Term::height/2 - 10;
			bg = Draw::banner_gen(y, 0, true);
			if (not tty_mode) {
				colors_selected = {
					Theme::hex_to_color(Global::Banner_src.at(0).at(0)),
					Theme::hex_to_color(Global::Banner_src.at(2).at(0)),
					Theme::hex_to_color(Global::Banner_src.at(4).at(0))
				};
				colors_normal = {
					Theme::hex_to_color("#CC"),
					Theme::hex_to_color("#AA"),
					Theme::hex_to_color("#80")
				};
			}
		}
		else if (is_in(key, "escape", "q", "m", "mouse_click")) {
			return Closed;
		}
		else if (key.starts_with("button_")) {
			if (int new_select = key.back() - '0'; new_select == selected)
				goto MainEntering;
			else
				selected = new_select;
		}
		else if (is_in(key, "enter", "space")) {
			MainEntering:
			switch (selected) {
				case Options:
					menuMask.set(Menus::Options);
					currentMenu = Menus::Options;
					return Switch;
				case Help:
					menuMask.set(Menus::Help);
					currentMenu = Menus::Help;
					return Switch;
				case Quit:
					clean_quit(0);
			}
		}
		else if (is_in(key, "down", "tab", "mouse_scroll_down", "j")) {
			if (++selected > 2) selected = 0;
		}
		else if (is_in(key, "up", "shift_tab", "mouse_scroll_up", "k")) {
			if (--selected < 0) selected = 2;
		}
		else {
			retval = NoChange;
		}

		if (retval == Changed) {
			auto& out = Global::overlay;
			out = bg + Fx::reset + Fx::b;
			auto cy = y + 7;
			for (const auto& i : iota(0, 3)) {
				if (tty_mode) out += (i == selected ? Theme::c("hi_fg") : Theme::c("main_fg"));
				const auto& menu = (not tty_mode and i == selected ? menu_selected[i] : menu_normal[i]);
				const auto& colors = (i == selected ? colors_selected : colors_normal);
				if (redraw) mouse_mappings["button_" + to_string(i)] = {cy, Term::width/2 - menu_width[i]/2, 3, menu_width[i]};
				for (int ic = 0; const auto& line : menu) {
					out += Mv::to(cy++, Term::width/2 - menu_width[i]/2) + (tty_mode ? "" : colors[ic++]) + line;
				}
			}
			out += Fx::reset;
		}

		return (redraw ? Changed : retval);
	}

static int optionsMenu(const string& key) {
 		enum Predispositions { isBool, isInt, isString, is2D, isBrowsable, isEditable};
		static int y{};
		static int x{};
		static int height{};
		static int page{};
		static int pages{};
		static int selected{};
		static int select_max{};
		static int item_height{};
		static int selected_cat{};
		static int max_items{};
		static int last_sel{};
		static bool editing{};
		static Draw::TextEdit editor;
		static string warnings;
		static bitset<8> selPred;
		static const std::unordered_map<string, std::reference_wrapper<const vector<string>>> optionsList = {
			{"color_theme", std::cref(Theme::themes)},
			{"log_level", std::cref(Logger::log_levels)},
			{"temp_scale", std::cref(Config::temp_scales)},
		#ifdef __linux__
			{"freq_mode", std::cref(Config::freq_modes)},
		#endif
			{"proc_sorting", std::cref(Proc::sort_vector)},
			{"graph_symbol", std::cref(Config::valid_graph_symbols)},
			{"graph_symbol_cpu", std::cref(Config::valid_graph_symbols_def)},
			{"graph_symbol_mem", std::cref(Config::valid_graph_symbols_def)},
			{"graph_symbol_net", std::cref(Config::valid_graph_symbols_def)},
			{"graph_symbol_proc", std::cref(Config::valid_graph_symbols_def)},
			{"cpu_graph_upper", std::cref(Cpu::available_fields)},
			{"cpu_graph_lower", std::cref(Cpu::available_fields)},
			{"cpu_sensor", std::cref(Cpu::available_sensors)},
			{"selected_battery", std::cref(Config::available_batteries)},
	        {"base_10_bitrate", std::cref(Config::base_10_bitrate_values)},
			{"disable_presets", std::cref(Config::disable_preset_options)},
		#ifdef GPU_SUPPORT
			{"show_gpu_info", std::cref(Config::show_gpu_values)},
			{"graph_symbol_gpu", std::cref(Config::valid_graph_symbols_def)},
		#endif
		};
		auto tty_mode = Config::getB("tty_mode");
		auto vim_keys = Config::getB("vim_keys");
		if (max_items == 0) {
			for (const auto& cat : categories) {
				if ((int)cat.size() > max_items) max_items = cat.size();
			}
		}
		if (bg.empty()) {
			page = selected = selected_cat = last_sel = 0;
			redraw = true;
			Theme::updateThemes();
		}
		int retval = Changed;
		bool recollect{};
		bool screen_redraw{};
		bool theme_refresh{};

		//? Draw background if needed else process input
		if (redraw) {
			mouse_mappings.clear();
			selPred.reset();
			y = max(1, Term::height/2 - 3 - max_items);
			x = Term::width/2 - 39;
			height = min(Term::height - 7, max_items * 2 + 4);
			if (height % 2 != 0) height--;
			bg 	= Draw::banner_gen(y, 0, true)
				+ Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, fmt::format("{}tab{}", Theme::c("hi_fg"), Theme::c("main_fg")) + Symbols::right)
				+ Mv::to(y+8, x) + Theme::c("hi_fg") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * 29
				+ Symbols::div_up + Symbols::h_line * (78 - 32) + Theme::c("hi_fg") + Symbols::div_right
				+ Mv::to(y+6+height - 1, x+30) + Symbols::div_down + Theme::c("div_line");
			for (const auto& i : iota(0, height - 4)) {
				bg += Mv::to(y+9 + i, x + 30) + Symbols::v_line;
			}
		}
		else if (not warnings.empty() and not key.empty()) {
			auto ret = messageBox.input(key);
			if (ret == msgBox::msgReturn::Ok_Yes or ret == msgBox::msgReturn::No_Esc) {
				warnings.clear();
				messageBox.clear();
			}
		}
		else if (editing and not key.empty()) {
			if (is_in(key, "escape", "mouse_click")) {
				editor.clear();
				editing = false;
			}
			else if (key == "enter") {
				const auto& option = categories[selected_cat][item_height * page + selected][0];
				if (selPred.test(isString) and Config::stringValid(option, editor.text)) {
					Config::set(option, editor.text);
					if (option == "custom_cpu_name" or option.starts_with("custom_gpu_name"))
						screen_redraw = true;
					else if (is_in(option, "shown_boxes", "presets")) {
						screen_redraw = true;
						Config::current_preset.reset();
					}
					else if (option == "clock_format") {
						Draw::update_clock(true);
						screen_redraw = true;
					}
					else if (option == "cpu_core_map") {
						atomic_wait(Runner::active);
						Cpu::core_mapping = Cpu::get_core_mapping();
					}
				}
				else if (selPred.test(isInt) and Config::intValid(option, editor.text)) {
					Config::set(option, stoi(editor.text));
				}
				else
					warnings = Config::validError;

				editor.clear();
				editing = false;
			}
			else if (not editor.command(key))
				retval = NoChange;
		}
		else if (key == "mouse_click") {
			const auto [mouse_x, mouse_y] = Input::mouse_pos;
			if (mouse_x < x or mouse_x > x + 80 or mouse_y < y + 6 or mouse_y > y + 6 + height) {
				return Closed;
			}
			else if (mouse_x < x + 30 and mouse_y > y + 8) {
				auto m_select = ceil((double)(mouse_y - y - 8) / 2) - 1;
				if (selected != m_select)
					selected = m_select;
				else if (selPred.test(isEditable))
					goto mouseEnter;
				else retval = NoChange;
			}
		}
		else if (is_in(key, "enter", "e", "E") and selPred.test(isEditable)) {
			mouseEnter:
			const auto& option = categories[selected_cat][item_height * page + selected][0];
			editor = Draw::TextEdit{Config::getAsString(option), selPred.test(isInt)};
			editing = true;
			mouse_mappings.clear();
		}
		else if (is_in(key, "escape", "q", "o", "backspace")) {
			return Closed;
		}
		else if (is_in(key, "down", "mouse_scroll_down") or (vim_keys and key == "j")) {
			if (++selected > select_max or selected >= item_height) {
				if (page < pages - 1) page++;
				else if (pages > 1) page = 0;
				selected = 0;
			}
		}
		else if (is_in(key, "up", "mouse_scroll_up") or (vim_keys and key == "k")) {
			if (--selected < 0) {
				if (page > 0) page--;
				else if (pages > 1) page = pages - 1;

				selected = item_height - 1;
			}
		}
		else if (pages > 1 and key == "page_down") {
			if (++page >= pages) page = 0;
			selected = 0;
			last_sel = -1;
		}
		else if (pages > 1 and key == "page_up") {
			if (--page < 0) page = pages - 1;
			selected = 0;
			last_sel = -1;
		}
		else if (key == "tab") {
			if (++selected_cat >= (int)categories.size()) selected_cat = 0;
			page = selected = 0;
		}
		else if (key == "shift_tab") {
			if (--selected_cat < 0) selected_cat = (int)categories.size() - 1;
			page = selected = 0;
		}
#ifdef GPU_SUPPORT
		else if (is_in(key, "1", "2", "3", "4", "5", "6") or key.starts_with("select_cat_")) {
#else
		else if (is_in(key, "1", "2", "3", "4", "5") or key.starts_with("select_cat_")) {
#endif
		selected_cat = key.back() - '0' - 1;
			page = selected = 0;
		}
		else if (is_in(key, "left", "right") or (vim_keys and is_in(key, "h", "l"))) {
			const auto& option = categories[selected_cat][item_height * page + selected][0];
			if (selPred.test(isInt)) {
				const int mod = (option == "update_ms" ? 100 : 1);
				long value = Config::getI(option);
				if (key == "right" or (vim_keys and key == "l")) value += mod;
				else value -= mod;

				if (Config::intValid(option, to_string(value)))
					Config::set(option, static_cast<int>(value));
				else {
					warnings = Config::validError;
				}
			}
			else if (selPred.test(isBool)) {
				Config::flip(option);
				screen_redraw = true;

				// Special handling for options that need additional action.
				if (option == "truecolor") {
					theme_refresh = true;
					Config::flip("lowcolor");
				}
			#if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
				else if (option == "force_tty" and not Term::current_tty.starts_with("/dev/tty")) {
			#else
				else if (option == "force_tty") {
			#endif
					theme_refresh = true;
					Config::set("tty_mode", Config::getB("force_tty"));
				}
				else if (is_in(option, "rounded_corners", "theme_background"))
					theme_refresh = true;
				else if (option == "background_update") {
					Runner::pause_output = false;
				}
				else if (option == "base_10_sizes") {
					recollect = true;
				}
				else if (option == "save_config_on_exit" and not Config::getB("save_config_on_exit")) {
					const bool old_write_new = Config::write_new;
					Config::write_new = true;
					Config::write();
					Config::write_new = old_write_new;
				}
				else if (option == "disable_mouse") {
					const auto is_mouse_enabled = !Config::getB("disable_mouse");
					std::cout << (is_mouse_enabled ? Term::mouse_on : Term::mouse_off) << std::flush;
				}
			}
			else if (selPred.test(isBrowsable)) {
				auto& optList = optionsList.at(option).get();
				int i = v_index(optList, Config::getS(option));

				if ((key == "right" or (vim_keys and key == "l")) and ++i >= (int)optList.size()) i = 0;
				else if ((key == "left" or (vim_keys and key == "h")) and --i < 0) i = optList.size() - 1;
				Config::set(option, optList.at(i));

				if (option == "color_theme")
					theme_refresh = true;
				else if (option == "log_level") {
					Logger::set_log_level(optList.at(i));
					Logger::info("Logger set to {}", optList.at(i));
				}
				else if (option == "base_10_bitrate") {
				    recollect = true;
				}
				else if (is_in(option, "proc_sorting", "cpu_sensor", "show_gpu_info") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_"))
					screen_redraw = true;
				else if (option == "disable_presets" and optList.at(i) != "Off")
					Config::current_preset.reset();
			}
			else
				retval = NoChange;
		}
		else {
			retval = NoChange;
		}

		//? Draw the menu
		if (retval == Changed) {
			Config::unlock();
			auto& out = Global::overlay;
			out = bg;
			item_height = min((int)categories[selected_cat].size(), (int)floor((double)(height - 4) / 2));
			pages = ceil((double)categories[selected_cat].size() / item_height);
			if (page > pages - 1) page = pages - 1;
			select_max = min(item_height - 1, (int)categories[selected_cat].size() - 1 - item_height * page);
			if (selected > select_max) {
				selected = select_max;
			}

			//? Get variable properties for currently selected option
			selPred.reset();
			last_sel = (selected_cat << 8) + selected;
			const auto& selOption = categories[selected_cat][item_height * page + selected][0];
			if (Config::ints.contains(selOption))
				selPred.set(isInt);
			else if (Config::bools.contains(selOption))
				selPred.set(isBool);
			else
				selPred.set(isString);

			if (not selPred.test(isString))
				selPred.set(is2D);
			else if (optionsList.contains(selOption)) {
				selPred.set(isBrowsable);
			}
			if (not selPred.test(isBrowsable) and (selPred.test(isString) or selPred.test(isInt)))
				selPred.set(isEditable);

			//? Category buttons
			out += Mv::to(y+7, x+4);
		#ifdef GPU_SUPPORT
			for (int i = 0; const auto& m : {"general", "cpu", "gpu", "mem", "net", "proc"}) {
		#else
			for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) {
		#endif
				out += Fx::b + (i == selected_cat
						? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']'
						: Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ')
				#ifdef GPU_SUPPORT
					+ Mv::r(7);
				#else
					+ Mv::r(10);
				#endif

#if !defined(GPU_SUPPORT)
				constexpr static auto option_menu_tab_width = 15;
#else
				constexpr static auto option_menu_tab_width = 12;
#endif
				if (const auto button_name = fmt::format("select_cat_{}", i + 1); not editing and not mouse_mappings.contains(button_name)) {
					mouse_mappings[button_name] = {
						.line = y + 6,
						.col = x + 2 + (option_menu_tab_width * i),
						.height = 3,
						.width = option_menu_tab_width,
					};
				}
				i++;
			}
			if (pages > 1) {
				out += Mv::to(y+6 + height - 1, x+2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page "
					+ to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down;
			}
			//? Option name and value
			auto cy = y+9;
			for (int c = 0, i = max(0, item_height * page); c++ < item_height and i < (int)categories[selected_cat].size(); i++) {
				const auto& option = categories[selected_cat][i][0];
				const auto& value = (option == "color_theme" ? fs::path(Config::getS("color_theme")).stem().string() : Config::getAsString(option));

				out += Mv::to(cy++, x + 1) + (c-1 == selected ? Theme::c("selected_bg") + Theme::c("selected_fg") : Theme::c("title"))
					+ Fx::b + cjust(capitalize(s_replace(option, "_", " "))
						+ (c-1 == selected and selPred.test(isBrowsable)
							? ' ' + to_string(v_index(optionsList.at(option).get(), (option == "color_theme" ? Config::getS("color_theme") : value)) + 1) + '/' + to_string(optionsList.at(option).get().size())
							: ""), 29);
				out	+= Mv::to(cy++, x + 1) + (c-1 == selected ? "" : Theme::c("main_fg")) + Fx::ub + "  "
					+ (c-1 == selected and editing ? cjust(editor(24), 34, true) : cjust(value, 25, true)) + "  ";

				if (c-1 == selected) {
					if (not editing and (selPred.test(is2D) or selPred.test(isBrowsable))) {
						out += Fx::b + Mv::to(cy-1, x+2) + Symbols::left + Mv::to(cy-1, x+28) + Symbols::right;
						mouse_mappings["left"] = {cy-2, x, 2, 5};
						mouse_mappings["right"] = {cy-2, x+25, 2, 5};
					}
					if (selPred.test(isEditable)) {
						out += Fx::b + Mv::to(cy-1, x+28 - (not editing and selPred.test(isInt) ? 2 : 0)) + (tty_mode ? "E" : Symbols::enter);
					}
					//? Description of selected option
					out += Fx::reset + Theme::c("title") + Fx::b;
					for (int cyy = y+7; const auto& desc : categories[selected_cat][i]) {
						if (cyy++ == y+7) continue;
						else if (cyy == y+10) out += Theme::c("main_fg") + Fx::ub;
						else if (cyy > y + height + 4) break;
						out += Mv::to(cyy, x+32) + desc;
					}
				}
			}

			if (not warnings.empty()) {
				messageBox = msgBox{min(78, (int)ulen(warnings) + 10), msgBox::BoxTypes::OK, {uresize(warnings, 74)}, "warning"};
				out += messageBox();
			}

			out += Fx::reset;
		}

		if (theme_refresh) {
			Theme::setTheme();
			Draw::banner_gen(0, 0, false, true);
			screen_redraw = true;
			redraw = true;
			optionsMenu("");
		}
		if (screen_redraw) {
			auto overlay_bkp = std::move(Global::overlay);
			auto clock_bkp = std::move(Global::clock);
			Draw::calcSizes();
			Global::overlay = std::move(overlay_bkp);
			Global::clock = std::move(clock_bkp);
			recollect = true;
		}
		if (recollect) {
			Runner::run("all", false, true);
			retval = NoChange;
		}

		return (redraw ? Changed : retval);
	}

	static int helpMenu(const string& key) {
		static int y{};
		static int x{};
		static int height{};
		static int page{};
		static int pages{};

		if (bg.empty()) page = 0;
		int retval = Changed;

		if (redraw) {
			y = max(1, Term::height/2 - 4 - (int)(help_text.size() / 2));
			x = Term::width/2 - 39;
			height = min(Term::height - 6, (int)help_text.size() + 3);
			pages = ceil((double)help_text.size() / (height - 3));
			page = 0;
			bg = Draw::banner_gen(y, 0, true);
			bg += Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, "help");
		}
		else if (is_in(key, "escape", "q", "h", "backspace", "space", "enter", "mouse_click")) {
			return Closed;
		}
		else if (pages > 1 and is_in(key, "down", "j", "page_down", "tab", "mouse_scroll_down")) {
			if (++page >= pages) page = 0;
		}
		else if (pages > 1 and is_in(key, "up", "k", "page_up", "shift_tab", "mouse_scroll_up")) {
			if (--page < 0) page = pages - 1;
		}
		else {
			retval = NoChange;
		}


		if (retval == Changed) {
			auto& out = Global::overlay;
			out = bg;
			if (pages > 1) {
				out += Mv::to(y+height+6, x + 2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page "
					+ to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down;
			}
			auto cy = y+7;
			out += Mv::to(cy++, x + 1) + Theme::c("title") + Fx::b + cjust("Key:", 20) + "Description:";
			for (int c = 0, i = max(0, (height - 3) * page); c++ < height - 3 and i < (int)help_text.size(); i++) {
				out += Mv::to(cy++, x + 1) + Theme::c("hi_fg") + Fx::b + cjust(help_text[i][0], 20)
					+ Theme::c("main_fg") + Fx::ub + help_text[i][1];
			}
			out += Fx::reset;
		}


		return (redraw ? Changed : retval);
	}

	static int reniceMenu(const string& key) {
		static std::optional<int> s_pid;
		static int x{};
		static int y{};
		static int selected_nice = 0;
		static string nice_edit;

		if (!s_pid) {
			s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid"));
		}

		if (bg.empty()) {
			selected_nice = 0;
			nice_edit.clear();
		}
		auto& out = Global::overlay;
		int retval = Changed;

		if (redraw) {
			x = Term::width/2 - 25;
			y = Term::height/2 - 6;
			bg = Draw::createBox(x + 2, y, 50, 13, Theme::c("hi_fg"), true, "renice");
			bg += Mv::to(y+2, x+3) + Theme::c("title") + Fx::b + cjust("Renice PID " + to_string(s_pid.value()) + " ("
				+ uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 15) + ")", 48);
		}
		else if (is_in(key, "escape", "q")) {
			goto MenuClosing;
		}
		else if (is_in(key, "enter", "space")) {
			if (s_pid > 0) {
				if (not nice_edit.empty()) {
					try {
						selected_nice = stoi(nice_edit);
					}
					catch (...) { selected_nice = 0; }
				}
				if (not Proc::set_priority(s_pid.value(), selected_nice)) {
					// TODO: show error message
				}
			}
			goto MenuClosing;
		}
		else if (key.size() == 1 and (isdigit(key.at(0)) or (key.at(0) == '-' and nice_edit.empty()))) {
			nice_edit += key;
		}
		else if (key == "backspace" and not nice_edit.empty()) {
			nice_edit.pop_back();
		}
		else if (is_in(key, "up", "k")) {
			if (++selected_nice > 19) selected_nice = -20;
			nice_edit.clear();
		}
		else if (is_in(key, "down", "j")) {
			if (--selected_nice < -20) selected_nice = 19;
			nice_edit.clear();
		}
		else if (is_in(key, "left", "h")) {
			if ((selected_nice -= 5) < -20) selected_nice += 40;
			nice_edit.clear();
		}
		else if (is_in(key, "right", "l")) {
			if ((selected_nice += 5) > 19) selected_nice -= 40;
			nice_edit.clear();
		}
		else {
			retval = NoChange;
		}

		if (retval == Changed) {
			int cy = y+4;
			if (not nice_edit.empty()) {
				try {
					selected_nice = stoi(nice_edit);
				}
				catch (...) { selected_nice = 0; }
			}
			out = bg + Mv::to(cy++, x+3) + Theme::c("main_fg") + Fx::ub
				+ rjust("Enter nice value: ", 30) + Theme::c("hi_fg") + (nice_edit.empty() ? to_string(selected_nice) : nice_edit) + Theme::c("main_fg") + Fx::bl + "█" + Fx::ubl;

			cy++;
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust( "↑ ↓", 20, true) + Theme::c("main_fg") + Fx::ub + " | To change value.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust( "← →", 20, true) + Theme::c("main_fg") + Fx::ub + " | To change value by 5.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("0-9", 20) + Theme::c("main_fg") + Fx::ub + " | Enter manually.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ENTER", 20) + Theme::c("main_fg") + Fx::ub + " | To set nice value.";
			out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ESC or 'q'", 20) + Theme::c("main_fg") + Fx::ub + " | To abort.";

			out += Fx::reset;
		}

		return (redraw ? Changed : retval);

		MenuClosing:
			s_pid.reset();
			return Closed;
	}

	//* Add menus here and update enum Menus in header
	const auto menuFunc = vector{
		ref(sizeError),
		ref(signalChoose),
		ref(signalSend),
		ref(signalReturn),
		ref(optionsMenu),
		ref(helpMenu),
		ref(reniceMenu),
		ref(mainMenu),
	};
	bitset<8> menuMask;

	void process(const std::string_view key) {
		if (menuMask.none()) {
			Menu::active = false;
			Global::overlay.clear();
			Global::overlay.shrink_to_fit();
			Runner::pause_output = false;
			bg.clear();
			bg.shrink_to_fit();
			currentMenu = -1;
			Runner::run("all", true, true);
			mouse_mappings.clear();
			return;
		}

		if (currentMenu < 0 or not menuMask.test(currentMenu)) {
			Menu::active = true;
			redraw = true;
			if (((menuMask.test(Main) or menuMask.test(Options) or menuMask.test(Help) or menuMask.test(SignalChoose))
			and (Term::width < 80 or Term::height < 24))
			or (Term::width < 50 or Term::height < 20)) {
				menuMask.reset();
				menuMask.set(SizeError);
			}

			for (const auto& i : iota(0, (int)menuMask.size())) {
				if (menuMask.test(i)) currentMenu = i;
			}

		}

		auto retCode = menuFunc.at(currentMenu)(key.data());
		if (retCode == Closed) {
			menuMask.reset(currentMenu);
			mouse_mappings.clear();
			bg.clear();
			Runner::pause_output = false;
			process();
		}
		else if (redraw) {
			redraw = false;
			Runner::run("all", true, true);
		}
		else if (retCode == Changed)
			Runner::run("overlay");
		else if (retCode == Switch) {
			Runner::pause_output = false;
			bg.clear();
			redraw = true;
			mouse_mappings.clear();
			process();
		}
	}

	void show(int menu, int signal) {
		menuMask.set(menu);
		signalToSend = signal;
		process();
	}
}
