use super::{
	seat::{SeatWrapper, handle_cursor},
	state::{ClientState, WaylandState},
	surface::CoreSurface,
	utils::*,
};
use crate::{
	core::error::Result,
	nodes::{
		drawable::model::ModelPart,
		items::panel::{
			Backend, ChildInfo, Geometry, PanelItem, PanelItemInitData, SurfaceId, ToplevelInfo,
		},
	},
};
use color_eyre::eyre::eyre;
use mint::Vector2;
use parking_lot::Mutex;
use rand::Rng;
use rustc_hash::FxHashMap;
use smithay::{
	delegate_xdg_shell,
	desktop::PopupKind,
	reexports::{
		wayland_protocols::xdg::{
			decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode,
			shell::server::xdg_toplevel::{ResizeEdge, State},
		},
		wayland_server::{
			Resource,
			protocol::{wl_output::WlOutput, wl_seat::WlSeat, wl_surface::WlSurface},
		},
	},
	utils::{Logical, Rectangle, Serial},
	wayland::{
		compositor::add_post_commit_hook,
		shell::xdg::{
			PopupSurface, PositionerState, ShellClient, ToplevelSurface, XdgShellHandler,
			XdgShellState,
		},
	},
};
use std::sync::{Arc, Weak};
use tracing::warn;

fn get_unconstrained_popup_geometry(positioner: &PositionerState) -> Geometry {
	positioner
		.get_unconstrained_geometry(Rectangle {
			loc: (-100000, -100000).into(),
			size: (200000, 200000).into(),
		})
		.into()
}

impl From<Rectangle<i32, Logical>> for Geometry {
	fn from(value: Rectangle<i32, Logical>) -> Self {
		Geometry {
			origin: [value.loc.x, value.loc.y].into(),
			size: [value.size.w as u32, value.size.h as u32].into(),
		}
	}
}

pub fn surface_panel_item(wl_surface: &WlSurface) -> Option<Arc<PanelItem<XdgBackend>>> {
	let panel_item = wl_surface
		.get_data::<Weak<PanelItem<XdgBackend>>>()
		.as_ref()
		.and_then(Weak::upgrade);
	if panel_item.is_none() {
		warn!("Couldn't get panel item");
	}
	panel_item
}

impl XdgShellHandler for WaylandState {
	fn xdg_shell_state(&mut self) -> &mut XdgShellState {
		&mut self.xdg_shell
	}

	fn new_client(&mut self, _client: ShellClient) {}
	fn client_destroyed(&mut self, _client: ShellClient) {}

