use std::{collections::HashMap, marker::PhantomData, rc::Rc, str::FromStr};

use glam::Vec2;
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
use wgui::{
	assets::AssetPath,
	components::{
		button::{ButtonClickEvent, ComponentButton},
		checkbox::ComponentCheckbox,
		slider::ComponentSlider,
		tabs::ComponentTabs,
	},
	event::{CallbackDataCommon, EventAlterables},
	globals::WguiGlobals,
	i18n::Translation,
	layout::{Layout, WidgetID},
	log::LogErr,
	parser::{Fetchable, ParseDocumentParams, ParserState},
	task::Tasks,
	widget::label::WidgetLabel,
	windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
};
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot};

use crate::{
	frontend::{Frontend, FrontendTask},
	tab::{Tab, TabType},
};

#[derive(Clone)]
enum TabNameEnum {
	LookAndFeel,
	Features,
	Controls,
	Misc,
	AutostartApps,
	Troubleshooting,
}

impl TabNameEnum {
	fn from_string(s: &str) -> Option<Self> {
		match s {
			"look_and_feel" => Some(TabNameEnum::LookAndFeel),
			"features" => Some(TabNameEnum::Features),
			"controls" => Some(TabNameEnum::Controls),
			"misc" => Some(TabNameEnum::Misc),
			"autostart_apps" => Some(TabNameEnum::AutostartApps),
			"troubleshooting" => Some(TabNameEnum::Troubleshooting),
			_ => None,
		}
	}
}

enum Task {
	UpdateBool(SettingType, bool),
	UpdateFloat(SettingType, f32),
	UpdateInt(SettingType, i32),
	OpenContextMenu(Vec2, Vec<context_menu::Cell>),
	ClearPipewireTokens,
	ClearSavedState,
	DeleteAllConfigs,
	RestartSoftware,
	RemoveAutostartApp(Rc<str>),
	SetTab(TabNameEnum),
}

pub struct TabSettings<T> {
	pub state: ParserState,

	app_button_ids: Vec<Rc<str>>,
	context_menu: ContextMenu,

	tasks: Tasks<Task>,
	marker: PhantomData<T>,
}

impl<T> Tab<T> for TabSettings<T> {
	fn get_type(&self) -> TabType {
		TabType::Settings
	}

	fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
		let mut changed = false;
		for task in self.tasks.drain() {
			match task {
				Task::SetTab(tab) => {
					self.set_tab(frontend, data, tab)?;
				}
				Task::UpdateBool(setting, n) => {
					if let Some(task) = setting.get_frontend_task() {
						frontend.tasks.push(task)
					}
					let config = frontend.interface.general_config(data);
					*setting.mut_bool(config) = n;
					changed = true;
				}
				Task::UpdateFloat(setting, n) => {
					if let Some(task) = setting.get_frontend_task() {
						frontend.tasks.push(task)
					}
					let config = frontend.interface.general_config(data);
					*setting.mut_f32(config) = n;
					changed = true;
				}
				Task::UpdateInt(setting, n) => {
					if let Some(task) = setting.get_frontend_task() {
						frontend.tasks.push(task)
					}
					let config = frontend.interface.general_config(data);
					*setting.mut_i32(config) = n;
					changed = true;
				}
				Task::ClearPipewireTokens => {
					let _ = std::fs::remove_file(ConfigRoot::Generic.get_conf_d_path().join("pw_tokens.yaml"))
						.log_err("Could not remove pw_tokens.yaml");
				}
				Task::ClearSavedState => {
					let _ = std::fs::remove_file(ConfigRoot::Generic.get_conf_d_path().join("zz-saved-state.json5"))
						.log_err("Could not remove zz-saved-state.json5");
				}
				Task::DeleteAllConfigs => {
					let path = ConfigRoot::Generic.get_conf_d_path();
					std::fs::remove_dir_all(&path)?;
					std::fs::create_dir(&path)?;
				}
				Task::RestartSoftware => {
					frontend.interface.restart(data);
					return Ok(());
				}
				Task::OpenContextMenu(position, cells) => {
					self.context_menu.open(context_menu::OpenParams {
						on_custom_attribs: None,
						position,
						blueprint: Blueprint::Cells(cells),
					});
				}
				Task::RemoveAutostartApp(button_id) => {
					if let (Some(idx), Ok(widget)) = (
						self.app_button_ids.iter().position(|x| button_id.eq(x)),
						self.state.get_widget_id(&format!("{button_id}_root")),
					) {
						self.app_button_ids.remove(idx);
						let config = frontend.interface.general_config(data);
						config.autostart_apps.remove(idx);
						frontend.layout.remove_widget(widget);
						changed = true;
					}
				}
			}
		}

