use anyhow::Result;
use yazi_actor::Ctx;
use yazi_macro::{act, succ};
use yazi_shared::{Layer, data::Data, event::ActionCow};
use yazi_widgets::input::InputMode;

use crate::app::App;

pub(super) struct Executor<'a> {
	app: &'a mut App,
}

impl<'a> Executor<'a> {
	#[inline]
	pub(super) fn new(app: &'a mut App) -> Self { Self { app } }

	pub(super) fn execute(&mut self, action: ActionCow) -> Result<Data> {
		match action.layer {
			Layer::Null => self.null(action),
			Layer::App => self.app(action),
			Layer::Mgr => self.mgr(action),
			Layer::Tasks => self.tasks(action),
			Layer::Spot => self.spot(action),
			Layer::Pick => self.pick(action),
			Layer::Input => self.input(action),
			Layer::Confirm => self.confirm(action),
			Layer::Help => self.help(action),
			Layer::Cmp => self.cmp(action),
			Layer::Which => self.which(action),
			Layer::Notify => self.notify(action),
		}
	}

	fn null(&mut self, _action: ActionCow) -> Result<Data> { succ!() }

	fn app(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(app:$name, cx, action);
				}
			};
		}

		on!(accept_payload);
		on!(plugin);
		on!(plugin_do);
		on!(update_progress);
		on!(lua);
		on!(deprecate);
		on!(passthrough);
		on!(theme);
		on!(stop);
		on!(quit);

		match &*action.name {
			"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
			"resume" => act!(app:resume, cx, crate::Root::reflow as fn(_) -> _),
			_ => succ!(),
		}
	}

	fn mgr(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(mgr:$name, cx, action)
				}
			};
		}

		on!(cd);
		on!(update_yanked);

		on!(update_files);
		on!(update_mimes);
		on!(update_paged);
		on!(watch);
		on!(peek);
		on!(seek);
		on!(spot);
		on!(refresh);
		on!(quit);
		on!(close);
		on!(suspend);
		on!(escape);
		on!(update_peeked);
		on!(update_spotted);

		// Navigation
		on!(arrow);
		on!(leave);
		on!(enter);
		on!(back);
		on!(forward);
		on!(reveal);
		on!(follow);
		on!(stash);

		// Toggle
		on!(toggle);
		on!(toggle_all);
		on!(visual_mode);

		// Operation
		on!(open);
		on!(open_do);
		on!(yank);
		on!(unyank);
		on!(paste);
		on!(link);
		on!(hardlink);
		on!(remove);
		on!(remove_do);
		on!(create);
		on!(rename);
		on!(copy);
		on!(shell);
		on!(hidden);
		on!(linemode);
		on!(search);
		on!(search_do);
		on!(bulk_exit);
		on!(bulk_rename);
		on!(bulk_create);

		// Filter
		on!(filter);
		on!(filter_do);

		// Find
		on!(find);
		on!(find_do);
		on!(find_arrow);

		// Sorting
		on!(sort);

		// Tabs
		on!(tab_create);
		on!(tab_rename);
		on!(tab_close);
		on!(tab_switch);
		on!(tab_swap);

		// VFS
		on!(download);
		on!(upload);
		on!(displace_do);

		match action.name.as_ref() {
			// Help
			"help" => act!(help:toggle, cx, Layer::Mgr),
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => succ!(),
		}
	}

	fn tasks(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(tasks:$name, cx, action);
				}
			};
		}

		on!(output);
		on!(update_succeed);
		on!(spawn);

		on!(show);
		on!(close);
		on!(arrow);
		on!(inspect);
		on!(cancel);
		on!(process_open);

		match action.name.as_ref() {
			// Help
			"help" => act!(help:toggle, cx, Layer::Tasks),
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => succ!(),
		}
	}

	fn spot(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(spot:$name, cx, action);
				}
			};
		}

		on!(arrow);
		on!(close);
		on!(swipe);
		on!(copy);

		match action.name.as_ref() {
			// Help
			"help" => act!(help:toggle, cx, Layer::Spot),
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => succ!(),
		}
	}

	fn pick(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(pick:$name, cx, action);
				}
			};
		}

		on!(show);
		on!(close);
		on!(arrow);

		match action.name.as_ref() {
			// Help
			"help" => act!(help:toggle, cx, Layer::Pick),
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => succ!(),
		}
	}

	fn input(&mut self, action: ActionCow) -> Result<Data> {
		let mut guard;
		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
					return act!(input:$name, cx, action);
				}
			};
			($layer:ident : $name:ident, $opt:expr) => {{
				drop(guard);
				let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
				return act!($layer:$name, cx, $opt);
			}};
		}

		on!(escape);
		on!(show);
		on!(close);
		on!(recall);
		on!(remember);

		guard = match self.app.core.input.lock_mut() {
			Some(g) => g,
			None => succ!(),
		};

		match guard.mode() {
			InputMode::Normal => match action.name.as_ref() {
				"help" => on!(help:toggle, Layer::Input),
				"plugin" => on!(app:plugin, action),
				"lua" => on!(app:lua, action),
				_ => {}
			},
			InputMode::Insert => match action.name.as_ref() {
				"complete" => on!(input:complete, action),
				_ => {}
			},
			InputMode::Replace => {}
		};

		guard.execute(action)
	}

	fn confirm(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(confirm:$name, cx, action);
				}
			};
		}

		on!(arrow);
		on!(show);
		on!(close);

		succ!();
	}

	fn help(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(help:$name, cx, action);
				}
			};
		}

		on!(escape);
		on!(arrow);
		on!(close);

		match action.name.as_ref() {
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => {
				cx.help.input.execute(action)?;
				cx.help.filter_apply();
				succ!()
			}
		}
	}

	fn cmp(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(cmp:$name, cx, action);
				}
			};
		}

		on!(trigger);
		on!(show);
		on!(close);
		on!(arrow);

		match action.name.as_ref() {
			// Help
			"help" => act!(help:toggle, cx, Layer::Cmp),
			// Plugin
			"plugin" => act!(app:plugin, cx, action),
			// Lua
			"lua" => act!(app:lua, cx, action),
			_ => succ!(),
		}
	}

	fn which(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(which:$name, cx, action);
				}
			};
		}

		on!(activate);
		on!(dismiss);

		succ!();
	}

	fn notify(&mut self, action: ActionCow) -> Result<Data> {
		let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;

		macro_rules! on {
			($name:ident) => {
				if action.name == stringify!($name) {
					return act!(notify:$name, cx, action);
				}
			};
		}

		on!(push);
		on!(tick);

		succ!();
	}
}
