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

use crate::render::Canvas;
use crate::types::{ Length, Rect };
use crate::widget::Element;

/// A grid layout that wraps children into rows of a fixed column count.
///
/// All cells in a row share the same height (the tallest item in that row).
/// Column widths are equal, dividing the available width minus padding and spacing.
///
/// Designed for app-drawer style layouts — combine with [`scroll()`](crate::widget::scroll::scroll)
/// for vertically scrollable grids:
///
/// ```rust,no_run
/// # use std::sync::Arc;
/// # use ltk::{ grid, icon_button, scroll, Element };
/// # #[ derive( Clone ) ] enum Msg { Open( usize ) }
/// # fn _ex( data: Arc<Vec<u8>>, w: u32, h: u32 ) -> Element<Msg> {
/// scroll(
///     grid( 4 )
///         .padding( 16.0 )
///         .spacing( 12.0 )
///         .push( icon_button( data.clone(), w, h ).on_press( Msg::Open( 0 ) ) )
///         .push( icon_button( data, w, h ).on_press( Msg::Open( 1 ) ) )
///         // ...
/// )
/// .into()
/// # }
/// ```
pub struct WrapGrid<Msg: Clone>
{
	/// Child widgets laid out in row-major order.
	pub( crate ) children:        Vec<Element<Msg>>,
	/// Number of columns per row. Ignored when `min_cell_width` is set.
	pub( crate ) columns:         usize,
	/// Adaptive mode: derive the column count from the available width
	/// so every cell is at least this wide. See [`grid_min_cell`].
	pub( crate ) min_cell_width:  Option<Length>,
	/// Upper bound on the derived column count in adaptive mode.
	pub( crate ) max_columns:     Option<usize>,
	/// Horizontal gap between cells.
	pub( crate ) spacing_x:       Length,
	/// Vertical gap between rows.
	pub( crate ) spacing_y:       Length,
	/// Padding on all sides.
	pub( crate ) padding:         Length,
	/// When `true`, a partial last row is centred horizontally within
	/// the grid's content rect instead of being left-aligned.
	pub( crate ) centre_last_row: bool,
}

impl<Msg: Clone> WrapGrid<Msg>
{
	/// Append a child widget to the grid.
	pub fn push( mut self, child: impl Into<Element<Msg>> ) -> Self
	{
		self.children.push( child.into() );
		self
	}

	/// Set both horizontal and vertical gap between cells (default 8.0).
	pub fn spacing( mut self, s: impl Into<Length> ) -> Self
	{
		let s = s.into();
		self.spacing_x = s;
		self.spacing_y = s;
		self
	}

	/// Set only the horizontal gap between cells; leaves vertical spacing untouched.
	pub fn spacing_x( mut self, s: impl Into<Length> ) -> Self
	{
		self.spacing_x = s.into();
		self
	}

	/// Set only the vertical gap between rows; leaves horizontal spacing untouched.
	pub fn spacing_y( mut self, s: impl Into<Length> ) -> Self
	{
		self.spacing_y = s.into();
		self
	}

	/// Set the padding on all sides (default 0.0).
	pub fn padding( mut self, p: impl Into<Length> ) -> Self
	{
		self.padding = p.into();
		self
	}

	/// Centre a partial last row horizontally inside the content rect.
	/// Default is `false` (left-aligned, like other grids).
	pub fn centre_last_row( mut self, yes: bool ) -> Self
	{
		self.centre_last_row = yes;
		self
	}

	/// Cap the column count derived by [`grid_min_cell`] so cells stop
	/// multiplying on very wide surfaces and grow instead. No effect on
	/// a fixed-column [`grid`].
	pub fn max_columns( mut self, n: usize ) -> Self
	{
		self.max_columns = Some( n );
		self
	}