		// Dropdown handling
		if let TickResult::Action(name) = self.context_menu.tick(&mut frontend.layout, &mut self.state)?
			&& let (Some(setting), Some(id), Some(value), Some(text), Some(translated)) = {
				let mut s = name.splitn(5, ';');
				(s.next(), s.next(), s.next(), s.next(), s.next())
			} {
			let mut label = self
				.state
				.fetch_widget_as::<WidgetLabel>(&frontend.layout.state, &format!("{id}_value"))?;

			let mut alterables = EventAlterables::default();
			let mut common = CallbackDataCommon {
				alterables: &mut alterables,
				state: &frontend.layout.state,
			};

			let translation = Translation {
				text: text.into(),
				translated: translated == "1",
			};

			label.set_text(&mut common, translation);

			let setting = SettingType::from_str(setting).expect("Invalid Enum string");
			let config = frontend.interface.general_config(data);
			setting.set_enum(config, value);
			changed = true;
		}

		// Notify overlays of the change
		if changed {
			frontend.interface.config_changed(data);
		}

		Ok(())
	}
}

#[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, AsRefStr, EnumString)]
enum SettingType {
	AnimationSpeed,
	RoundMultiplier,
	InvertScrollDirectionX,
	InvertScrollDirectionY,
	ScrollSpeed,
	LongPressDuration,
	NotificationsEnabled,
	NotificationsSoundEnabled,
	KeyboardSoundEnabled,
	UprightScreenFix,
	DoubleCursorFix,
	SetsOnWatch,
	HideGrabHelp,
	XrClickSensitivity,
	XrClickSensitivityRelease,
	AllowSliding,
	ClickFreezeTimeMs,
	FocusFollowsMouseMode,
	LeftHandedMouse,
	BlockGameInput,
	BlockGameInputIgnoreWatch,
	SpaceDragMultiplier,
	UseSkybox,
	UsePassthrough,
	ScreenRenderDown,
	PointerLerpFactor,
	SpaceDragUnlocked,
	SpaceRotateUnlocked,
	Clock12h,
	HideUsername,
	OpaqueBackground,
	XwaylandByDefault,
	CaptureMethod,
	KeyboardMiddleClick,
	HandsfreePointer,
}

impl SettingType {
	pub fn mut_bool(self, config: &mut GeneralConfig) -> &mut bool {
		match self {
			Self::InvertScrollDirectionX => &mut config.invert_scroll_direction_x,
			Self::InvertScrollDirectionY => &mut config.invert_scroll_direction_y,
			Self::NotificationsEnabled => &mut config.notifications_enabled,
			Self::NotificationsSoundEnabled => &mut config.notifications_sound_enabled,
			Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled,
			Self::UprightScreenFix => &mut config.upright_screen_fix,
			Self::DoubleCursorFix => &mut config.double_cursor_fix,
			Self::SetsOnWatch => &mut config.sets_on_watch,
			Self::HideGrabHelp => &mut config.hide_grab_help,
			Self::AllowSliding => &mut config.allow_sliding,
			Self::FocusFollowsMouseMode => &mut config.focus_follows_mouse_mode,
			Self::LeftHandedMouse => &mut config.left_handed_mouse,
			Self::BlockGameInput => &mut config.block_game_input,
			Self::BlockGameInputIgnoreWatch => &mut config.block_game_input_ignore_watch,
			Self::UseSkybox => &mut config.use_skybox,
			Self::UsePassthrough => &mut config.use_passthrough,
			Self::ScreenRenderDown => &mut config.screen_render_down,
			Self::SpaceDragUnlocked => &mut config.space_drag_unlocked,
			Self::SpaceRotateUnlocked => &mut config.space_rotate_unlocked,
			Self::Clock12h => &mut config.clock_12h,
			Self::HideUsername => &mut config.hide_username,
			Self::OpaqueBackground => &mut config.opaque_background,
			Self::XwaylandByDefault => &mut config.xwayland_by_default,
			_ => panic!("Requested bool for non-bool SettingType"),
		}
	}

