use anyhow::{Result, bail};
use yazi_macro::{act, render, render_and, succ};
use yazi_parser::{VoidForm, mgr::EscapeForm};
use yazi_scheduler::NotifyProxy;
use yazi_shared::{data::Data, url::UrlLike};

use crate::{Actor, Ctx};

pub struct Escape;

impl Actor for Escape {
	type Form = EscapeForm;

	const NAME: &str = "escape";

	fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
		if form.is_empty() {
			_ = act!(mgr:escape_find, cx)? != false
				|| act!(mgr:escape_visual, cx)? != false
				|| act!(mgr:escape_filter, cx)? != false
				|| act!(mgr:escape_select, cx)? != false
				|| act!(mgr:escape_search, cx)? != false;
			succ!();
		}

		if form.contains(EscapeForm::FIND) {
			act!(mgr:escape_find, cx)?;
		}
		if form.contains(EscapeForm::VISUAL) {
			act!(mgr:escape_visual, cx)?;
		}
		if form.contains(EscapeForm::FILTER) {
			act!(mgr:escape_filter, cx)?;
		}
		if form.contains(EscapeForm::SELECT) {
			act!(mgr:escape_select, cx)?;
		}
		if form.contains(EscapeForm::SEARCH) {
			act!(mgr:escape_search, cx)?;
		}
		succ!();
	}
}

// --- Find
pub struct EscapeFind;

impl Actor for EscapeFind {
	type Form = VoidForm;

	const NAME: &str = "escape_find";

	fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
		succ!(render_and!(cx.tab_mut().finder.take().is_some()))
	}
}

// --- Visual
pub struct EscapeVisual;

impl Actor for EscapeVisual {
	type Form = VoidForm;

	const NAME: &str = "escape_visual";

	fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
		let tab = cx.tab_mut();

		let select = tab.mode.is_select();
		let Some((_, indices)) = tab.mode.take_visual() else { succ!(false) };

		render!();
		let urls: Vec<_> =
			indices.into_iter().filter_map(|i| tab.current.files.get(i)).map(|f| &f.url).collect();

		if !select {
			tab.selected.remove_many(urls);
		} else if urls.len() != tab.selected.add_many(urls) {
			NotifyProxy::push_warn(
				"Escape visual mode",
				"Some files cannot be selected, due to path nesting conflict.",
			);
			bail!("Some files cannot be selected, due to path nesting conflict.");
		}

		succ!(true)
	}
}

// --- Filter
pub struct EscapeFilter;

impl Actor for EscapeFilter {
	type Form = VoidForm;

	const NAME: &str = "escape_filter";

	fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
		if cx.current_mut().files.filter().is_none() {
			succ!(false);
		}

		act!(mgr:filter_do, cx)?;
		render!();
		succ!(true);
	}
}

// --- Select
pub struct EscapeSelect;

impl Actor for EscapeSelect {
	type Form = VoidForm;

	const NAME: &str = "escape_select";

	fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
		let tab = cx.tab_mut();
		if tab.selected.is_empty() {
			succ!(false);
		}

		tab.selected.clear();
		if tab.hovered().is_some_and(|h| h.is_dir()) {
			act!(mgr:peek, cx, true)?;
		}

		render!();
		succ!(true);
	}
}

// --- Search
pub struct EscapeSearch;

impl Actor for EscapeSearch {
	type Form = VoidForm;

	const NAME: &str = "escape_search";

	fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
		let b = cx.cwd().is_search();
		act!(mgr:search_stop, cx)?;
		succ!(render_and!(b));
	}
}
