// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

use smithay_client_toolkit::seat::pointer::{ PointerEvent, PointerEventKind };
use smithay_client_toolkit::reexports::client::
{
	protocol::wl_pointer::WlPointer,
	Connection, QueueHandle,
};

use crate::app::App;
use crate::event_loop::{ AppData, SurfaceFocus };
use crate::tree::{ find_handlers, find_widget_at };
use crate::widget::WidgetHandlers;

impl<A: App> AppData<A>
{
	pub( super ) fn on_pointer_right_click(
		&mut self,
		_conn:    &Connection,
		qh:       &QueueHandle<Self>,
		_pointer: &WlPointer,
		event:    &PointerEvent,
	)
	{
		let focus = self.focus_for_surface( &event.surface )
			.unwrap_or( SurfaceFocus::Main );
		// Right-click: the desktop equivalent of a touch
		// long-press. Three cases on the press target:
		//
		// * Widget with `on_long_press` (Button or
		//   Pressable that opted in) — fire the message.
		//   The drag-arm slot (`on_drag_start`) is NOT
		//   consumed: right-click never enters drag mode,
		//   so an icon's context menu opens but no drag
		//   is armed.
		// * TextEdit with no user `on_long_press` — open
		//   the built-in Copy / Cut / Paste menu near the
		//   click. Selection is preserved (we deliberately
		//   skip `handle_text_pointer_down`) so the common
		//   "select text → right-click → Copy" flow works.
		// * Anywhere else — dismiss any already-open menu.
		let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 );
		self.pointer_pos = pos;
		self.app.on_pointer_move( pos.x, pos.y );
		let hit_idx = find_widget_at( &self.surface( focus ).frame.widget_rects, pos );
		let lp_msg = hit_idx.and_then( |idx|
			find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.long_press_msg() ) );
		if let Some( msg ) = lp_msg
		{
			self.pending_msgs.push( msg );
			self.surface_mut( focus ).request_redraw();
		} else {
			let is_text = hit_idx.and_then( |idx|
				find_handlers( &self.surface( focus ).frame.widget_rects, idx )
					.map( |h| h.is_text_input() ) ).unwrap_or( false );
			if is_text
			{
				let idx = hit_idx.unwrap();
				if self.surface( focus ).focused_idx != Some( idx )
				{
					self.set_focus( focus, hit_idx, qh );
				}
				self.show_context_menu( focus, idx, pos );
			} else {
				self.hide_context_menu( focus );
			}
		}
	}

	pub( super ) fn on_pointer_left_press(
		&mut self,
		_conn:    &Connection,
		qh:       &QueueHandle<Self>,
		_pointer: &WlPointer,
		event:    &PointerEvent,
	)
	{
		let focus = self.focus_for_surface( &event.surface )
			.unwrap_or( SurfaceFocus::Main );
		let serial = if let PointerEventKind::Press { serial, .. } = event.kind
		{
			serial
		} else {
			return;
		};
		self.last_pointer_serial = serial;
		self.last_input_serial   = serial;
		self.cancel_tooltip();
		let pos = self.surface( focus ).to_physical( event.position.0, event.position.1 );
		self.pointer_pos = pos;
		self.app.on_pointer_move( pos.x, pos.y );
		if matches!( focus, SurfaceFocus::Main ) && !self.overlays.is_empty()
		{
			self.dismiss_main_outside_popups( pos );
		}

		// Built-in context menu intercepts the press
		// before the regular gesture machine. Either an
		// item activates (Copy / Cut / Paste) or the
		// click is outside the menu and dismisses it —
		// in both cases we consume the event.
		if self.surface( focus ).context_menu.is_some()
		{
			if self.handle_context_menu_press( focus, pos )
			{
				return;
			}
		}

		// Client-side title bar interaction — pointer-only
		// (touch never hits a titlebar; layer-shell surfaces
		// have titlebar_height == 0).
		let sf   = self.surface( focus ).scale_factor.max( 1 ) as f32;
		let tb_h = self.surface( focus ).titlebar_height * sf;
		if tb_h > 0.0 && pos.y < tb_h
		{
			let close_rect = self.surface( focus ).titlebar_close_rect;
			if close_rect.contains( pos )
			{
				if self.app.on_close_requested()
				{
					self.exit_requested = true;
				}
				return;
			}
		}

		// Resize-edge interception. Wins over the titlebar
		// drag-move (top-left / top-right corners overlap
		// the titlebar) and over the gesture machine.
		if let Some( edge ) = self.resize_edge_under_pointer( focus )
		{
			if let crate::event_loop::SurfaceKind::Window( ref window ) = self.main.surface
			{
				let seats: Vec<_> = self.seat_state.seats().collect();
				if let Some( seat ) = seats.into_iter().next()
				{
					window.resize( &seat, serial, edge );
				}
			}
			return;
		}

		if tb_h > 0.0 && pos.y < tb_h
		{
			// Drag to move the window.
			if matches!( focus, SurfaceFocus::Main )
			{
				if let crate::event_loop::SurfaceKind::Window( ref window ) = self.main.surface
				{
					let seats: Vec<_> = self.seat_state.seats().collect();
					if let Some( seat ) = seats.into_iter().next()
					{
						window.move_( &seat, serial );
					}
				}
			}
			return;
		}

		// Past every chrome interception — surface the
		// press to the app so embeddings (e.g. an
		// embedded WPE WebView) see real button-down
		// events that were not consumed by the window
		// frame.
		self.app.on_pointer_button( pos.x, pos.y, true );

		let outcome =
		{
			let ss = self.surface_mut( focus );
			let result = ss.gesture.on_press( pos, &ss.frame.widget_rects, &ss.frame.scroll_rects );
			// Mark this gesture as mouse-driven so the
			// gesture machine's 6 px stray-cancel skips
			// the drag-start / long-press slots — mouse
			// motion is intentional and should never
			// drop the candidate before the pointer-side
			// promotion at 24 px gets to fire.
			ss.gesture.mouse_press = true;
			ss.needs_redraw = true;
			result
		};
		self.set_focus( focus, outcome.hit_idx, qh );
		if let Some( msg ) = outcome.initial_slider_msg
		{
			self.pending_msgs.push( msg );
		}
		// Press-and-hold repeat: when the hit is a button
		// that opted into `.repeating( true )`, fire the
		// `on_press` message immediately and arm the
		// repeat timer. The release handler in
		// `gesture.rs` knows to suppress the regular tap-
		// on-release fire for repeating buttons so a
		// quick click still counts as exactly one press.
		// The timer re-reads `on_press` from the live
		// widget tree on every tick — see
		// `start_button_repeat` for why.
		if let Some( idx ) = outcome.hit_idx
		{
			let immediate = {
				let handlers = find_handlers( &self.surface( focus ).frame.widget_rects, idx );
				if matches!( handlers, Some( WidgetHandlers::Button { repeating: true, .. } ) )
				{
					handlers.and_then( |h| h.press_msg() )
				} else { None }
			};
			if let Some( msg ) = immediate
			{
				self.pending_msgs.push( msg );
				self.start_button_repeat( focus, idx );
			}
		}
		// Click-to-position the text cursor when the press
		// landed on a TextEdit. `set_focus` above moves the
		// cursor to the end of the value; this overrides
		// it with the byte offset under the pointer and
		// collapses the selection there so a subsequent
		// drag widens the selection from the click point.
		//
		// Double-click on a TextEdit selects the word
		// under the cursor instead of just positioning.
		if let Some( idx ) = outcome.hit_idx
		{
			let is_text = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.map( |h| h.is_text_input() ).unwrap_or( false );
			if is_text
			{
				// Eye icon hit on a password field
				// short-circuits the text-edit
				// dispatch — fire the toggle msg and
				// skip cursor placement.
				if self.handle_password_toggle_press( focus, idx, pos )
				{
					let _ = self.note_press_for_double_click( pos );
				}
				else
				{
					let is_double = self.note_press_for_double_click( pos );
					if is_double
					{
						self.handle_text_select_word( focus, idx, pos );
					} else {
						self.handle_text_pointer_down( focus, idx, pos );
					}
				}
			} else {
				let _ = self.note_press_for_double_click( pos );
			}
		} else {
			let _ = self.note_press_for_double_click( pos );
		}
		// Slider press → drag may have started; refresh
		// the cursor shape so it switches to `Grabbing`.
		self.dispatch_cursor_shape( focus );
	}
}
