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

use std::sync::Arc;
use crate::types::{ Point, Rect };
use crate::render::Canvas;
use super::{
	anchored_overlay, button, carousel, checkbox, container, external, flex,
	image, list_item, pressable, progress_bar, radio, rich_text, scroll, separator,
	slider, spinner, text, text_edit, toggle, viewport, vslider, window_button,
};
use super::handlers::WidgetHandlers;
use super::MapFn;

pub enum Element<Msg: Clone>
{
	Button( button::Button<Msg> ),
	Container( container::Container<Msg> ),
	TextEdit( text_edit::TextEdit<Msg> ),
	Image( image::Image ),
	Column( crate::layout::column::Column<Msg> ),
	Row( crate::layout::row::Row<Msg> ),
	Stack( crate::layout::stack::Stack<Msg> ),
	Text( text::Text ),
	RichText( rich_text::RichText<Msg> ),
	Spacer( crate::layout::spacer::Spacer ),
	Scroll( scroll::Scroll<Msg> ),
	Viewport( viewport::Viewport<Msg> ),
	WrapGrid( crate::layout::wrap_grid::WrapGrid<Msg> ),
	Slider( slider::Slider<Msg> ),
	VSlider( vslider::VSlider<Msg> ),
	Toggle( toggle::Toggle<Msg> ),
	Separator( separator::Separator ),
	ProgressBar( progress_bar::ProgressBar ),
	Checkbox( checkbox::Checkbox<Msg> ),
	Radio( radio::Radio<Msg> ),
	ListItem( list_item::ListItem<Msg> ),
	WindowButton( window_button::WindowButton<Msg> ),
	Pressable( pressable::Pressable<Msg> ),
	Flex( flex::Flex<Msg> ),
	AnchoredOverlay( anchored_overlay::AnchoredOverlay<Msg> ),
	Spinner( spinner::Spinner ),
	External( external::External ),
	Carousel( carousel::Carousel<Msg> ),
}

impl<Msg: Clone> Element<Msg>
{
	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
	{
		match self
		{
			Element::Button( b )      => b.preferred_size( max_width, canvas ),
			Element::TextEdit( t )    => t.preferred_size( max_width, canvas ),
			Element::Image( i )       => i.preferred_size( max_width, canvas ),
			Element::Column( c )      => c.preferred_size( max_width, canvas ),
			Element::Row( r )         => r.preferred_size( max_width, canvas ),
			Element::Stack( s )       => s.preferred_size( max_width, canvas ),
			Element::Text( t )        => t.preferred_size( max_width, canvas ),
			Element::RichText( r )    => r.preferred_size( max_width, canvas ),
			Element::Spacer( s )      => s.preferred_size( canvas ),
			Element::Scroll( s )      => s.preferred_size( max_width, canvas ),
			Element::Viewport( v )    => v.preferred_size( max_width, canvas ),
			Element::WrapGrid( g )    => g.preferred_size( max_width, canvas ),
			Element::Slider( s )      => s.preferred_size( max_width, canvas ),
			Element::VSlider( s )     => s.preferred_size( max_width, canvas ),
			Element::Container( c )   => c.preferred_size( max_width, canvas ),
			Element::Toggle( t )      => t.preferred_size( max_width, canvas ),
			Element::Separator( s )   => s.preferred_size( max_width, canvas ),
			Element::ProgressBar( p ) => p.preferred_size( max_width, canvas ),
			Element::Checkbox( c )    => c.preferred_size( max_width, canvas ),
			Element::Radio( r )       => r.preferred_size( max_width, canvas ),
			Element::ListItem( l )    => l.preferred_size( max_width, canvas ),
			Element::WindowButton( b ) => b.preferred_size( max_width, canvas ),
			Element::Pressable( p )   => p.preferred_size( max_width, canvas ),
			Element::Flex( f )        => f.preferred_size( max_width, canvas ),
			Element::AnchoredOverlay( a ) => a.child.preferred_size( max_width, canvas ),
			Element::Spinner( s )         => s.preferred_size( max_width, canvas ),
			Element::External( e )        => e.preferred_size( max_width ),
			Element::Carousel( c )        => c.preferred_size( max_width, canvas ),
		}
	}

