use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::defer::DeferMessageContext;
use crate::messages::dialog::DialogMessageContext;
use crate::messages::layout::layout_message_handler::LayoutMessageContext;
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::preferences::preferences_message_handler::PreferencesMessageContext;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
use graph_craft::application_io::resource::ResourceStorage;
use std::sync::Arc;

#[derive(Debug, Default)]
pub struct Dispatcher {
	message_queues: Vec<VecDeque<Message>>,
	pub responses: Vec<FrontendMessage>,
	pub frontend_update_messages: Vec<Message>,
	pub message_handlers: DispatcherMessageHandlers,
}

#[derive(Debug, Default)]
pub struct DispatcherMessageHandlers {
	animation_message_handler: AnimationMessageHandler,
	app_window_message_handler: AppWindowMessageHandler,
	pub(crate) future_message_handler: FutureMessageHandler,
	broadcast_message_handler: BroadcastMessageHandler,
	clipboard_message_handler: ClipboardMessageHandler,
	color_picker_message_handler: ColorPickerMessageHandler,
	debug_message_handler: DebugMessageHandler,
	defer_message_handler: DeferMessageHandler,
	dialog_message_handler: DialogMessageHandler,
	input_preprocessor_message_handler: InputPreprocessorMessageHandler,
	key_mapping_message_handler: KeyMappingMessageHandler,
	layout_message_handler: LayoutMessageHandler,
	menu_bar_message_handler: MenuBarMessageHandler,
	network_message_handler: NetworkMessageHandler,
	pub(crate) portfolio_message_handler: PortfolioMessageHandler,
	preferences_message_handler: PreferencesMessageHandler,
	pub(crate) resource_storage_message_handler: ResourceStorageMessageHandler,
	tool_message_handler: ToolMessageHandler,
	viewport_message_handler: ViewportMessageHandler,
}

impl DispatcherMessageHandlers {
	pub fn with_resource_storage(resource_storage: Arc<dyn ResourceStorage>) -> Self {
		Self {
			resource_storage_message_handler: ResourceStorageMessageHandler::new(resource_storage),
			..Self::default()
		}
	}

	pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
		Self {
			portfolio_message_handler: PortfolioMessageHandler::with_executor(executor),
			..Self::default()
		}
	}
}

/// For optimization, these are messages guaranteed to be redundant when repeated.
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
/// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(
		NodeGraphMessageDiscriminant::RunDocumentGraph,
	))),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitActiveGraphRender),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::SubmitEyedropperPreviewRender),
	MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateUIScale),
];
/// Since we don't need to update the frontend multiple times per frame,
/// we have a set of messages which we will buffer until the next frame is requested.
const FRONTEND_UPDATE_MESSAGES: &[MessageDiscriminant] = &[
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
		PropertiesPanelMessageDiscriminant::Refresh,
	))),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::UpdateDocumentWidgets),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderRulers)),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)),
	MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
];
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
	MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
	MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
	MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::AutoSaveAllDocuments),
];
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];

impl Dispatcher {
	pub fn new(resource_storage: Arc<dyn ResourceStorage>, working_copy_root: Option<std::path::PathBuf>) -> Self {
		let mut s = Self::default();
		s.message_handlers.resource_storage_message_handler = ResourceStorageMessageHandler::new(resource_storage);
		s.message_handlers.portfolio_message_handler.set_working_copy_root(working_copy_root);
		s
	}

	pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
		let mut s = Self::default();
		s.message_handlers.portfolio_message_handler = PortfolioMessageHandler::with_executor(executor);
		s
	}

	// If the deepest queues (higher index in queues list) are now empty (after being popped from) then remove them
	fn cleanup_queues(&mut self, leave_last: bool) {
		while self.message_queues.last().filter(|queue| queue.is_empty()).is_some() {
			if leave_last && self.message_queues.len() == 1 {
				break;
			}
			self.message_queues.pop();
		}
	}

	/// Add a message to a queue so that it can be executed.
	/// If `process_after_all_current` is set, all currently queued messages (including children) will be processed first.
	/// If not set, it (and its children) will be processed as soon as possible.
	pub fn schedule_execution(message_queues: &mut Vec<VecDeque<Message>>, process_after_all_current: bool, messages: impl IntoIterator<Item = Message>) {
		match message_queues.first_mut() {
			// If there are currently messages being processed and we are processing after them, add to the end of the first queue
			Some(queue) if process_after_all_current => queue.extend(messages),
			// In all other cases, make a new inner queue and add our message there
			_ => message_queues.push(VecDeque::from_iter(messages)),
		}
	}

	pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
		let message = message.into();

		// Drain async results into the queue before processing the new message.
		let mut async_results = VecDeque::new();
		self.message_handlers.future_message_handler.drain_results(&mut async_results);
		if !async_results.is_empty() {
			Self::schedule_execution(&mut self.message_queues, true, async_results);
		}

		// If we are not maintaining the buffer, simply add to the current queue
		Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);

		while let Some(message) = self.message_queues.last_mut().and_then(VecDeque::pop_front) {
			// Skip processing of this message if it will be processed later (at the end of the shallowest level queue)
			if FRONTEND_UPDATE_MESSAGES.contains(&message.to_discriminant()) {
				let already_in_queue = self.message_queues.first().is_some_and(|queue| queue.contains(&message));
				if already_in_queue {
					self.cleanup_queues(false);
					continue;
				} else if self.message_queues.len() > 1 {
					if !self.frontend_update_messages.contains(&message) {
						self.frontend_update_messages.push(message);
					}
					self.cleanup_queues(false);
					continue;
				}
			}
			if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) {
				let already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some();
				if already_in_queue {
					self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
					self.cleanup_queues(false);
					continue;
				} else if self.message_queues.len() > 1 {
					self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
					self.cleanup_queues(true);
					self.message_queues[0].add(message);
					continue;
				}
			}

			// Print the message at a verbosity level of `info`
			self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);

			// Create a new queue for the child messages
			let mut queue = VecDeque::new();

			// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
			match message {
				Message::Animation(message) => {
					if let AnimationMessage::IncrementFrameCounter = &message {
						self.message_queues[0].extend(self.frontend_update_messages.drain(..));
					}
					self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
				}
				Message::AppWindow(message) => {
					self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
				}
				Message::Future(message) => {
					self.message_handlers.future_message_handler.process_message(message, &mut queue, FutureMessageContext {});
				}
				Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
				Message::Clipboard(message) => {
					let context = ClipboardMessageContext {
						portfolio: &mut self.message_handlers.portfolio_message_handler,
						current_tool: &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type,
						resource_storage: &self.message_handlers.resource_storage_message_handler,
					};
					self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, context);
				}
				Message::ColorPicker(message) => self.message_handlers.color_picker_message_handler.process_message(message, &mut queue, ()),
				Message::Debug(message) => {
					self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
				}
				Message::Defer(message) => {
					let context = DeferMessageContext {
						portfolio: &self.message_handlers.portfolio_message_handler,
					};
					self.message_handlers.defer_message_handler.process_message(message, &mut queue, context);
				}
				Message::Dialog(message) => {
					let context = DialogMessageContext {
						portfolio: &self.message_handlers.portfolio_message_handler,
						preferences: &self.message_handlers.preferences_message_handler,
					};
					self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
				}
				Message::Frontend(message) => {
					self.responses.push(message);
				}
				Message::InputPreprocessor(message) => {
					self.message_handlers.input_preprocessor_message_handler.process_message(
						message,
						&mut queue,
						InputPreprocessorMessageContext {
							viewport: &self.message_handlers.viewport_message_handler,
						},
					);
				}
				Message::KeyMapping(message) => {
					let input = &self.message_handlers.input_preprocessor_message_handler;
					let actions = self.collect_actions();

					self.message_handlers
						.key_mapping_message_handler
						.process_message(message, &mut queue, KeyMappingMessageContext { input, actions });
				}
				Message::Layout(message) => {
					let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
					let context = LayoutMessageContext { action_input_mapping };

					self.message_handlers.layout_message_handler.process_message(message, &mut queue, context);
				}
				Message::Network(message) => {
					self.message_handlers.network_message_handler.process_message(message, &mut queue, NetworkMessageContext {});
				}
				Message::ResourceStorage(message) => {
					self.message_handlers
						.resource_storage_message_handler
						.process_message(message, &mut queue, ResourceStorageMessageContext {});
				}
				Message::Portfolio(message) => {
					self.message_handlers.portfolio_message_handler.process_message(
						message,
						&mut queue,
						PortfolioMessageContext {
							ipp: &self.message_handlers.input_preprocessor_message_handler,
							preferences: &self.message_handlers.preferences_message_handler,
							current_tool: &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type,
							reset_node_definitions_on_open: self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open,
							timing_information: self.message_handlers.animation_message_handler.timing_information(),
							animation: &self.message_handlers.animation_message_handler,
							viewport: &self.message_handlers.viewport_message_handler,
							resource_storage: &self.message_handlers.resource_storage_message_handler,
						},
					);
				}
				Message::MenuBar(message) => {
					let menu_bar_message_handler = &mut self.message_handlers.menu_bar_message_handler;

					menu_bar_message_handler.focus_document = self.message_handlers.portfolio_message_handler.workspace_panel_layout.focus_document;
					let layout = &self.message_handlers.portfolio_message_handler.workspace_panel_layout;
					menu_bar_message_handler.data_panel_open = layout.is_panel_present(PanelType::Data);
					menu_bar_message_handler.layers_panel_open = layout.is_panel_present(PanelType::Layers);
					menu_bar_message_handler.properties_panel_open = layout.is_panel_present(PanelType::Properties);
					menu_bar_message_handler.message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
					menu_bar_message_handler.reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;
					menu_bar_message_handler.show_storage_preferences = self.message_handlers.preferences_message_handler.show_storage_preferences;

					if let Some(document) = self
						.message_handlers
						.portfolio_message_handler
						.active_document_id
						.and_then(|document_id| self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id))
					{
						let selected_nodes = document.network_interface.selected_nodes();
						let metadata = &document.network_interface.document_network_metadata().persistent_metadata;

						menu_bar_message_handler.has_active_document = true;
						menu_bar_message_handler.canvas_tilted = document.document_ptz.tilt() != 0.;
						menu_bar_message_handler.canvas_flipped = document.document_ptz.flip;
						menu_bar_message_handler.rulers_visible = document.rulers_visible;
						menu_bar_message_handler.node_graph_open = document.is_graph_overlay_open();
						menu_bar_message_handler.has_selected_nodes = selected_nodes.selected_nodes().next().is_some();
						menu_bar_message_handler.has_selected_layers = selected_nodes.selected_visible_layers(&document.network_interface).next().is_some();
						menu_bar_message_handler.has_selection_history = (!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty());
						menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
					} else {
						menu_bar_message_handler.has_active_document = false;
						menu_bar_message_handler.canvas_tilted = false;
						menu_bar_message_handler.canvas_flipped = false;
						menu_bar_message_handler.rulers_visible = false;
						menu_bar_message_handler.node_graph_open = false;
						menu_bar_message_handler.has_selected_nodes = false;
						menu_bar_message_handler.has_selected_layers = false;
						menu_bar_message_handler.has_selection_history = (false, false);
						menu_bar_message_handler.make_path_editable_is_allowed = false;
					}

					menu_bar_message_handler.process_message(message, &mut queue, ());
				}
				Message::Preferences(message) => {
					let context = PreferencesMessageContext {
						tool_message_handler: &self.message_handlers.tool_message_handler,
					};

					self.message_handlers.preferences_message_handler.process_message(message, &mut queue, context);
				}
				Message::Tool(message) => {
					let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {
						warn!("Called ToolMessage without an active document.\nGot {message:?}");
						return;
					};
					let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
						warn!("Called ToolMessage with an invalid active document.\nGot {message:?}");
						return;
					};

					let context = ToolMessageContext {
						document_id,
						document,
						input: &self.message_handlers.input_preprocessor_message_handler,
						fonts: &self.message_handlers.portfolio_message_handler.fonts,
						node_graph: &self.message_handlers.portfolio_message_handler.executor,
						preferences: &self.message_handlers.preferences_message_handler,
						viewport: &self.message_handlers.viewport_message_handler,
					};

					self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
				}
				Message::Viewport(message) => {
					self.message_handlers.viewport_message_handler.process_message(message, &mut queue, ());
				}
				Message::NoOp => {}
				Message::Batched { messages } => {
					messages.into_iter().for_each(|message| self.handle_message(message, false));
				}
			}

			// If there are child messages, append the queue to the list of queues
			if !queue.is_empty() {
				self.message_queues.push(queue);
			}

			self.cleanup_queues(false);
		}
	}

	pub fn collect_actions(&self) -> ActionList {
		// TODO: Reduce the number of heap allocations
		let mut list = Vec::new();
		list.extend(self.message_handlers.app_window_message_handler.actions());
		list.extend(self.message_handlers.clipboard_message_handler.actions());
		list.extend(self.message_handlers.dialog_message_handler.actions());
		list.extend(self.message_handlers.animation_message_handler.actions());
		list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
		list.extend(self.message_handlers.key_mapping_message_handler.actions());
		list.extend(self.message_handlers.debug_message_handler.actions());
		if let Some(document) = self.message_handlers.portfolio_message_handler.active_document()
			&& !document.graph_view_overlay_open
		{
			list.extend(self.message_handlers.tool_message_handler.actions_with_preferences(&self.message_handlers.preferences_message_handler));
		}
		list.extend(self.message_handlers.portfolio_message_handler.actions());
		list
	}

	pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
		self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses)
	}

	/// Create the tree structure for logging the messages as a tree
	fn create_indents(queues: &[VecDeque<Message>]) -> String {
		String::from_iter(queues.iter().enumerate().skip(1).map(|(index, queue)| {
			if index == queues.len() - 1 {
				if queue.is_empty() { "└── " } else { "├── " }
			} else if queue.is_empty() {
				"   "
			} else {
				"│    "
			}
		}))
	}

	/// Logs a message that is about to be executed, either as a tree
	/// with a discriminant or the entire payload (depending on settings)
	fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
		let discriminant = MessageDiscriminant::from(message);
		let is_blocked =
			|discriminant| DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
		let is_batch_all_blocked = if let Message::Batched { messages } = message {
			messages.iter().all(|message| is_blocked(MessageDiscriminant::from(message)))
		} else {
			false
		};

		if !is_blocked(discriminant) && !is_batch_all_blocked {
			match message_logging_verbosity {
				MessageLoggingVerbosity::Off => {}
				MessageLoggingVerbosity::Names => {
					info!("{}{:?}", Self::create_indents(queues), message.to_discriminant());
				}
				MessageLoggingVerbosity::Contents => {
					if !(matches!(message, Message::InputPreprocessor(_))) {
						info!("Message: {}{:?}", Self::create_indents(queues), message);
					}
				}
			}
		}
	}

	/// Logs into the tree that the message is in the side effect free messages and its execution will be deferred
	fn log_deferred_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
		if let MessageLoggingVerbosity::Names = message_logging_verbosity {
			info!("{}Deferred \"{:?}\" because it's a SIDE_EFFECT_FREE_MESSAGE", Self::create_indents(queues), message.to_discriminant());
		}
	}
}

