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

use crate::app::App;
use crate::event_loop::app_data::AppData;
use crate::event_loop::surface::SurfaceFocus;
use crate::tree::find_handlers;
use crate::widget::WidgetHandlers;

impl<A: App> AppData<A>
{
	pub( crate ) fn handle_text_insert( &mut self, focus: SurfaceFocus, text: &str )
	{
		let idx = match self.surface( focus ).focused_idx { Some( i ) => i, None => return };

		// If a selection is active, replace it. The deletion path
		// folds anchor=cursor and updates `pending_text_values`, so
		// the rest of this fn keeps the same shape after.
		let _ = self.delete_selection( focus );

		let current_value = if let Some( pending ) = self.surface( focus ).pending_text_values.get( &idx )
		{
			pending.clone()
		} else {
			find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.current_value() )
				.map( |s| s.to_string() )
				.unwrap_or_default()
		};

		// `select_on_focus` fields keep the cursor pinned to the end
		// of the *displayed* value. The pending value the user just
		// typed (e.g. "2") may render shorter than the post-update
		// display value (e.g. "02" after a `format!("{:02}")`
		// normalisation in the app's on_change handler), so anchoring
		// the cursor at "end of pending" would land it mid-string in
		// the next render. `usize::MAX` is a sentinel: every consumer
		// (`draw`, arrow-key handlers, `byte_offset_at`) clamps via
		// `cursor.min( value.len() )`, which resolves to "end of
		// whatever value we eventually render". Without this, typing
		// a second digit would insert into the middle of the
		// normalised string ("0|2" + "3" → "032" → 32 instead of 23).
		let select_on_focus = matches!(
			find_handlers( &self.surface( focus ).frame.widget_rects, idx ),
			Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ),
		);

		let new_value;
		{
			let ss = self.surface_mut( focus );
			let cursor      = ss.frame.cursor_state.entry( idx ).or_insert( current_value.len() );
			let safe_cursor = (*cursor).min( current_value.len() );
			let mut v       = current_value.clone();
			v.insert_str( safe_cursor, text );
			let next_cursor = if select_on_focus { usize::MAX } else { safe_cursor + text.len() };
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = next_cursor;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = next_cursor;
			ss.pending_text_values.insert( idx, v.clone() );
			ss.request_redraw();
			new_value = v;
		}

		let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
			.and_then( |h| h.text_change_msg( &new_value ) );
		if let Some( m ) = msg
		{
			self.pending_msgs.push( m );
		}
	}

	/// Forward delete — the `Delete` (Supr) key. Mirrors
	/// [`Self::handle_backspace`] but removes the character *after*
	/// the cursor instead of before. Selection-aware: if a range is
	/// active it is removed in one step, exactly like backspace.
	pub( crate ) fn handle_delete_forward( &mut self, focus: SurfaceFocus )
	{
		let idx = match self.surface( focus ).focused_idx { Some( i ) => i, None => return };

		if let Some( new_value ) = self.delete_selection( focus )
		{
			let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.text_change_msg( &new_value ) );
			if let Some( m ) = msg { self.pending_msgs.push( m ); }
			return;
		}

		let current_value = if let Some( pending ) = self.surface( focus ).pending_text_values.get( &idx )
		{
			pending.clone()
		} else {
			find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.current_value() )
				.map( |s| s.to_string() )
				.unwrap_or_default()
		};

		let cursor_val = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( current_value.len() );
		let safe_cursor_pre = cursor_val.min( current_value.len() );
		if safe_cursor_pre >= current_value.len() { return; }
		// Width of the char *starting* at the cursor — UTF-8 aware so
		// `é` / `🦀` come out as one keypress.
		let next_char = current_value[safe_cursor_pre..].chars().next();
		let step = match next_char { Some( c ) => c.len_utf8(), None => return };
		let mut new_value = current_value.clone();
		new_value.replace_range( safe_cursor_pre..safe_cursor_pre + step, "" );
		// `select_on_focus` fields keep the cursor at the end of the
		// post-update displayed value via the `usize::MAX` sentinel —
		// see `handle_text_insert` for the reasoning.
		let select_on_focus = matches!(
			find_handlers( &self.surface( focus ).frame.widget_rects, idx ),
			Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ),
		);
		let next_cursor = if select_on_focus { usize::MAX } else { safe_cursor_pre };
		{
			let ss = self.surface_mut( focus );
			// Cursor stays put — the char to its right is gone, so the
			// remaining tail shifts left under it.
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = next_cursor;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = next_cursor;
			ss.pending_text_values.insert( idx, new_value.clone() );
			ss.request_redraw();
		}

		let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
			.and_then( |h| h.text_change_msg( &new_value ) );
		if let Some( m ) = msg
		{
			self.pending_msgs.push( m );
		}
	}

	/// Respond to `zwp_text_input_v3.delete_surrounding_text`. Both
	/// arguments are **UTF-8 byte counts** measured from the cursor.
	/// IMEs (Mozc, Anthy, IBus-pinyin) emit this routinely to commit a
	/// preedit on top of pre-existing surrounding text. The previous
	/// implementation iterated `before_length` calls to `handle_backspace`,
	/// treating the byte count as a number of characters — corrupting
	/// any non-ASCII input. This method does a single bytes-aware
	/// replace_range that snaps to UTF-8 char boundaries.
	pub( crate ) fn handle_delete_surrounding( &mut self, focus: SurfaceFocus, before_bytes: u32, after_bytes: u32 )
	{
		let idx = match self.surface( focus ).focused_idx { Some( i ) => i, None => return };

		let _ = self.delete_selection( focus );

		let current_value = if let Some( pending ) = self.surface( focus ).pending_text_values.get( &idx )
		{
			pending.clone()
		} else {
			find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.current_value() )
				.map( |s| s.to_string() )
				.unwrap_or_default()
		};

		let cursor_val  = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( current_value.len() );
		let safe_cursor = cursor_val.min( current_value.len() );

		// `before_bytes` of UTF-8 walked back from the cursor, snapping
		// to char boundaries — if the byte count lands mid-codepoint we
		// expand outward to swallow the whole codepoint (matches what
		// GTK / Qt do under the same protocol).
		let start_byte =
		{
			let mut pos = safe_cursor;
			for ( i, _ ) in current_value[..safe_cursor].char_indices().rev()
			{
				if ( safe_cursor - pos ) as u32 >= before_bytes { break; }
				pos = i;
			}
			pos
		};

		let end_byte =
		{
			let mut pos = safe_cursor;
			for ( i, ch ) in current_value[safe_cursor..].char_indices()
			{
				if ( pos - safe_cursor ) as u32 >= after_bytes { break; }
				pos = safe_cursor + i + ch.len_utf8();
			}
			pos
		};

		if start_byte >= end_byte { return; }

		let mut new_value = current_value.clone();
		new_value.replace_range( start_byte..end_byte, "" );

		let select_on_focus = matches!(
			find_handlers( &self.surface( focus ).frame.widget_rects, idx ),
			Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ),
		);
		let next_cursor = if select_on_focus { usize::MAX } else { start_byte };

		{
			let ss = self.surface_mut( focus );
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = next_cursor;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = next_cursor;
			ss.pending_text_values.insert( idx, new_value.clone() );
			ss.request_redraw();
		}

		let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
			.and_then( |h| h.text_change_msg( &new_value ) );
		if let Some( m ) = msg
		{
			self.pending_msgs.push( m );
		}
	}

	pub( crate ) fn handle_backspace( &mut self, focus: SurfaceFocus )
	{
		let idx = match self.surface( focus ).focused_idx { Some( i ) => i, None => return };

		// Selection-aware: if a selection is active, backspace just
		// deletes the range. The on_change message is emitted from
		// here so the app sees a single text update.
		if let Some( new_value ) = self.delete_selection( focus )
		{
			let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.text_change_msg( &new_value ) );
			if let Some( m ) = msg { self.pending_msgs.push( m ); }
			return;
		}

		let current_value = if let Some( pending ) = self.surface( focus ).pending_text_values.get( &idx )
		{
			pending.clone()
		} else {
			find_handlers( &self.surface( focus ).frame.widget_rects, idx )
				.and_then( |h| h.current_value() )
				.map( |s| s.to_string() )
				.unwrap_or_default()
		};

		// Scoped block to release the immutable borrow of `self` (via
		// `self.surface( focus )`) before taking a mutable one below.
		let cursor_val = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( current_value.len() );
		if cursor_val == 0 { return; }
		let safe_cursor      = cursor_val.min( current_value.len() );
		let chars: Vec<char> = current_value[..safe_cursor].chars().collect();
		if chars.is_empty() { return; }

		let removed_char  = *chars.last().unwrap();
		let new_cursor    = safe_cursor - removed_char.len_utf8();
		let mut new_value = current_value.clone();
		new_value.remove( new_cursor );
		// `select_on_focus` fields keep the cursor at the end of the
		// post-update displayed value via the `usize::MAX` sentinel —
		// see `handle_text_insert` for the reasoning.
		let select_on_focus = matches!(
			find_handlers( &self.surface( focus ).frame.widget_rects, idx ),
			Some( WidgetHandlers::TextEdit { select_on_focus: true, .. } ),
		);
		let next_cursor = if select_on_focus { usize::MAX } else { new_cursor };
		{
			let ss = self.surface_mut( focus );
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = next_cursor;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = next_cursor;
			ss.pending_text_values.insert( idx, new_value.clone() );
			ss.request_redraw();
		}

		let msg = find_handlers( &self.surface( focus ).frame.widget_rects, idx )
			.and_then( |h| h.text_change_msg( &new_value ) );
		if let Some( m ) = msg
		{
			self.pending_msgs.push( m );
		}
	}
}