	pub fn draw(
		&self,
		canvas:           &mut Canvas,
		rect:             Rect,
		focused:          bool,
		hovered:          bool,
		pressed:          bool,
		cursor_pos:       usize,
		selection_anchor: usize,
	)
	{
		match self
		{
			Element::Button( b )      => b.draw( canvas, rect, focused, hovered, pressed ),
			Element::TextEdit( t )    => t.draw( canvas, rect, focused, cursor_pos, selection_anchor ),
			Element::Image( i )       => i.draw( canvas, rect ),
			Element::Column( c )      => c.draw( canvas, rect, focused ),
			Element::Row( r )         => r.draw( canvas, rect, focused ),
			Element::Stack( s )       => s.draw( canvas, rect, focused ),
			Element::Text( t )        => t.draw( canvas, rect, focused ),
			Element::RichText( r )    => r.draw( canvas, rect ),
			Element::Spacer( _ )      => {}
			Element::Scroll( _ )      => {}
			Element::Viewport( _ )    => {}
			Element::WrapGrid( _ )    => {}
			Element::Slider( s )      => s.draw( canvas, rect, focused ),
			Element::VSlider( s )     => s.draw( canvas, rect, focused ),
			Element::Container( _ )   => {}
			Element::Toggle( t )      => t.draw( canvas, rect, focused ),
			Element::Separator( s )   => s.draw( canvas, rect ),
			Element::ProgressBar( p ) => p.draw( canvas, rect ),
			Element::Checkbox( c )    => c.draw( canvas, rect, focused ),
			Element::Radio( r )       => r.draw( canvas, rect, focused ),
			Element::ListItem( l )    => l.draw( canvas, rect, focused, hovered, pressed ),
			Element::WindowButton( b ) => b.draw( canvas, rect, focused, hovered, pressed ),
			Element::Pressable( _ )   => {}
			Element::Flex( _ )        => {}
			Element::AnchoredOverlay( _ ) => {}
			Element::Spinner( s )         => s.draw( canvas, rect ),
			Element::External( e )        => e.draw( canvas, rect ),
			Element::Carousel( _ )        => {}
		}
	}

	pub fn hit_test( &self, rect: Rect, pos: Point ) -> bool
	{
		rect.contains( pos )
	}

	/// Bounding box of every pixel this widget may paint given `rect` as its
	/// layout rect. Must enclose the `draw` output in every possible state
	/// (hover, focus, press). Widgets that paint outside their layout rect
	/// (hover halos, focus rings, drop shadows…) must override this; the
	/// default returns `rect` unchanged.
	///
	/// Used by the partial-redraw path to invalidate exactly the pixels that
	/// might change when a widget transitions in/out of a state.
	pub( crate ) fn paint_bounds( &self, rect: Rect ) -> Rect
	{
		match self
		{
			Element::Button( b )   => b.paint_bounds( rect ),
			Element::Toggle( t )   => t.paint_bounds( rect ),
			Element::Radio( r )    => r.paint_bounds( rect ),
			Element::Checkbox( c ) => c.paint_bounds( rect ),
			Element::TextEdit( e ) => e.paint_bounds( rect ),
			Element::ListItem( l ) => l.paint_bounds( rect ),
			Element::WindowButton( b ) => b.paint_bounds( rect ),
			Element::Slider( s )   => s.paint_bounds( rect ),
			Element::VSlider( s )  => s.paint_bounds( rect ),
			_                      => rect,
		}
	}

	/// Whether the widget should be included in the per-surface
	/// `widget_rects` list — i.e. whether pointer / touch hit testing must be
	/// able to land on it. This is the predicate that gates the layout pass's
	/// push to `DrawCtx::widget_rects` in the draw pass.
	///
	/// Defaults to [`Self::is_focusable`] for every widget that takes keyboard
	/// focus (those are interactive by definition). Widgets that are
	/// click/touch-only without taking keyboard focus — currently
	/// [`Element::WindowButton`] — opt in here without opting in to the Tab
	/// cycle.
	pub fn is_interactive( &self ) -> bool
	{
		match self
		{
			// Always hit-testable regardless of `focusable`. Lets callers
			// opt out of keyboard focus (no Tab target, no lingering focus
			// ring after a press) while keeping clicks / taps working.
			Element::Button( _ ) | Element::WindowButton( _ ) => true,
			// Pressable wrappers participate in hit testing only when they
			// carry a handler — a no-op pressable is invisible to input.
			Element::Pressable( p ) => p.has_handler(),
			_                                                 => self.is_focusable(),
		}
	}