	pub fn mut_f32(self, config: &mut GeneralConfig) -> &mut f32 {
		match self {
			Self::AnimationSpeed => &mut config.animation_speed,
			Self::RoundMultiplier => &mut config.round_multiplier,
			Self::ScrollSpeed => &mut config.scroll_speed,
			Self::LongPressDuration => &mut config.long_press_duration,
			Self::XrClickSensitivity => &mut config.xr_click_sensitivity,
			Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release,
			Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
			Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
			_ => panic!("Requested f32 for non-f32 SettingType"),
		}
	}

	pub fn mut_i32(self, config: &mut GeneralConfig) -> &mut i32 {
		match self {
			Self::ClickFreezeTimeMs => &mut config.click_freeze_time_ms,
			_ => panic!("Requested i32 for non-i32 SettingType"),
		}
	}

	pub fn set_enum(self, config: &mut GeneralConfig, value: &str) {
		match self {
			Self::CaptureMethod => {
				config.capture_method = wlx_common::config::CaptureMethod::from_str(value).expect("Invalid enum value!")
			}
			Self::KeyboardMiddleClick => {
				config.keyboard_middle_click_mode =
					wlx_common::config::AltModifier::from_str(value).expect("Invalid enum value!")
			}
			Self::HandsfreePointer => {
				config.handsfree_pointer = wlx_common::config::HandsfreePointer::from_str(value).expect("Invalid enum value!")
			}
			_ => panic!("Requested enum for non-enum SettingType"),
		}
	}

	fn get_enum_title(self, config: &mut GeneralConfig) -> Translation {
		match self {
			Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method),
			Self::KeyboardMiddleClick => Self::get_enum_title_inner(config.keyboard_middle_click_mode),
			Self::HandsfreePointer => Self::get_enum_title_inner(config.handsfree_pointer),
			_ => panic!("Requested enum for non-enum SettingType"),
		}
	}

	fn get_enum_title_inner<E>(value: E) -> Translation
	where
		E: EnumProperty + AsRef<str>,
	{
		value
			.get_str("Translation")
			.map(Translation::from_translation_key)
			.or_else(|| value.get_str("Text").map(Translation::from_raw_text))
			.unwrap_or_else(|| Translation::from_raw_text(value.as_ref()))
	}

	fn get_enum_tooltip_inner<E>(value: E) -> Option<Translation>
	where
		E: EnumProperty + AsRef<str>,
	{
		value.get_str("Tooltip").map(Translation::from_translation_key)
	}

	/// Ok is translation, Err is raw text
	fn get_translation(self) -> Result<&'static str, &'static str> {
		match self {
			Self::AnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
			Self::RoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
			Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
			Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
			Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
			Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
			Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
			Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
			Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
			Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
			Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
			Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
			Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
			Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
			Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
			Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
			Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
			Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
			Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
			Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
			Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
			Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
			Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
			Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
			Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
			Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
			Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
			Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
			Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
			Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
			Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
			Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
			Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
			Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
			Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
		}
	}

	fn get_tooltip(self) -> Option<&'static str> {
		match self {
			Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
			Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
			Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
			Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
			Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
			Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
			Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
			Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
			Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
			Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
			Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
			Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
			Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
			Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"),
			_ => None,
		}
	}

	//TODO: incorporate this
	fn requires_restart(self) -> bool {
		match self {
			Self::AnimationSpeed
			| Self::RoundMultiplier
			| Self::UprightScreenFix
			| Self::DoubleCursorFix
			| Self::UseSkybox
			| Self::UsePassthrough
			| Self::ScreenRenderDown
			| Self::CaptureMethod => true,
			_ => false,
		}
	}

	fn get_frontend_task(self) -> Option<FrontendTask> {
		match self {
			Self::Clock12h => Some(FrontendTask::RefreshClock),
			Self::OpaqueBackground => Some(FrontendTask::RefreshBackground),
			_ => None,
		}
	}
}

