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

use crate::types::Rect;
use crate::render::Canvas;
use crate::widget::Element;

/// Horizontal alignment of a child within a [`Stack`] rect.
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub enum HAlign
{
	/// Align to the left edge.
	Start,
	/// Center horizontally.
	Center,
	/// Align to the right edge.
	End,
	/// Stretch to fill the full width.
	Fill,
}

/// Vertical alignment of a child within a [`Stack`] rect.
#[ derive( Debug, Clone, Copy, PartialEq ) ]
pub enum VAlign
{
	/// Align to the top edge.
	Top,
	/// Center vertically.
	Center,
	/// Align to the bottom edge.
	Bottom,
	/// Stretch to fill the full height.
	Fill,
}

/// A layout that draws all its children stacked on top of each other.
/// Each child can be positioned within the Stack rect via [`HAlign`]/[`VAlign`].
///
/// Useful for overlaying a foreground widget on top of a background image:
///
/// ```rust,no_run
/// # use std::sync::Arc;
/// # use ltk::{ column, img_widget, stack, text, Element, HAlign, VAlign };
/// # #[ derive( Clone ) ] enum Msg {}
/// # fn _ex( bg_rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> Element<Msg> {
/// stack()
///     .push( img_widget( bg_rgba, w, h ) )
///     .push_aligned( column().push( text( "Bottom right" ) ), HAlign::End, VAlign::Bottom )
/// .into()
/// # }
/// ```
pub struct Stack<Msg: Clone>
{
	/// Children with their alignment, margin, and extra `(x, y)` translation
	/// applied after alignment. Drawn in order — last child is on top. The 7th
	/// `Option<Rect>` overrides alignment/sizing with an exact rect (see
	/// [`push_placed`](Self::push_placed)); the 8th clips the child's draw to a
	/// rect (Android clipChildren — see [`push_placed_clipped`](Self::push_placed_clipped)).
	pub( crate ) children: Vec<( Element<Msg>, HAlign, VAlign, f32, f32, f32, Option<Rect>, Option<Rect> )>,
	/// When `true`, [`preferred_size`](Self::preferred_size) reports the max of
	/// children's intrinsic widths and heights instead of `(max_width, tallest)`.
	pub( crate ) fit_content: bool,
}

impl<Msg: Clone> Stack<Msg>
{
	/// Create an empty stack.
	pub fn new() -> Self
	{
		Self { children: Vec::new(), fit_content: false }
	}

	/// Enable [`Self::fit_content`].
	pub fn fit_content( mut self ) -> Self
	{
		self.fit_content = true;
		self
	}

	/// Append a child that fills the entire Stack rect (Android FrameLayout default).
	pub fn push( self, e: impl Into<Element<Msg>> ) -> Self
	{
		self.push_aligned_margin( e, HAlign::Fill, VAlign::Fill, 0.0 )
	}

	/// Append a child with explicit horizontal and vertical alignment.
	pub fn push_aligned(
		self,
		e:       impl Into<Element<Msg>>,
		h_align: HAlign,
		v_align: VAlign,
	) -> Self
	{
		self.push_aligned_margin( e, h_align, v_align, 0.0 )
	}

	/// Append a child with alignment and a uniform margin (inset from the Stack edges).
	pub fn push_aligned_margin(
		mut self,
		e:       impl Into<Element<Msg>>,
		h_align: HAlign,
		v_align: VAlign,
		margin:  f32,
	) -> Self
	{
		self.children.push( ( e.into(), h_align, v_align, margin, 0.0, 0.0, None, None ) );
		self
	}

	/// Append a child with alignment plus an extra `(x, y)` translation in
	/// logical pixels. Useful when a child needs to shift outside the normal
	/// alignment grid without giving up the margin or alignment shorthand.
	/// Positive `x` / `y` move the child right / down.
	pub fn push_translated(
		mut self,
		e:        impl Into<Element<Msg>>,
		h_align:  HAlign,
		v_align:  VAlign,
		offset_x: f32,
		offset_y: f32,
	) -> Self
	{
		self.children.push( ( e.into(), h_align, v_align, 0.0, offset_x, offset_y, None, None ) );
		self
	}