	/// Column count for the given inner width: fixed, or derived from
	/// `min_cell_width` (as many columns as fit at least that wide,
	/// never fewer than one, capped by `max_columns`).
	fn effective_columns( &self, inner_w: f32, sx: f32, canvas: &Canvas ) -> usize
	{
		match self.min_cell_width
		{
			Some( m ) =>
			{
				let m    = canvas.resolve_geom( m ).max( 1.0 );
				let cols = ( ( ( inner_w + sx ) / ( m + sx ) ).floor() as usize ).max( 1 );
				match self.max_columns
				{
					Some( cap ) => cols.min( cap.max( 1 ) ),
					None        => cols,
				}
			}
			None => self.columns,
		}
	}

	fn resolved( &self, canvas: &Canvas ) -> ( f32, f32, f32 )
	{
		(
			canvas.resolve_geom( self.spacing_x ),
			canvas.resolve_geom( self.spacing_y ),
			canvas.resolve_geom( self.padding ),
		)
	}

	/// Compute the preferred size given an available width.
	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
	{
		let ( sx, sy, pad ) = self.resolved( canvas );
		let inner_w   = (max_width - pad * 2.0).max( 0.0 );
		let cols      = self.effective_columns( inner_w, sx, canvas );
		if self.children.is_empty() || cols == 0
		{
			return ( max_width, 0.0 );
		}
		let cell_w    = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
		let row_count = (self.children.len() + cols - 1) / cols;

		let mut total_h = pad * 2.0;
		for row in 0..row_count
		{
			let start = row * cols;
			let end   = (start + cols).min( self.children.len() );
			let row_h = self.children[start..end]
				.iter()
				.map( |c| c.preferred_size( cell_w, canvas ).1 )
				.fold( 0.0_f32, f32::max );
			total_h += row_h;
			if row + 1 < row_count { total_h += sy; }
		}
		( max_width, total_h )
	}

	/// Compute child rects. Returns `(child_rect, index_in_children)` pairs.
	pub fn layout( &self, rect: Rect, canvas: &Canvas ) -> Vec<(Rect, usize)>
	{
		let ( sx, sy, pad ) = self.resolved( canvas );
		let inner_w = (rect.width - pad * 2.0).max( 0.0 );
		let cols    = self.effective_columns( inner_w, sx, canvas );
		if self.children.is_empty() || cols == 0
		{
			return Vec::new();
		}
		let cell_w  = (inner_w - sx * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
		let x0      = rect.x + pad;
		let mut y   = rect.y + pad;

		let row_count = (self.children.len() + cols - 1) / cols;
		let mut out   = Vec::with_capacity( self.children.len() );

		for row in 0..row_count
		{
			let start = row * cols;
			let end   = (start + cols).min( self.children.len() );
			let row_h = self.children[start..end]
				.iter()
				.map( |c| c.preferred_size( cell_w, canvas ).1 )
				.fold( 0.0_f32, f32::max );

			let items_in_row = end - start;
			let row_offset   = if self.centre_last_row && items_in_row < cols
			{
				let missing = (cols - items_in_row) as f32;
				missing * (cell_w + sx) / 2.0
			} else { 0.0 };

			for col in 0..items_in_row
			{
				let x    = x0 + row_offset + col as f32 * (cell_w + sx);
				let crect = Rect { x, y, width: cell_w, height: row_h };
				out.push( ( crect, start + col ) );
			}
			y += row_h + sy;
		}
		out
	}

	pub( crate ) fn map_msg<U>( self, f: &crate::widget::MapFn<Msg, U> ) -> WrapGrid<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		WrapGrid
		{
			children:        self.children.into_iter().map( |c| c.map_arc( f ) ).collect(),
			columns:         self.columns,
			min_cell_width:  self.min_cell_width,
			max_columns:     self.max_columns,
			spacing_x:       self.spacing_x,
			spacing_y:       self.spacing_y,
			padding:         self.padding,
			centre_last_row: self.centre_last_row,
		}
	}
}

