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

use crate::render::Canvas;
use crate::types::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 children:  Vec<Element<Msg>>,
	/// Number of columns per row.
	pub columns:   usize,
	/// Horizontal gap between cells (pixels).
	pub spacing_x: f32,
	/// Vertical gap between rows (pixels).
	pub spacing_y: f32,
	/// Padding on all sides (pixels).
	pub padding:   f32,
}

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: f32 ) -> Self
	{
		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: f32 ) -> Self
	{
		self.spacing_x = s;
		self
	}

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

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

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

		let mut total_h = self.padding * 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 += self.spacing_y; }
		}
		( 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)>
	{
		if self.children.is_empty() || self.columns == 0
		{
			return Vec::new();
		}
		let cols    = self.columns;
		let inner_w = (rect.width - self.padding * 2.0).max( 0.0 );
		let cell_w  = (inner_w - self.spacing_x * (cols as f32 - 1.0)).max( 0.0 ) / cols as f32;
		let x0      = rect.x + self.padding;
		let mut y   = rect.y + self.padding;

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

			for col in 0..(end - start)
			{
				let x    = x0 + col as f32 * (cell_w + self.spacing_x);
				let crect = Rect { x, y, width: cell_w, height: row_h };
				out.push( ( crect, start + col ) );
			}
			y += row_h + self.spacing_y;
		}
		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,
			spacing_x: self.spacing_x,
			spacing_y: self.spacing_y,
			padding:   self.padding,
		}
	}
}

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 );
	}
}

/// 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,
		spacing_x: 8.0,
		spacing_y: 8.0,
		padding:   0.0,
	}
}
