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

use crate::types::{ Rect, WidgetId };
use crate::render::Canvas;
use super::Element;

mod theme;

#[ cfg( test ) ]
mod tests;

/// A two-state opt-in control with a square box and a check glyph.
///
/// Use for individual binary choices inside a form (terms acceptance,
/// multi-select lists, "remember me"). The widget is stateless — the
/// application owns `checked` and rebuilds the checkbox from current state
/// on every frame.
///
/// ```rust,no_run
/// # use ltk::{ checkbox, Checkbox };
/// # #[ derive( Clone ) ] enum Msg { ToggleTerms }
/// # struct App { accept_terms: bool }
/// # impl App { fn _ex( &self ) -> Checkbox<Msg> {
/// // In view():
/// checkbox( self.accept_terms )
///     .label( "I accept the terms" )
///     .on_toggle( Msg::ToggleTerms )
/// # }}
/// ```
///
/// See also [`Toggle`](super::toggle::Toggle) for prominent on / off
/// switches (settings panels, system toggles) and
/// [`Radio`](super::radio::Radio) for mutually-exclusive selection in a
/// group.
pub struct Checkbox<Msg: Clone>
{
	/// Current checked state. Drawn from this field every frame; the
	/// runtime never mutates it.
	pub( crate ) checked:   bool,
	/// Message emitted on activation. `None` leaves the checkbox inert.
	pub( crate ) on_toggle: Option<Msg>,
	/// Optional label drawn to the right of the box.
	pub( crate ) label:     Option<String>,
	/// Optional stable identifier for focus management.
	pub( crate ) id:        Option<WidgetId>,
}

impl<Msg: Clone> Checkbox<Msg>
{
	/// Create a checkbox in the given state, with no label and no
	/// callback. Wire activation through [`Self::on_toggle`] before adding
	/// it to a widget tree.
	pub fn new( checked: bool ) -> Self
	{
		Self { checked, on_toggle: None, label: None, id: None }
	}

	/// Set the message emitted when the checkbox is activated. The
	/// application's `update` is responsible for flipping `checked` in
	/// response.
	pub fn on_toggle( mut self, msg: Msg ) -> Self
	{
		self.on_toggle = Some( msg );
		self
	}

	/// Set a text label rendered to the right of the box. The checkbox's
	/// preferred width grows to fit `box_size + gap + label_width`,
	/// clamped to `max_width`.
	pub fn label( mut self, label: impl Into<String> ) -> Self
	{
		self.label = Some( label.into() );
		self
	}

	/// Assign a stable identifier for focus management.
	pub fn id( mut self, id: WidgetId ) -> Self
	{
		self.id = Some( id );
		self
	}

	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> (f32, f32)
	{
		let box_size = canvas.geom_px( theme::BOX_SIZE );
		let w = if let Some( ref label ) = self.label
		{
			let text_w = canvas.measure_text( label, canvas.font_px( theme::FONT_SIZE ) );
			( box_size + canvas.geom_px( theme::GAP ) + text_w ).min( max_width )
		} else {
			box_size.min( max_width )
		};
		( w, canvas.geom_px( theme::HEIGHT ) )
	}

	/// Focus ring on the box extends `FOCUS_W + 2 + FOCUS_W/2 ≈ 6.5 px` beyond
	/// the box edge (which sits flush with the widget's left edge).
	pub fn paint_bounds( &self, rect: Rect ) -> Rect
	{
		rect.expand( theme::FOCUS_W + 2.0 + theme::FOCUS_W * 0.5 + 1.0 )
	}

	pub fn draw( &self, canvas: &mut Canvas, rect: Rect, focused: bool )
	{
		let box_size = canvas.geom_px( theme::BOX_SIZE );
		let box_y = rect.y + ( rect.height - box_size ) / 2.0;
		let box_rect = Rect
		{
			x:      rect.x,
			y:      box_y,
			width:  box_size,
			height: box_size,
		};

		if self.checked
		{
			canvas.fill_rect( box_rect, theme::box_checked(), theme::RADIUS );
			let cx = rect.x + box_size / 2.0;
			let cy = box_y + box_size / 2.0;
			let s  = box_size * 0.3;
			canvas.draw_line( cx - s, cy, cx - s * 0.3, cy + s * 0.7, theme::check_color(), theme::CHECK_W );
			canvas.draw_line( cx - s * 0.3, cy + s * 0.7, cx + s, cy - s * 0.5, theme::check_color(), theme::CHECK_W );
		} else {
			canvas.stroke_rect( box_rect, theme::box_border(), theme::BORDER_W, theme::RADIUS );
		}

		if focused
		{
			let ring = box_rect.expand( theme::FOCUS_W + 2.0 );
			canvas.stroke_rect( ring, theme::focus_color(), theme::FOCUS_W, theme::RADIUS + theme::FOCUS_W + 2.0 );
		}

		if let Some( ref label ) = self.label
		{
			let fs     = canvas.font_px( theme::FONT_SIZE );
			let text_x = rect.x + box_size + canvas.geom_px( theme::GAP );
			let text_y = rect.y + ( rect.height + fs ) / 2.0 - 2.0;
			canvas.draw_text( label, text_x, text_y, fs, theme::label_color() );
		}
	}

	pub( crate ) fn map_msg<U>( self, f: &super::MapFn<Msg, U> ) -> Checkbox<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		Checkbox
		{
			checked:   self.checked,
			on_toggle: self.on_toggle.map( |m| ( *f )( m ) ),
			label:     self.label,
			id:        self.id,
		}
	}
}

/// Create a [`Checkbox`] in the given state.
///
/// Shorthand for [`Checkbox::new`]. Wire activation with
/// [`Checkbox::on_toggle`] and add a label with [`Checkbox::label`]:
///
/// ```rust,no_run
/// # use ltk::{ checkbox, Checkbox };
/// # #[ derive( Clone ) ] enum Msg { ToggleAccept }
/// # struct App { accept: bool }
/// # impl App { fn _ex( &self ) -> Checkbox<Msg> {
/// checkbox( self.accept )
///     .label( "I accept the terms" )
///     .on_toggle( Msg::ToggleAccept )
/// # }}
/// ```
pub fn checkbox<Msg: Clone>( checked: bool ) -> Checkbox<Msg>
{
	Checkbox::new( checked )
}

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