impl<Msg: Clone + 'static> From<WrapGrid<Msg>> for Element<Msg>
{
	fn from( g: WrapGrid<Msg> ) -> Self
	{
		Element::WrapGrid( g )
	}
}

#[ cfg( test ) ]
mod tests
{
	use super::*;
	use crate::render::Canvas;
	use crate::layout::spacer::spacer;


	fn canvas() -> Canvas { Canvas::new( 1, 1 ) }

	// Helper: build a grid of N spacer children with the given settings.
	fn spacer_grid( cols: usize, n: usize, spacing: f32, padding: f32 ) -> WrapGrid<()>
	{
		let mut g = grid( cols ).spacing( spacing ).padding( padding );
		for _ in 0..n { g = g.push( spacer() ); }
		g
	}

	// --- preferred_size ---

	#[ test ]
	fn empty_grid_height_is_zero()
	{
		let g: WrapGrid<()> = grid( 4 );
		let ( _, h ) = g.preferred_size( 400.0, &canvas() );
		assert_eq!( h, 0.0 );
	}

	#[ test ]
	fn preferred_width_equals_max_width()
	{
		let g = spacer_grid( 4, 8, 0.0, 0.0 );
		let ( w, _ ) = g.preferred_size( 320.0, &canvas() );
		assert_eq!( w, 320.0 );
	}

	// --- layout: cell widths ---

