// 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;

impl<A: App> AppData<A>
{
	/// Read the current text value of the focused widget — preferring
	/// the pending typed-but-not-yet-applied value when present, falling
	/// back to the value snapshot from the per-frame handler list.
	pub( crate ) fn focused_text_value( &self, focus: SurfaceFocus ) -> Option<( usize, String )>
	{
		let idx = self.surface( focus ).focused_idx?;
		let value = if let Some( pending ) = self.surface( focus ).pending_text_values.get( &idx )
		{
			pending.clone()
		} else {
			crate::tree::find_handlers( &self.surface( focus ).frame.widget_rects, idx )?
				.current_value()
				.map( |s| s.to_string() )
				.unwrap_or_default()
		};
		Some( ( idx, value ) )
	}

	/// Move the text cursor one codepoint to the left. With `extend`,
	/// the selection anchor stays put (Shift+Left widens the
	/// selection); without it, the selection is collapsed to the new
	/// cursor position. No-op when no text input is focused.
	pub( crate ) fn handle_cursor_left( &mut self, focus: SurfaceFocus, extend: bool )
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let anchor = self.surface( focus ).frame.selection_anchor.get( &idx ).copied()
			.unwrap_or( cursor ).min( value.len() );
		// Without shift: if a selection exists, collapse to its start.
		if !extend && anchor != cursor
		{
			let s = cursor.min( anchor );
			let ss = self.surface_mut( focus );
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = s;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = s;
			ss.request_redraw();
			return;
		}
		if cursor == 0 { return; }
		let prev_char = value[..cursor].chars().last();
		let step = prev_char.map( |c| c.len_utf8() ).unwrap_or( 1 );
		let new_cursor = cursor.saturating_sub( step );
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
	}

	/// Move the text cursor one codepoint to the right.
	pub( crate ) fn handle_cursor_right( &mut self, focus: SurfaceFocus, extend: bool )
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let anchor = self.surface( focus ).frame.selection_anchor.get( &idx ).copied()
			.unwrap_or( cursor ).min( value.len() );
		if !extend && anchor != cursor
		{
			let e = cursor.max( anchor );
			let ss = self.surface_mut( focus );
			*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = e;
			*ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = e;
			ss.request_redraw();
			return;
		}
		if cursor >= value.len() { return; }
		let next_char = value[cursor..].chars().next();
		let step = next_char.map( |c| c.len_utf8() ).unwrap_or( 1 );
		let new_cursor = ( cursor + step ).min( value.len() );
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
	}

	/// Move the text cursor up one *visual* row. With `extend`, the
	/// selection anchor stays put.
	///
	/// In multiline mode the move follows the soft-wrapped layout — a
	/// long logical line that wraps onto N visual rows lets Up step
	/// through each of those rows in turn, instead of jumping straight
	/// over them to the previous hard `\n`. Single-line inputs and
	/// secure inputs both render as one visual row, so Up always
	/// returns `false` and the runtime falls through to sibling-widget
	/// keyboard navigation.
	pub( crate ) fn handle_cursor_up( &mut self, focus: SurfaceFocus, extend: bool ) -> bool
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return false };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let ( rect, _, multiline, secure, _align, _font_size ) = match self.text_input_geometry( focus, idx )
		{
			Some( g ) => g,
			None      => return false,
		};
		if !multiline || secure { return false; }
		let canvas = match self.surface( focus ).canvas.as_ref() { Some( c ) => c, None => return false };
		let new_cursor = match crate::widget::text_edit::cursor_visual_up( canvas, rect, &value, cursor )
		{
			Some( n ) => n,
			None      => return false,
		};
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
		true
	}

	/// Move the text cursor down one *visual* row. Mirror of
	/// [`Self::handle_cursor_up`].
	pub( crate ) fn handle_cursor_down( &mut self, focus: SurfaceFocus, extend: bool ) -> bool
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return false };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let ( rect, _, multiline, secure, _align, _font_size ) = match self.text_input_geometry( focus, idx )
		{
			Some( g ) => g,
			None      => return false,
		};
		if !multiline || secure { return false; }
		let canvas = match self.surface( focus ).canvas.as_ref() { Some( c ) => c, None => return false };
		let new_cursor = match crate::widget::text_edit::cursor_visual_down( canvas, rect, &value, cursor )
		{
			Some( n ) => n,
			None      => return false,
		};
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
		true
	}

	/// Move the cursor to the start of the current *visual* row (Home
	/// key). On a soft-wrapped logical line that lands at the wrap
	/// point of the row the caret is on — a second Home press from
	/// there does not move further, since the visual row already
	/// starts at that boundary.
	pub( crate ) fn handle_cursor_home( &mut self, focus: SurfaceFocus, extend: bool )
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let new_cursor = match self.text_input_geometry( focus, idx )
		{
			Some( ( rect, _, true, false, _, _ ) ) => match self.surface( focus ).canvas.as_ref()
			{
				Some( canvas ) => crate::widget::text_edit::cursor_visual_home( canvas, rect, &value, cursor ),
				None           => value[..cursor].rfind( '\n' ).map( |p| p + 1 ).unwrap_or( 0 ),
			},
			_ => value[..cursor].rfind( '\n' ).map( |p| p + 1 ).unwrap_or( 0 ),
		};
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
	}

	/// Move the cursor to the end of the current *visual* row (End
	/// key). For a soft-wrapped row that's the wrap point; for a row
	/// terminated by `\n` it's the byte just before the newline.
	pub( crate ) fn handle_cursor_end( &mut self, focus: SurfaceFocus, extend: bool )
	{
		let ( idx, value ) = match self.focused_text_value( focus ) { Some( v ) => v, None => return };
		let cursor = self.surface( focus ).frame.cursor_state.get( &idx ).copied()
			.unwrap_or( value.len() ).min( value.len() );
		let new_cursor = match self.text_input_geometry( focus, idx )
		{
			Some( ( rect, _, true, false, _, _ ) ) => match self.surface( focus ).canvas.as_ref()
			{
				Some( canvas ) => crate::widget::text_edit::cursor_visual_end( canvas, rect, &value, cursor ),
				None           => value[cursor..].find( '\n' ).map( |p| cursor + p ).unwrap_or( value.len() ),
			},
			_ => value[cursor..].find( '\n' ).map( |p| cursor + p ).unwrap_or( value.len() ),
		};
		let ss = self.surface_mut( focus );
		*ss.frame.cursor_state.entry( idx ).or_insert( 0 ) = new_cursor;
		if !extend { *ss.frame.selection_anchor.entry( idx ).or_insert( 0 ) = new_cursor; }
		ss.request_redraw();
	}
}