	/// Whether the widget participates in the Tab / Shift+Tab focus cycle.
	/// Snapshotted onto [`super::LaidOutWidget::keyboard_focusable`] at layout time so
	/// `next_focusable_index` can iterate without the [`Element`] tree.
	///
	/// Hit-testable chrome that should *not* steal keyboard focus from window
	/// content (e.g. [`Element::WindowButton`]) returns `false` here while
	/// still returning `true` from [`Self::is_interactive`].
	pub fn is_focusable( &self ) -> bool
	{
		match self
		{
			Element::Button( b ) => b.focusable,
			Element::TextEdit( t ) => !t.read_only,
			Element::Slider( _ ) | Element::VSlider( _ ) => true,
			Element::Toggle( _ ) | Element::Checkbox( _ ) | Element::Radio( _ ) | Element::ListItem( _ ) => true,
			Element::WindowButton( b ) => b.focusable,
			_ => false,
		}
	}

	pub fn is_text_input( &self ) -> bool
	{
		matches!( self, Element::TextEdit( t ) if !t.read_only )
	}

	/// Cursor shape to display while the pointer is over this widget.
	/// Per-widget defaults match desktop conventions:
	///
	/// * Text inputs → `Text` (I-beam)
	/// * Clickables (Button, Pressable, Toggle, Checkbox, Radio,
	///   ListItem, WindowButton) → `Pointer` (hand)
	/// * Anything else → `Default`
	///
	/// Widgets that carry a per-instance override (set via the
	/// `.cursor( shape )` builder) return the override; otherwise they
	/// return their type-based default. The runtime snapshots this onto
	/// [`super::LaidOutWidget::cursor`] at layout time and dispatches the
	/// shape via `wp_cursor_shape_v1` on hover transitions.
	pub fn cursor_shape( &self ) -> crate::types::CursorShape
	{
		use crate::types::CursorShape::*;
		match self
		{
			Element::TextEdit( t )    => t.cursor.unwrap_or( Text ),
			Element::Button( b )      => b.cursor.unwrap_or( Pointer ),
			Element::Pressable( p )   => p.cursor.unwrap_or( Pointer ),
			Element::Toggle( _ )      => Pointer,
			Element::Checkbox( _ )    => Pointer,
			Element::Radio( _ )       => Pointer,
			Element::ListItem( _ )    => Pointer,
			Element::WindowButton( _ ) => Pointer,
			// Sliders read as buttons on hover (`Pointer`) and as
			// `Grabbing` during a drag — the runtime swaps to
			// `Grabbing` itself when `gesture.dragging_slider` is
			// set, so the static value here is just the hover state.
			Element::Slider( _ )      => Pointer,
			Element::VSlider( _ )     => Pointer,
			_                         => Default,
		}
	}

	/// Add an arm here to opt a widget kind into the auto-tooltip flow.
	pub fn tooltip( &self ) -> Option<&str>
	{
		match self
		{
			Element::Button( b ) => b.tooltip.as_deref(),
			_                    => None,
		}
	}

	/// Snapshot the widget's callbacks/value into a [`WidgetHandlers`] for
	/// O(1) dispatch later. Called once per focusable leaf during the layout
	/// pass; the handlers are then stored alongside the rect in
	/// [`super::LaidOutWidget`] so input handlers don't need the [`Element`] tree.
	///
	/// Containers and layouts that delegate to children return
	/// [`WidgetHandlers::None`] — only leaf widgets actually carry payload.
	pub( crate ) fn accessible_label( &self ) -> Option<String>
	{
		match self
		{
			Element::Button( b ) => match &b.content
			{
				super::button::ButtonContent::Text( s ) => Some( s.clone() ),
				super::button::ButtonContent::Icon { .. } => b.tooltip.clone(),
			},
			Element::Toggle( t )       => t.label.clone(),
			Element::Checkbox( c )     => c.label.clone(),
			Element::Radio( r )        => r.label.clone(),
			Element::ListItem( l )     => Some( l.label.clone() ).filter( |s| !s.is_empty() ),
			Element::WindowButton( _ ) => None,
			Element::Text( t )         => Some( t.content.clone() ).filter( |s| !s.is_empty() ),
			Element::RichText( r )     => Some( r.content.clone() ).filter( |s| !s.is_empty() ),
			_                          => None,
		}
	}