	#[ test ]
	fn cell_width_no_spacing_no_padding()
	{
		// 400px / 4 cols = 100px each
		let g = spacer_grid( 4, 4, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
		let rects = g.layout( rect, &c );
		assert_eq!( rects.len(), 4 );
		for ( r, _ ) in &rects { assert!( (r.width - 100.0).abs() < 0.01 ); }
	}

	#[ test ]
	fn cell_width_with_spacing()
	{
		// (400 - 3 * 10) / 4 = 370 / 4 = 92.5
		let g = spacer_grid( 4, 4, 10.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
		let rects = g.layout( rect, &c );
		for ( r, _ ) in &rects { assert!( (r.width - 92.5).abs() < 0.01 ); }
	}

	#[ test ]
	fn cell_width_with_padding()
	{
		// inner = 400 - 2*20 = 360; 360 / 4 = 90
		let g = spacer_grid( 4, 4, 0.0, 20.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 200.0 };
		let rects = g.layout( rect, &c );
		for ( r, _ ) in &rects { assert!( (r.width - 90.0).abs() < 0.01 ); }
	}

	// --- layout: child count and indices ---

	#[ test ]
	fn layout_yields_one_rect_per_child()
	{
		let g = spacer_grid( 4, 7, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert_eq!( rects.len(), 7 );
	}

	#[ test ]
	fn layout_indices_are_sequential()
	{
		let g = spacer_grid( 3, 5, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 300.0 };
		let rects = g.layout( rect, &c );
		let indices: Vec<usize> = rects.iter().map( |( _, i )| *i ).collect();
		assert_eq!( indices, vec![ 0, 1, 2, 3, 4 ] );
	}

	// --- layout: column x-positions ---

	#[ test ]
	fn column_x_positions_no_spacing()
	{
		// 300px / 3 cols = 100px each, starting at x=0
		let g = spacer_grid( 3, 3, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 100.0 };
		let rects = g.layout( rect, &c );
		let xs: Vec<f32> = rects.iter().map( |( r, _ )| r.x ).collect();
		assert!( (xs[0] - 0.0).abs() < 0.01 );
		assert!( (xs[1] - 100.0).abs() < 0.01 );
		assert!( (xs[2] - 200.0).abs() < 0.01 );
	}

	#[ test ]
	fn column_x_positions_with_spacing()
	{
		// (300 - 2*10) / 3 = 280/3 ≈ 93.33; x[0]=0, x[1]=103.33, x[2]=206.67
		let g = spacer_grid( 3, 3, 10.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 100.0 };
		let rects = g.layout( rect, &c );
		let cell_w = 280.0_f32 / 3.0;
		let xs: Vec<f32> = rects.iter().map( |( r, _ )| r.x ).collect();
		assert!( (xs[0] - 0.0).abs() < 0.01 );
		assert!( (xs[1] - (cell_w + 10.0)).abs() < 0.01 );
		assert!( (xs[2] - (2.0 * (cell_w + 10.0))).abs() < 0.01 );
	}

	// --- layout: partial last row ---

	#[ test ]
	fn partial_last_row_has_correct_count()
	{
		// 7 children, 4 cols => row 0: 4, row 1: 3.
		let g = spacer_grid( 4, 7, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert_eq!( rects.len(), 7 );
		for ( r, _ ) in &rects[..4] { assert!( r.y.abs() < 0.01 ); }
	}

	// --- layout: rect origin offset ---

	#[ test ]
	fn layout_respects_rect_origin()
	{
		let g = spacer_grid( 2, 2, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 50.0, y: 30.0, width: 200.0, height: 100.0 };
		let rects = g.layout( rect, &c );
		assert!( (rects[0].0.x - 50.0).abs() < 0.01 );
		assert!( (rects[0].0.y - 30.0).abs() < 0.01 );
	}

	// --- layout: centre_last_row ---

	#[ test ]
	fn last_row_centred_when_partial()
	{
		// 3 children, 2 cols => row 0: 2 items, row 1: 1 item centred.
		// cell_w = 200/2 = 100; centred-offset = (2-1)*100/2 = 50.
		let g = spacer_grid( 2, 3, 0.0, 0.0 ).centre_last_row( true );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert!( (rects[2].0.x - 50.0).abs() < 0.01 );
	}

	#[ test ]
	fn centre_last_row_noop_on_full_row()
	{
		// 4 children, 2 cols => both rows full; nothing to centre.
		let g = spacer_grid( 2, 4, 0.0, 0.0 ).centre_last_row( true );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert!( rects[2].0.x.abs() < 0.01 );
		assert!( (rects[3].0.x - 100.0).abs() < 0.01 );
	}

	#[ test ]
	fn centre_last_row_off_by_default()
	{
		// Same case as above but without the flag — last item stays at x=0.
		let g = spacer_grid( 2, 3, 0.0, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 200.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert!( rects[2].0.x.abs() < 0.01 );
	}

	// --- adaptive column count (grid_min_cell) ---

	fn min_cell_grid( min: f32, n: usize, spacing: f32 ) -> WrapGrid<()>
	{
		let mut g = grid_min_cell( min ).spacing( spacing );
		for _ in 0..n { g = g.push( spacer() ); }
		g
	}

	#[ test ]
	fn min_cell_derives_columns_from_width()
	{
		// 400px / min 90 => floor(400/90) = 4 columns, cells 100px.
		let g = min_cell_grid( 90.0, 8, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		for ( r, _ ) in &rects { assert!( ( r.width - 100.0 ).abs() < 0.01 ); }
		// 8 children in 4 columns => index 3 sits in the last column and
		// index 4 wraps back to x = 0 on the next row.
		assert!( ( rects[3].0.x - 300.0 ).abs() < 0.01 );
		assert!( rects[4].0.x.abs() < 0.01 );
	}

	#[ test ]
	fn min_cell_accounts_for_spacing()
	{
		// With spacing 10: floor((300+10)/(90+10)) = 3 columns; the resulting
		// cells ((300 - 2*10)/3 ≈ 93.3) still clear the 90px minimum.
		let g = min_cell_grid( 90.0, 3, 10.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 300.0, height: 100.0 };
		let rects = g.layout( rect, &c );
		assert!( rects.iter().all( |( r, _ )| r.width >= 90.0 ) );
		assert!( ( rects[0].0.y - rects[2].0.y ).abs() < 0.01 );
	}

	#[ test ]
	fn min_cell_never_below_one_column()
	{
		// Narrower than the minimum still lays out a single column:
		// both children sit flush left at the full 80px width.
		let g = min_cell_grid( 120.0, 2, 0.0 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 80.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		assert_eq!( rects.len(), 2 );
		assert!( rects[1].0.x.abs() < 0.01 );
		for ( r, _ ) in &rects { assert!( ( r.width - 80.0 ).abs() < 0.01 ); }
	}

	#[ test ]
	fn max_columns_caps_adaptive_count()
	{
		// 400px / min 90 would give 4; the cap keeps 2 and cells grow to 200.
		let g = min_cell_grid( 90.0, 4, 0.0 ).max_columns( 2 );
		let c = canvas();
		let rect = Rect { x: 0.0, y: 0.0, width: 400.0, height: 400.0 };
		let rects = g.layout( rect, &c );
		for ( r, _ ) in &rects { assert!( ( r.width - 200.0 ).abs() < 0.01 ); }
		// Two columns: index 1 fills the second column, index 2 wraps.
		assert!( ( rects[1].0.x - 200.0 ).abs() < 0.01 );
		assert!( rects[2].0.x.abs() < 0.01 );
	}
}

/// Create a grid layout with the given number of columns.
///
/// Use [`.push()`](WrapGrid::push), [`.spacing()`](WrapGrid::spacing), and
/// [`.padding()`](WrapGrid::padding) to populate and style the grid.
///
/// ```rust,no_run
/// # use ltk::{ button, grid, WrapGrid };
/// # #[ derive( Clone ) ] enum Msg { A }
/// # fn _ex() -> WrapGrid<Msg> {
/// grid( 4 ).padding( 16.0 ).spacing( 8.0 ).push( button( "A" ).on_press( Msg::A ) )
/// # }
/// ```
pub fn grid<Msg: Clone>( columns: usize ) -> WrapGrid<Msg>
{
	WrapGrid
	{
		children:        Vec::new(),
		columns,
		min_cell_width:  None,
		max_columns:     None,
		spacing_x:       Length::px( 8.0 ),
		spacing_y:       Length::px( 8.0 ),
		padding:         Length::px( 0.0 ),
		centre_last_row: false,
	}
}

/// Create an adaptive grid: the column count is derived at layout time
/// from the available width, fitting as many columns as possible while
/// keeping every cell at least `min_cell_width` wide (never fewer than
/// one). Cells then share the width equally, so they range between
/// `min_cell_width` and just under twice it — cap the count with
/// [`max_columns`](WrapGrid::max_columns) to let cells grow instead on
/// very wide surfaces.
///
/// Accepts logical `f32` pixels or any [`Length`] (e.g.
/// `Length::fluid( 96.0 )` for a threshold that scales with the
/// surface).
///
/// ```rust,no_run
/// # use std::sync::Arc;
/// # use ltk::{ grid_min_cell, icon_button, scroll, Element };
/// # #[ derive( Clone ) ] enum Msg { Open( usize ) }
/// # fn _ex( data: Arc<Vec<u8>>, w: u32, h: u32 ) -> Element<Msg> {
/// scroll(
///     grid_min_cell( 96.0 )
///         .max_columns( 8 )
///         .spacing( 12.0 )
///         .push( icon_button( data, w, h ).on_press( Msg::Open( 0 ) ) )
///         // ...
/// )
/// .into()
/// # }
/// ```
pub fn grid_min_cell<Msg: Clone>( min_cell_width: impl Into<Length> ) -> WrapGrid<Msg>
{
	WrapGrid
	{
		children:        Vec::new(),
		columns:         0,
		min_cell_width:  Some( min_cell_width.into() ),
		max_columns:     None,
		spacing_x:       Length::px( 8.0 ),
		spacing_y:       Length::px( 8.0 ),
		padding:         Length::px( 0.0 ),
		centre_last_row: false,
	}
}