macro_rules! category {
	($pe:expr, $root:expr, $translation:expr, $icon:expr) => {{
		let id = $pe.idx.to_string();
		$pe.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("translation"), Rc::from($translation));
		params.insert(Rc::from("icon"), Rc::from($icon));
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));

		$pe
			.parser_state
			.instantiate_template($pe.doc_params, "SettingsGroupBox", $pe.layout, $root, params)?;

		$pe.parser_state.get_widget_id(&id)
	}};
}

macro_rules! checkbox {
	($mp:expr, $root:expr, $setting:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));

		match $setting.get_translation() {
			Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
			Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
		};

		if let Some(tooltip) = $setting.get_tooltip() {
			params.insert(Rc::from("tooltip"), Rc::from(tooltip));
		}

		let checked = if *$setting.mut_bool($mp.config) { "1" } else { "0" };
		params.insert(Rc::from("checked"), Rc::from(checked));

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, $root, params)?;

		let checkbox = $mp.parser_state.fetch_component_as::<ComponentCheckbox>(&id)?;
		checkbox.on_toggle(Box::new({
			let tasks = $mp.tasks.clone();
			move |_common, e| {
				tasks.push(Task::UpdateBool($setting, e.checked));
				Ok(())
			}
		}));
	};
}

macro_rules! slider_f32 {
	($mp:expr, $root:expr, $setting:expr, $min:expr, $max:expr, $step:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));

		match $setting.get_translation() {
			Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
			Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
		};

		if let Some(tooltip) = $setting.get_tooltip() {
			params.insert(Rc::from("tooltip"), Rc::from(tooltip));
		}

		let value = $setting.mut_f32($mp.config).to_string();
		params.insert(Rc::from("value"), Rc::from(value));
		params.insert(Rc::from("min"), Rc::from($min.to_string()));
		params.insert(Rc::from("max"), Rc::from($max.to_string()));
		params.insert(Rc::from("step"), Rc::from($step.to_string()));

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;

		let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
		slider.on_value_changed(Box::new({
			let tasks = $mp.tasks.clone();
			move |_common, e| {
				tasks.push(Task::UpdateFloat($setting, e.value));
				Ok(())
			}
		}));
	};
}

macro_rules! slider_i32 {
	($mp:expr, $root:expr, $setting:expr, $min:expr, $max:expr, $step:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));

		match $setting.get_translation() {
			Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
			Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
		};

		if let Some(tooltip) = $setting.get_tooltip() {
			params.insert(Rc::from("tooltip"), Rc::from(tooltip));
		}

		let value = $setting.mut_i32($mp.config).to_string();
		params.insert(Rc::from("value"), Rc::from(value));
		params.insert(Rc::from("min"), Rc::from($min.to_string()));
		params.insert(Rc::from("max"), Rc::from($max.to_string()));
		params.insert(Rc::from("step"), Rc::from($step.to_string()));

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;

		let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
		slider.on_value_changed(Box::new({
			let tasks = $mp.tasks.clone();
			move |_common, e| {
				tasks.push(Task::UpdateInt($setting, e.value as i32));
				Ok(())
			}
		}));
	};
}

macro_rules! dropdown {
	($mp:expr, $root:expr, $setting:expr, $options:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));

		match $setting.get_translation() {
			Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
			Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
		};

		if let Some(tooltip) = $setting.get_tooltip() {
			params.insert(Rc::from("tooltip"), Rc::from(tooltip));
		}

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, $root, params)?;

		let setting_str = $setting.as_ref();
		let title = $setting.get_enum_title($mp.config);

		{
			let mut label = $mp
				.parser_state
				.fetch_widget_as::<WidgetLabel>(&$mp.layout.state, &format!("{id}_value"))?;
			label.set_text_simple(&mut $mp.layout.state.globals.get(), title);
		}

		let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
		btn.on_click(Rc::new({
			let tasks = $mp.tasks.clone();
			move |_common, e: ButtonClickEvent| {
				tasks.push(Task::OpenContextMenu(
					e.mouse_pos_absolute.unwrap_or_default(),
					$options
						.iter()
						.filter_map(|item| {
							if item.get_bool("Hidden").unwrap_or(false) {
								return None;
							}

							let value = item.as_ref();
							let title = SettingType::get_enum_title_inner(*item);
							let tooltip = SettingType::get_enum_tooltip_inner(*item);

							let text = &title.text;
							let translated = if title.translated { "1" } else { "0" };

							Some(context_menu::Cell {
								action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
								title,
								tooltip,
								attribs: vec![],
							})
						})
						.collect(),
				));
				Ok(())
			}
		}));
	};
}