#[cfg(test)]
mod test {
	pub use crate::test_utils::test_prelude::*;

	#[tokio::test]
	/// This test will fail when you make changes to the underlying serialization format for a document.
	async fn check_if_demo_art_opens() {
		use crate::messages::layout::utility_types::widget_prelude::*;

		let print_problem_to_terminal_on_failure = |value: &String| {
			println!();
			println!("-------------------------------------------------");
			println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
			println!();
			println!("NOTE:");
			println!("Document upgrading isn't performed in tests like when opening in the actual editor.");
			println!("You may need to open and re-save a document in the editor to apply its migrations.");
			println!();
			println!("DisplayDialogError details:");
			println!();
			println!("Description:");
			println!("{value}");
			println!("-------------------------------------------------");
			println!();

			panic!()
		};

		let mut editor = EditorTestUtils::create();

		// UNCOMMENT THIS FOR RUNNING UNDER MIRI
		//
		// let files = [
		// 	include_str!("../../demo-artwork/changing-seasons.graphite"),
		// 	include_str!("../../demo-artwork/isometric-fountain.graphite"),
		// 	include_str!("../../demo-artwork/painted-dreams.graphite"),
		// 	include_str!("../../demo-artwork/procedural-string-lights.graphite"),
		// 	include_str!("../../demo-artwork/parametric-dunescape.graphite"),
		// 	include_str!("../../demo-artwork/red-dress.graphite"),
		// 	include_str!("../../demo-artwork/valley-of-spires.graphite"),
		// ];
		// for (id, document_serialized_content) in files.iter().enumerate() {
		// let document_name = format!("document {id}");

		for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
			let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();

			assert_eq!(
				document_serialized_content.lines().count(),
				1,
				"Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
			);

			let responses = editor.editor.handle_message(PortfolioMessage::OpenFile {
				path: file_name.into(),
				content: document_serialized_content.bytes().collect(),
			});

			// Check if the graph renders
			if let Err(e) = editor.eval_graph().await {
				print_problem_to_terminal_on_failure(&format!("Failed to evaluate the graph for document '{document_name}':\n{e}"));
			}

			for response in responses {
				// Check for the existence of the file format incompatibility warning dialog after opening the test file
				if let FrontendMessage::UpdateLayout {
					layout_target: LayoutTarget::DialogColumn1,
					diff,
				} = response
				{
					if let DiffUpdate::Layout(sub_layout) = &diff[0].new_value {
						if let LayoutGroup::Row(WidgetRow { widgets }) = &sub_layout.0[0] {
							if let Widget::TextLabel(TextLabel { value, .. }) = &*widgets[0].widget {
								print_problem_to_terminal_on_failure(value);
							}
						}
					}
				}
			}
		}
	}
}