	pub( crate ) fn handlers( &self ) -> WidgetHandlers<Msg>
	{
		match self
		{
			Element::Button( b )   => WidgetHandlers::Button
			{
				on_press:      b.on_press.clone(),
				on_long_press: b.on_long_press.clone(),
				on_drag_start: b.on_drag_start.clone(),
				on_escape:     None,
				repeating:     b.repeating,
			},
			Element::Pressable( p ) => WidgetHandlers::Button
			{
				on_press:      p.on_press.clone(),
				on_long_press: p.on_long_press.clone(),
				on_drag_start: p.on_drag_start.clone(),
				on_escape:     p.on_escape.clone(),
				repeating:     false,
			},
			Element::Toggle( t )   => WidgetHandlers::Toggle   { on_toggle: t.on_toggle.clone(), value: t.value },
			Element::Checkbox( c ) => WidgetHandlers::Checkbox { on_toggle: c.on_toggle.clone(), value: c.checked },
			Element::Radio( r )    => WidgetHandlers::Radio    { on_select: r.on_select.clone(), selected: r.selected },
			Element::ListItem( l ) => WidgetHandlers::ListItem { on_press:  l.on_press.clone()  },
			Element::WindowButton( b ) => WidgetHandlers::WindowButton { on_press: b.on_press.clone() },
			Element::TextEdit( t ) =>
			{
				WidgetHandlers::TextEdit
				{
					value:               t.value.clone(),
					on_change:           t.on_change.clone(),
					on_submit:           t.on_submit.clone(),
					// `secure` here drives memory wipe-on-drop and
					// the IME bypass — so any password field (with
					// or without a toggle) opts in, regardless of
					// the user's current visibility choice.
					secure:              t.secure || t.password_toggle.is_some(),
					multiline:           t.is_multiline(),
					align:               t.align,
					font_size:           t.font_size,
					select_on_focus:     t.select_on_focus,
					password_toggle_msg: t.password_toggle.as_ref().map( |( _, m )| m.clone() ),
				}
			}
			Element::Slider( s ) =>
			{
				WidgetHandlers::Slider
				{
					on_change:  s.on_change.clone(),
					on_release: s.on_release.clone(),
					axis:       slider::SliderAxis::Horizontal,
					value:      s.value,
					// Design-px fallback; the layout pass overwrites this with
					// the widget-scaling-resolved size via `set_slider_thumb_px`.
					thumb_px:   slider::thumb_design_px(),
				}
			}
			Element::VSlider( s ) =>
			{
				WidgetHandlers::Slider
				{
					on_change:  s.on_change.clone(),
					on_release: s.on_release.clone(),
					axis:       slider::SliderAxis::Vertical,
					value:      s.value,
					// The vertical axis maps against the full rect height and
					// takes no thumb inset, so this is unused.
					thumb_px:   0.0,
				}
			}
			_ => WidgetHandlers::None,
		}
	}
}

impl<Msg: Clone + 'static> Element<Msg>
{
	/// Re-tag every message a sub-view emits.
	///
	/// Walks the tree once and rewrites every per-leaf message store —
	/// `Button::on_press`, `Slider::on_change`, the children of
	/// `Column`/`Row`/`Stack`/`WrapGrid`, and so on — so the returned
	/// `Element<U>` no longer references `Msg`. The standard Elm /
	/// `iced` shape: a sub-view defined as `fn view( …) -> Element<SubMsg>`
	/// can be embedded inside a parent that produces `Element<AppMsg>`
	/// by calling `.map( AppMsg::Sub )`.
	///
	/// Cost: `O( leaves )` allocations for the closure-wrapping in the
	/// `Arc<dyn Fn(...)>` callbacks (`text_edit`, `slider`, `vslider`),
	/// and the closure itself runs an extra indirect call per emitted
	/// message — both per-`map`-layer. Trees built fresh every frame
	/// (the typical case) absorb this in the same allocator pressure
	/// `view()` already produces, so the overhead is in the noise.
	///
	/// ```rust,no_run
	/// # use ltk::{ button, text, Element };
	/// # #[ derive( Clone ) ] enum SubMsg { Save }
	/// # #[ derive( Clone ) ] enum AppMsg { Sub( SubMsg ) }
	/// # fn sub_view() -> Element<SubMsg> {
	/// #     button( "Save" ).on_press( SubMsg::Save ).into()
	/// # }
	/// # fn _ex() -> Element<AppMsg> {
	/// sub_view().map( AppMsg::Sub )
	/// # }
	/// ```
	pub fn map<U, F>( self, f: F ) -> Element<U>
	where
		U: Clone + 'static,
		F: Fn( Msg ) -> U + 'static,
	{
		let f: MapFn<Msg, U> = Arc::new( f );
		self.map_arc( &f )
	}