macro_rules! danger_button {
	($mp:expr, $root:expr, $translation:expr, $icon:expr, $task:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));
		params.insert(Rc::from("translation"), Rc::from($translation));
		params.insert(Rc::from("icon"), Rc::from($icon));

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "DangerButton", $mp.layout, $root, params)?;

		let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
		btn.on_click(Rc::new({
			let tasks = $mp.tasks.clone();
			move |_common, _e| {
				tasks.push($task);
				Ok(())
			}
		}));
	};
}

macro_rules! autostart_app {
	($mp:expr, $root:expr, $text:expr, $ids:expr) => {
		let id = $mp.idx.to_string();
		$mp.idx += 1;

		let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
		params.insert(Rc::from("id"), Rc::from(id.as_ref()));
		params.insert(Rc::from("text"), Rc::from($text.as_str()));

		$mp
			.parser_state
			.instantiate_template($mp.doc_params, "AutostartApp", $mp.layout, $root, params)?;

		let btn = $mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
		let id: Rc<str> = Rc::from(id);

		$ids.push(id.clone());

		btn.on_click(Rc::new({
			let tasks = $mp.tasks.clone();
			move |_common, _e| {
				tasks.push(Task::RemoveAutostartApp(id.clone()));
				Ok(())
			}
		}));
	};
}

struct MacroParams<'a> {
	layout: &'a mut Layout,
	parser_state: &'a mut ParserState,
	doc_params: &'a ParseDocumentParams<'a>,
	config: &'a mut GeneralConfig,
	tasks: Tasks<Task>,
	idx: usize,
}

fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
	ParseDocumentParams {
		globals: globals.clone(),
		path: AssetPath::BuiltIn("gui/tab/settings.xml"),
		extra: Default::default(),
	}
}