	fn new_toplevel(&mut self, toplevel: ToplevelSurface) {
		toplevel.wl_surface().insert_data(SurfaceId::Toplevel(()));
		toplevel.with_pending_state(|s| {
			s.decoration_mode = Some(Mode::ServerSide);
			s.states.set(State::TiledTop);
			s.states.set(State::TiledBottom);
			s.states.set(State::TiledLeft);
			s.states.set(State::TiledRight);
			s.states.set(State::Maximized);
			s.states.unset(State::Fullscreen);
		});

		let initial_size = toplevel
			.wl_surface()
			.get_size()
			.unwrap_or(Vector2::from([0; 2]));

		let initial_toplevel_info = ToplevelInfo {
			parent: toplevel.wl_surface().get_parent(),
			title: toplevel.wl_surface().get_title(),
			app_id: toplevel.wl_surface().get_app_id(),
			size: initial_size,
			min_size: toplevel
				.wl_surface()
				.min_size()
				.map(|s| Vector2::from([s.x as f32, s.y as f32])),
			max_size: toplevel
				.wl_surface()
				.max_size()
				.map(|s| Vector2::from([s.x as f32, s.y as f32])),
			logical_rectangle: toplevel.wl_surface().get_geometry().unwrap_or(Geometry {
				origin: [0; 2].into(),
				size: initial_size,
			}),
		};
		toplevel
			.wl_surface()
			.insert_data(Mutex::new(initial_toplevel_info));

		CoreSurface::add_to(toplevel.wl_surface());

		add_post_commit_hook(
			toplevel.wl_surface(),
			|_state: &mut WaylandState, _dh, surf| {
				let parent = surf.get_parent();
				let new_size = surf.get_size().unwrap_or(Vector2::from([0; 2]));
				let min_size = surf
					.min_size()
					.map(|s| Vector2::from([s.x as f32, s.y as f32]));
				let max_size = surf
					.max_size()
					.map(|s| Vector2::from([s.x as f32, s.y as f32]));
				let logical_rectangle = surf.get_geometry().unwrap_or_default();

				let mut size_changed = false;
				surf.with_toplevel_info(|info| {
					info.parent = parent;
					if new_size != info.size {
						info.size = new_size;
						size_changed = true;
					}
					info.min_size = min_size;
					info.max_size = max_size;
					info.logical_rectangle = logical_rectangle;
				});

				if size_changed {
					let Some(panel_item) = surface_panel_item(surf) else {
						return;
					};
					if let Some(toplevel_info) = surf.get_toplevel_info() {
						panel_item.toplevel_size_changed(toplevel_info.size);
					}
				}
			},
		);

		add_post_commit_hook(
			toplevel.wl_surface(),
			|state: &mut WaylandState, _dh, surf| {
				if surface_panel_item(surf).is_some() {
					return;
				}
				let client = surf.client().unwrap();
				let client_state = client.get_data::<ClientState>().unwrap();
				let Some(toplevel) = state
					.xdg_shell
					.toplevel_surfaces()
					.iter()
					.find(|s| s.wl_surface() == surf)
				else {
					return;
				};
				let (node, panel_item) = PanelItem::create(
					Box::new(XdgBackend::create(
						toplevel.clone(),
						client_state.seat.clone(),
					)),
					client_state.pid,
				);
				handle_cursor(&panel_item, panel_item.backend.seat.cursor_info_rx.clone());
				surf.insert_data(Arc::downgrade(&panel_item));
				surf.insert_data(node);
			},
		);
	}
	fn toplevel_destroyed(&mut self, toplevel: ToplevelSurface) {
		let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
			return;
		};
		panel_item.backend.seat.unfocus(toplevel.wl_surface(), self);
		panel_item.backend.toplevel.lock().take();
		panel_item.backend.children.lock().clear();
	}
	fn app_id_changed(&mut self, toplevel: ToplevelSurface) {
		let wl_surface = toplevel.wl_surface();
		let Some(app_id) = wl_surface.get_app_id() else {
			return;
		};

		wl_surface.with_toplevel_info(|info| {
			info.app_id = Some(app_id.clone());
		});

		let Some(panel_item) = surface_panel_item(wl_surface) else {
			return;
		};
		panel_item.toplevel_app_id_changed(&app_id)
	}

	fn title_changed(&mut self, toplevel: ToplevelSurface) {
		let wl_surface = toplevel.wl_surface();
		let Some(title) = wl_surface.get_title() else {
			return;
		};

		wl_surface.with_toplevel_info(|info| {
			info.title = Some(title.clone());
		});

		let Some(panel_item) = surface_panel_item(wl_surface) else {
			return;
		};
		panel_item.toplevel_title_changed(&title)
	}
	fn new_popup(&mut self, popup: PopupSurface, positioner: PositionerState) {
		self.popup_manager
			.track_popup(PopupKind::Xdg(popup.clone()))
			.unwrap();

		let id = rand::thread_rng().gen_range(0..u64::MAX);
		popup.wl_surface().insert_data(SurfaceId::Child(id));
		let Some(parent) = popup.get_parent_surface() else {
			warn!("No parent surface found for popup");
			return;
		};
		CoreSurface::add_to(popup.wl_surface());

		popup.wl_surface().insert_data(Mutex::new(ChildInfo {
			id,
			parent: parent.get_data::<SurfaceId>().unwrap(),
			geometry: get_unconstrained_popup_geometry(&positioner),
			z_order: 1,
			receives_input: true,
		}));

		let Some(panel_item) = surface_panel_item(&parent) else {
			warn!("No panel item found for popup parent");
			return;
		};
		let panel_item_weak = Arc::downgrade(&panel_item);
		add_post_commit_hook(
			popup.wl_surface(),
			move |_: &mut WaylandState, _dh, surf| {
				if surface_panel_item(surf).is_some() {
					return;
				}
				surf.insert_data(panel_item_weak.clone());
				let Some(panel) = surface_panel_item(surf) else {
					warn!("Failed to get panel item for popup surface");
					return;
				};
				panel.backend.new_child(surf);
			},
		);
	}
	fn reposition_request(
		&mut self,
		popup: PopupSurface,
		positioner: PositionerState,
		_token: u32,
	) {
		let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
			return;
		};

		popup.wl_surface().with_child_info(|ci| {
			ci.geometry = get_unconstrained_popup_geometry(&positioner);
		});

		panel_item.backend.reposition_child(popup.wl_surface());
	}
	fn popup_destroyed(&mut self, popup: PopupSurface) {
		let Some(panel_item) = surface_panel_item(popup.wl_surface()) else {
			return;
		};
		panel_item.backend.seat.unfocus(popup.wl_surface(), self);
		panel_item.backend.drop_child(popup.wl_surface());
	}

	fn grab(&mut self, _popup: PopupSurface, _seat: WlSeat, _serial: Serial) {}

	fn move_request(&mut self, toplevel: ToplevelSurface, _seat: WlSeat, _serial: Serial) {
		let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
			return;
		};
		panel_item.toplevel_move_request();
	}
	fn resize_request(
		&mut self,
		toplevel: ToplevelSurface,
		_seat: WlSeat,
		_serial: Serial,
		edges: ResizeEdge,
	) {
		let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
			return;
		};
		let (up, down, left, right) = match edges {
			ResizeEdge::None => (false, false, false, false),
			ResizeEdge::Top => (true, false, false, false),
			ResizeEdge::Bottom => (false, true, false, false),
			ResizeEdge::Left => (false, false, true, false),
			ResizeEdge::TopLeft => (true, false, true, false),
			ResizeEdge::BottomLeft => (false, true, true, false),
			ResizeEdge::Right => (false, false, false, true),
			ResizeEdge::TopRight => (true, false, false, true),
			ResizeEdge::BottomRight => (false, true, false, true),
			_ => (false, false, false, false),
		};
		panel_item.toplevel_resize_request(up, down, left, right);
	}

	fn maximize_request(&mut self, toplevel: ToplevelSurface) {
		toplevel.with_pending_state(|s| {
			s.states.set(State::Maximized);
			s.states.unset(State::Fullscreen);
		});
		toplevel.send_configure();

		let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
			return;
		};
		panel_item.toplevel_fullscreen_active(false);
	}
	fn fullscreen_request(&mut self, toplevel: ToplevelSurface, _output: Option<WlOutput>) {
		toplevel.with_pending_state(|s| {
			s.states.set(State::Fullscreen);
			s.states.unset(State::Maximized);
		});
		toplevel.send_configure();

		let Some(panel_item) = surface_panel_item(toplevel.wl_surface()) else {
			return;
		};
		panel_item.toplevel_fullscreen_active(true);
	}
}
delegate_xdg_shell!(WaylandState);