	pub( crate ) fn map_arc<U>( self, f: &MapFn<Msg, U> ) -> Element<U>
	where
		U: Clone + 'static,
	{
		match self
		{
			Element::Button( b )          => Element::Button( b.map_msg( f ) ),
			Element::Container( c )       => Element::Container( c.map_msg( f ) ),
			Element::TextEdit( t )        => Element::TextEdit( t.map_msg( f ) ),
			Element::Image( i )           => Element::Image( i ),
			Element::Column( c )          => Element::Column( c.map_msg( f ) ),
			Element::Row( r )             => Element::Row( r.map_msg( f ) ),
			Element::Stack( s )           => Element::Stack( s.map_msg( f ) ),
			Element::Text( t )            => Element::Text( t ),
			Element::RichText( r )        => Element::RichText( r.map_msg( f ) ),
			Element::Spacer( s )          => Element::Spacer( s ),
			Element::Scroll( s )          => Element::Scroll( s.map_msg( f ) ),
			Element::Viewport( v )        => Element::Viewport( v.map_msg( f ) ),
			Element::WrapGrid( g )        => Element::WrapGrid( g.map_msg( f ) ),
			Element::Slider( s )          => Element::Slider( s.map_msg( f ) ),
			Element::VSlider( s )         => Element::VSlider( s.map_msg( f ) ),
			Element::Toggle( t )          => Element::Toggle( t.map_msg( f ) ),
			Element::Separator( s )       => Element::Separator( s ),
			Element::ProgressBar( p )     => Element::ProgressBar( p ),
			Element::Checkbox( c )        => Element::Checkbox( c.map_msg( f ) ),
			Element::Radio( r )           => Element::Radio( r.map_msg( f ) ),
			Element::ListItem( l )        => Element::ListItem( l.map_msg( f ) ),
			Element::WindowButton( b )    => Element::WindowButton( b.map_msg( f ) ),
			Element::Pressable( p )       => Element::Pressable( p.map_msg( f ) ),
			Element::Flex( fx )           => Element::Flex( fx.map_msg( f ) ),
			Element::AnchoredOverlay( a ) => Element::AnchoredOverlay( a.map_msg( f ) ),
			Element::Spinner( s )         => Element::Spinner( s ),
			Element::External( e )        => Element::External( e ),
			Element::Carousel( c )        => Element::Carousel( c.map_msg( f ) ),
		}
	}
}

impl<Msg: Clone> From<container::Container<Msg>> for Element<Msg>
{
	fn from( c: container::Container<Msg> ) -> Self
	{
		Element::Container( c )
	}
}

impl<Msg: Clone> From<button::Button<Msg>> for Element<Msg>
{
	fn from( b: button::Button<Msg> ) -> Self
	{
		Element::Button( b )
	}
}

impl<Msg: Clone> From<text_edit::TextEdit<Msg>> for Element<Msg>
{
	fn from( t: text_edit::TextEdit<Msg> ) -> Self
	{
		Element::TextEdit( t )
	}
}

impl<Msg: Clone> From<image::Image> for Element<Msg>
{
	fn from( i: image::Image ) -> Self
	{
		Element::Image( i )
	}
}

impl<Msg: Clone> From<external::External> for Element<Msg>
{
	fn from( e: external::External ) -> Self
	{
		Element::External( e )
	}
}

impl<Msg: Clone> From<crate::layout::column::Column<Msg>> for Element<Msg>
{
	fn from( c: crate::layout::column::Column<Msg> ) -> Self
	{
		Element::Column( c )
	}
}

impl<Msg: Clone> From<crate::layout::row::Row<Msg>> for Element<Msg>
{
	fn from( r: crate::layout::row::Row<Msg> ) -> Self
	{
		Element::Row( r )
	}
}

impl<Msg: Clone> From<crate::layout::stack::Stack<Msg>> for Element<Msg>
{
	fn from( s: crate::layout::stack::Stack<Msg> ) -> Self
	{
		Element::Stack( s )
	}
}