impl<T> TabSettings<T> {
	fn set_tab(&mut self, frontend: &mut Frontend<T>, data: &mut T, name: TabNameEnum) -> anyhow::Result<()> {
		let root = self.state.get_widget_id("settings_root")?;
		frontend.layout.remove_children(root);
		let globals = frontend.layout.state.globals.clone();

		let mut mp = MacroParams {
			layout: &mut frontend.layout,
			parser_state: &mut self.state,
			doc_params: &doc_params(&globals),
			config: frontend.interface.general_config(data),
			tasks: self.tasks.clone(),
			idx: 9001,
		};

		match name {
			TabNameEnum::LookAndFeel => {
				let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?;
				checkbox!(mp, c, SettingType::OpaqueBackground);
				checkbox!(mp, c, SettingType::HideUsername);
				checkbox!(mp, c, SettingType::HideGrabHelp);
				slider_f32!(mp, c, SettingType::AnimationSpeed, 0.5, 5.0, 0.1); // min, max, step
				slider_f32!(mp, c, SettingType::RoundMultiplier, 0.5, 5.0, 0.1);
				checkbox!(mp, c, SettingType::SetsOnWatch);
				checkbox!(mp, c, SettingType::UseSkybox);
				checkbox!(mp, c, SettingType::UsePassthrough);
				checkbox!(mp, c, SettingType::Clock12h);
			}
			TabNameEnum::Features => {
				let c = category!(mp, root, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?;
				checkbox!(mp, c, SettingType::NotificationsEnabled);
				checkbox!(mp, c, SettingType::NotificationsSoundEnabled);
				checkbox!(mp, c, SettingType::KeyboardSoundEnabled);
				checkbox!(mp, c, SettingType::SpaceDragUnlocked);
				checkbox!(mp, c, SettingType::SpaceRotateUnlocked);
				slider_f32!(mp, c, SettingType::SpaceDragMultiplier, -10.0, 10.0, 0.5);
				checkbox!(mp, c, SettingType::BlockGameInput);
				checkbox!(mp, c, SettingType::BlockGameInputIgnoreWatch);
			}
			TabNameEnum::Controls => {
				let c = category!(mp, root, "APP_SETTINGS.CONTROLS", "dashboard/controller.svg")?;
				dropdown!(
					mp,
					c,
					SettingType::KeyboardMiddleClick,
					wlx_common::config::AltModifier::VARIANTS
				);
				dropdown!(
					mp,
					c,
					SettingType::HandsfreePointer,
					wlx_common::config::HandsfreePointer::VARIANTS
				);
				checkbox!(mp, c, SettingType::FocusFollowsMouseMode);
				checkbox!(mp, c, SettingType::LeftHandedMouse);
				checkbox!(mp, c, SettingType::AllowSliding);
				checkbox!(mp, c, SettingType::InvertScrollDirectionX);
				checkbox!(mp, c, SettingType::InvertScrollDirectionY);
				slider_f32!(mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1);
				slider_f32!(mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1);
				slider_f32!(mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1);
				slider_f32!(mp, c, SettingType::XrClickSensitivity, 0.1, 1.0, 0.1);
				slider_f32!(mp, c, SettingType::XrClickSensitivityRelease, 0.1, 1.0, 0.1);
				slider_i32!(mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50);
			}
			TabNameEnum::Misc => {
				let c = category!(mp, root, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?;
				dropdown!(
					mp,
					c,
					SettingType::CaptureMethod,
					wlx_common::config::CaptureMethod::VARIANTS
				);
				checkbox!(mp, c, SettingType::XwaylandByDefault);
				checkbox!(mp, c, SettingType::UprightScreenFix);
				checkbox!(mp, c, SettingType::DoubleCursorFix);
				checkbox!(mp, c, SettingType::ScreenRenderDown);
			}
			TabNameEnum::AutostartApps => {
				self.app_button_ids = vec![];

				if !mp.config.autostart_apps.is_empty() {
					let c = category!(mp, root, "APP_SETTINGS.AUTOSTART_APPS", "dashboard/apps.svg")?;

					for app in &mp.config.autostart_apps {
						autostart_app!(mp, c, app.name, self.app_button_ids);
					}
				}
			}
			TabNameEnum::Troubleshooting => {
				let c = category!(mp, root, "APP_SETTINGS.TROUBLESHOOTING", "dashboard/cpu.svg")?;
				danger_button!(
					mp,
					c,
					"APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS",
					"dashboard/display.svg",
					Task::ClearPipewireTokens
				);
				danger_button!(
					mp,
					c,
					"APP_SETTINGS.CLEAR_SAVED_STATE",
					"dashboard/binary.svg",
					Task::ClearSavedState
				);
				danger_button!(
					mp,
					c,
					"APP_SETTINGS.DELETE_ALL_CONFIGS",
					"dashboard/circle.svg",
					Task::DeleteAllConfigs
				);
				danger_button!(
					mp,
					c,
					"APP_SETTINGS.RESTART_SOFTWARE",
					"dashboard/refresh.svg",
					Task::RestartSoftware
				);
			}
		}

		Ok(())
	}

	pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, _data: &mut T) -> anyhow::Result<Self> {
		let doc_params = ParseDocumentParams {
			globals: frontend.layout.state.globals.clone(),
			path: AssetPath::BuiltIn("gui/tab/settings.xml"),
			extra: Default::default(),
		};

		let parser_state = wgui::parser::parse_from_assets(&doc_params, &mut frontend.layout, parent_id)?;
		let tasks = Tasks::default();
		let tabs = parser_state.fetch_component_as::<ComponentTabs>("tabs")?;
		tabs.on_select({
			let tasks = tasks.clone();
			Rc::new(move |_common, evt| {
				if let Some(tab) = TabNameEnum::from_string(&evt.name) {
					tasks.push(Task::SetTab(tab));
				}
				Ok(())
			})
		});

		tasks.push(Task::SetTab(TabNameEnum::LookAndFeel));

		Ok(Self {
			app_button_ids: Vec::new(),
			tasks,
			state: parser_state,
			marker: PhantomData,
			context_menu: ContextMenu::default(),
		})
	}
}