pub struct XdgBackend {
	toplevel: Mutex<Option<ToplevelSurface>>,
	pub children: Mutex<FxHashMap<u64, WlSurface>>,
	seat: Arc<SeatWrapper>,
}
impl XdgBackend {
	pub fn create(toplevel: ToplevelSurface, seat: Arc<SeatWrapper>) -> Self {
		XdgBackend {
			toplevel: Mutex::new(Some(toplevel)),
			children: Mutex::new(FxHashMap::default()),
			seat,
		}
	}
	fn wl_surface_from_id(&self, id: &SurfaceId) -> Option<WlSurface> {
		match id {
			SurfaceId::Toplevel(_) => Some(self.toplevel.lock().clone()?.wl_surface().clone()),
			SurfaceId::Child(id) => self.children.lock().get(id).cloned(),
		}
	}
	fn panel_item(&self) -> Option<Arc<PanelItem<XdgBackend>>> {
		surface_panel_item(self.toplevel.lock().clone()?.wl_surface())
	}

	pub fn new_child(&self, surface: &WlSurface) {
		let Some(panel_item) = self.panel_item() else {
			return;
		};
		let Some(child_info) = surface.get_child_info() else {
			return;
		};

		self.children.lock().insert(child_info.id, surface.clone());
		panel_item.create_child(child_info.id, &child_info);
	}
	pub fn reposition_child(&self, surface: &WlSurface) {
		let Some(panel_item) = self.panel_item() else {
			return;
		};
		let Some(child_info) = surface.get_child_info() else {
			return;
		};

		panel_item.reposition_child(child_info.id, &child_info.geometry);
	}
	pub fn drop_child(&self, surface: &WlSurface) {
		let Some(panel_item) = self.panel_item() else {
			return;
		};
		let Some(child_info) = surface.get_child_info() else {
			return;
		};
		panel_item.destroy_child(child_info.id);
		self.children.lock().remove(&child_info.id);
	}
}
impl Backend for XdgBackend {
	fn start_data(&self) -> Result<PanelItemInitData> {
		let cursor = self
			.seat
			.cursor_info_rx
			.borrow()
			.surface
			.clone()
			.and_then(|s| s.upgrade().ok())
			.as_ref()
			.and_then(|c| c.get_size())
			.map(|size| Geometry {
				origin: [0; 2].into(),
				size,
			});

		let toplevel_info = self
			.toplevel
			.lock()
			.as_ref()
			.and_then(|toplevel| toplevel.wl_surface().get_toplevel_info())
			.ok_or(eyre!("Internal: no toplevel or ToplevelInfo"))?;

		let children = self
			.children
			.lock()
			.values()
			.filter_map(|v| v.get_child_info())
			.collect();

		Ok(PanelItemInitData {
			cursor,
			toplevel: toplevel_info,
			children,
			pointer_grab: None,
			keyboard_grab: None,
		})
	}
	fn apply_cursor_material(&self, model_part: &Arc<ModelPart>) {
		let Some(surface) = self
			.seat
			.cursor_info_rx
			.borrow()
			.surface
			.clone()
			.and_then(|s| s.upgrade().ok())
		else {
			return;
		};

		let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
			return;
		};
		core_surface.apply_material(model_part);
	}
	fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>) {
		let Some(surface) = self.wl_surface_from_id(&surface) else {
			return;
		};
		let Some(core_surface) = CoreSurface::from_wl_surface(&surface) else {
			return;
		};
		core_surface.apply_material(model_part);
	}

	fn close_toplevel(&self) {
		if let Some(toplevel) = self.toplevel.lock().clone() {
			toplevel.send_close();
		}
	}

	fn auto_size_toplevel(&self) {
		let Some(toplevel) = self.toplevel.lock().clone() else {
			return;
		};
		toplevel.with_pending_state(|s| s.size = None);
		toplevel.send_configure();
	}
	fn set_toplevel_size(&self, size: Vector2<u32>) {
		let Some(toplevel) = self.toplevel.lock().clone() else {
			return;
		};
		toplevel.with_pending_state(|s| {
			s.size = Some((size.x.max(16) as i32, size.y.max(16) as i32).into())
		});
		toplevel.send_pending_configure();
	}
	fn set_toplevel_focused_visuals(&self, focused: bool) {
		let Some(toplevel) = self.toplevel.lock().clone() else {
			return;
		};
		toplevel.with_pending_state(|s| {
			if focused {
				s.states.set(State::Activated);
			} else {
				s.states.unset(State::Activated);
			}
		})
	}

	fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>) {
		let Some(surface) = self.wl_surface_from_id(surface) else {
			return;
		};
		self.seat.pointer_motion(surface, position)
	}
	fn pointer_button(&self, _surface: &SurfaceId, button: u32, pressed: bool) {
		self.seat.pointer_button(button, pressed)
	}
	fn pointer_scroll(
		&self,
		_surface: &SurfaceId,
		scroll_distance: Option<Vector2<f32>>,
		scroll_steps: Option<Vector2<f32>>,
	) {
		self.seat.pointer_scroll(scroll_distance, scroll_steps)
	}

	fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool) {
		let Some(surface) = self.wl_surface_from_id(surface) else {
			return;
		};
		self.seat.keyboard_key(surface, keymap_id, key, pressed)
	}

	fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>) {
		let Some(surface) = self.wl_surface_from_id(surface) else {
			return;
		};
		self.seat.touch_down(surface, id, position)
	}
	fn touch_move(&self, id: u32, position: Vector2<f32>) {
		self.seat.touch_move(id, position)
	}
	fn touch_up(&self, id: u32) {
		self.seat.touch_up(id)
	}
	fn reset_input(&self) {
		self.seat.reset_input()
	}
}