	/// Append a child placed at an exact `rect` (in the Stack's coordinate space),
	/// bypassing alignment and intrinsic sizing. This is what hosts a view tree
	/// whose geometry is computed elsewhere — e.g. Android's measure/layout pass,
	/// which yields absolute rects per view.
	pub fn push_placed(
		mut self,
		e:    impl Into<Element<Msg>>,
		rect: Rect,
	) -> Self
	{
		self.children.push( ( e.into(), HAlign::Start, VAlign::Top, 0.0, 0.0, 0.0, Some( rect ), None ) );
		self
	}

	/// Like [`push_placed`](Self::push_placed) but clips the child's drawing to
	/// `clip` (in the Stack's coordinate space). Mirrors Android's clipChildren:
	/// content that overflows the clip — e.g. a scrolled list row reaching above
	/// the list, or an inner card past a rounded bubble — is not painted.
	pub fn push_placed_clipped(
		mut self,
		e:    impl Into<Element<Msg>>,
		rect: Rect,
		clip: Rect,
	) -> Self
	{
		self.children.push( ( e.into(), HAlign::Start, VAlign::Top, 0.0, 0.0, 0.0, Some( rect ), Some( clip ) ) );
		self
	}

	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
	{
		if self.fit_content
		{
			let content_w = self.children.iter()
				.map( |( c, .. )| match c
				{
					Element::Spacer( s )      => s.resolved_width( canvas ).unwrap_or( 0.0 ),
					Element::Separator( _ )   => 0.0,
					Element::Scroll( _ )      => 0.0,
					Element::ProgressBar( _ ) => 0.0,
					Element::Slider( _ )      => 0.0,
					Element::TextEdit( t )    => if t.fixed_width.is_some()
					{
						t.preferred_size( max_width, canvas ).0
					} else { 0.0 },
					other => other.preferred_size( max_width, canvas ).0,
				} )
				.fold( 0.0_f32, f32::max );
			let max_h = self.children.iter()
				.map( |( c, .. )| c.preferred_size( max_width, canvas ).1 )
				.fold( 0.0_f32, f32::max );
			return ( content_w.min( max_width ), max_h );
		}

		let max_h = self.children.iter()
			.map( |( c, .. )| c.preferred_size( max_width, canvas ).1 )
			.fold( 0.0_f32, f32::max );
		( max_width, max_h )
	}

	/// Return `(rect, child_index)` pairs, computing each child's rect from its alignment.
	pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
	{
		self.children.iter().enumerate().map( |( i, ( child, h_align, v_align, margin, ox, oy, placed, .. ) )|
		{
			if let Some( p ) = placed
			{
				return ( Rect { x: rect.x + p.x, y: rect.y + p.y, width: p.width, height: p.height }, i );
			}

			let inner_w = ( rect.width  - margin * 2.0 ).max( 0.0 );
			let inner_h = ( rect.height - margin * 2.0 ).max( 0.0 );
			let ( pref_w, pref_h ) = child.preferred_size( inner_w, canvas );

			let ( x, width ) = match h_align
			{
				HAlign::Start  => ( rect.x + margin, pref_w ),
				HAlign::Center => ( rect.x + ( rect.width - pref_w ) / 2.0, pref_w ),
				HAlign::End    => ( rect.x + rect.width  - pref_w - margin, pref_w ),
				HAlign::Fill   => ( rect.x + margin, inner_w ),
			};

			let ( y, height ) = match v_align
			{
				VAlign::Top    => ( rect.y + margin, pref_h ),
				VAlign::Center => ( rect.y + ( rect.height - pref_h ) / 2.0, pref_h ),
				VAlign::Bottom => ( rect.y + rect.height - pref_h - margin, pref_h ),
				VAlign::Fill   => ( rect.y + margin, inner_h ),
			};

			( Rect { x: x + ox, y: y + oy, width, height }, i )
		} ).collect()
	}

	/// No-op — children are drawn directly by the event loop during layout.
	pub fn draw( &self, _canvas: &mut Canvas, _rect: Rect, _focused: bool ) {}

	pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> Stack<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		Stack
		{
			children: self.children.into_iter()
				.map( |( child, ha, va, margin, ox, oy, placed, clip )|
					( child.map_arc( f ), ha, va, margin, ox, oy, placed, clip ) )
				.collect(),
			fit_content: self.fit_content,
		}
	}
}

impl<Msg: Clone> Default for Stack<Msg>
{
	fn default() -> Self
	{
		Self::new()
	}
}

/// Create an empty [`Stack`].
pub fn stack<Msg: Clone>() -> Stack<Msg>
{
	Stack::new()
}
