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

use super::Element;
use crate::layout::row::{ row, Row };
use crate::render::Canvas;
use crate::types::{ Color, Rect, WidgetId };

mod theme;

#[ cfg( test ) ]
mod tests;

/// Semantic role for a window-decoration button.
///
/// Drives both the rendered glyph (a horizontal bar for minimize, a square
/// outline for maximize, etc.) and the close button's special hover
/// treatment (red surface tint instead of the neutral hover wash). Maps
/// 1:1 to the four standard title-bar controls on Windows / GNOME / macOS.
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum WindowButtonKind
{
	/// Hide the window to the dock / taskbar.
	Minimize,
	/// Maximize the window to fill the available output.
	Maximize,
	/// Restore a previously-maximized window to its original size.
	Restore,
	/// Close the window. Renders with a destructive (red) hover surface.
	Close,
}

/// Button styled for compositor / window decorations.
///
/// The widget is intentionally policy-free: it paints a standard control and
/// emits the message supplied by the caller. Forge remains responsible for
/// deciding what that message does to the window.
pub struct WindowButton<Msg: Clone>
{
	/// Which decoration role this button paints.
	pub( crate ) kind:      WindowButtonKind,
	/// Message emitted on activation. `None` greys the button and skips
	/// the hover / pressed surface — useful for "maximize disabled" on
	/// fixed-size windows.
	pub( crate ) on_press:  Option<Msg>,
	/// Square hit-target size in logical pixels. An explicit [`Self::size`]
	/// is clamped to a 20 px floor so the button stays touchable; the `0.0`
	/// default follows the process [`crate::WidgetScaling`] mode.
	pub( crate ) size:      f32,
	/// Optional stable identifier for focus management.
	pub( crate ) id:        Option<WidgetId>,
	/// Whether this button takes part in the Tab / Shift+Tab cycle. Defaults
	/// to `false` to match desktop convention — title-bar chrome on macOS,
	/// GNOME and Windows is click/touch-only and never steals keyboard focus
	/// from window content. Opt in with [`Self::focusable`] for shells where
	/// keyboard reachability of decorations matters (accessibility, no-mouse
	/// kiosks). Pointer / touch hit testing is unaffected by this flag.
	pub( crate ) focusable: bool,
}

impl<Msg: Clone> WindowButton<Msg>
{
	/// Create a window-decoration button of the given kind. The button is
	/// inert (no callback) until [`Self::on_press`] is configured.
	pub fn new( kind: WindowButtonKind ) -> Self
	{
		Self
		{
			kind,
			on_press:  None,
			size:      0.0,
			id:        None,
			focusable: false,
		}
	}

	/// Set the message emitted when the button is activated.
	pub fn on_press( mut self, msg: Msg ) -> Self
	{
		self.on_press = Some( msg );
		self
	}

	/// Like [`Self::on_press`] but keeps the disabled state when `None`
	/// is passed — useful when the message depends on a runtime
	/// condition (e.g. maximize is disabled for fixed-size windows).
	pub fn on_press_maybe( mut self, msg: Option<Msg> ) -> Self
	{
		self.on_press = msg;
		self
	}

	/// Override the square hit-target size in logical pixels. Clamped to
	/// a 20 px floor so the button remains touchable.
	pub fn size( mut self, size: f32 ) -> Self
	{
		self.size = size.max( 20.0 );
		self
	}

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

	/// Opt into keyboard focus traversal. Defaults to `false` so the
	/// button does not steal Tab focus from window content.
	pub fn focusable( mut self, yes: bool ) -> Self
	{
		self.focusable = yes;
		self
	}

	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
	{
		let resolved = if self.size > 0.0 { self.size } else { canvas.geom_px( theme::SIZE ) };
		let s = resolved.min( max_width );
		( s, s )
	}

	pub fn paint_bounds( &self, rect: Rect ) -> Rect
	{
		rect.expand( theme::FOCUS_W * 1.5 + 2.0 )
	}

	pub fn draw(
		&self,
		canvas:  &mut Canvas,
		rect:    Rect,
		focused: bool,
		hovered: bool,
		pressed: bool,
	)
	{
		let disabled = self.on_press.is_none();
		let bg = if disabled
		{
			Color::TRANSPARENT
		}
		else if pressed
		{
			theme::pressed_bg()
		}
		else if hovered && self.kind == WindowButtonKind::Close
		{
			theme::close_hover()
		}
		else if hovered
		{
			theme::hover_bg()
		}
		else
		{
			Color::TRANSPARENT
		};

		if bg.a > 0.0
		{
			canvas.fill_rect( rect, bg, theme::RADIUS );
		}

		if focused
		{
			canvas.stroke_rect(
				rect.expand( theme::FOCUS_W + 1.0 ),
				theme::focus_color(),
				theme::FOCUS_W,
				theme::RADIUS + theme::FOCUS_W + 1.0,
			);
		}

		let icon_color = if hovered && self.kind == WindowButtonKind::Close && !disabled
		{
			theme::close_icon()
		}
		else
		{
			let base = theme::icon();
			if disabled
			{
				Color::rgba( base.r, base.g, base.b, base.a * 0.35 )
			}
			else
			{
				base
			}
		};
		draw_glyph( canvas, self.kind, rect, icon_color );
	}

	pub( crate ) fn map_msg<U>( self, f: &super::MapFn<Msg, U> ) -> WindowButton<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		WindowButton
		{
			kind:      self.kind,
			on_press:  self.on_press.map( |m| ( *f )( m ) ),
			size:      self.size,
			id:        self.id,
			focusable: self.focusable,
		}
	}
}

/// Catalogue path for the symbolic SVG that paints `kind`. Each entry
/// resolves through the theme's `icons/catalogue/filled/window/<kind>.svg`
/// (or the `line/` fallback the catalogue lookup applies automatically).
fn icon_name( kind: WindowButtonKind ) -> &'static str
{
	match kind
	{
		WindowButtonKind::Minimize => "window/minimize",
		WindowButtonKind::Maximize => "window/maximize",
		WindowButtonKind::Restore  => "window/restore",
		WindowButtonKind::Close    => "window/close",
	}
}

/// Render the glyph for `kind` centered inside `rect`, tinted with
/// `color`. Tries the theme catalogue first via [`crate::theme::icon_rgba`]
/// + [`crate::theme::tint_symbolic`]; falls back to the programmatic
/// line / rect drawing in [`draw_symbol`] when the active theme has no
/// catalogue entry for `window/<kind>`.
fn draw_glyph( canvas: &mut Canvas, kind: WindowButtonKind, rect: Rect, color: Color )
{
	let s = rect.width.min( rect.height );
	let icon_px = ( s * 0.44 ).round().max( 8.0 ) as u32;

	if let Some( ( rgba, iw, ih ) ) = crate::theme::icon_rgba( icon_name( kind ), icon_px )
	{
		let tinted = crate::theme::tint_symbolic( &rgba, color );
		// Centre the rasterised glyph inside the button rect. Round to
		// integer offsets so the bilinear sampler hits texel centres
		// and the glyph stays crisp.
		let cx = rect.x + rect.width  / 2.0;
		let cy = rect.y + rect.height / 2.0;
		let dest = Rect
		{
			x:      ( cx - iw as f32 / 2.0 ).round(),
			y:      ( cy - ih as f32 / 2.0 ).round(),
			width:  iw as f32,
			height: ih as f32,
		};
		canvas.draw_image_data( &tinted, iw, ih, dest, 1.0 );
	}
	else
	{
		// No catalogue entry → fall back to the programmatic glyph so
		// chrome stays usable on themes that don't ship
		// `icons/catalogue/filled/window/`.
		draw_symbol( canvas, kind, rect, color );
	}
}

fn draw_symbol( canvas: &mut Canvas, kind: WindowButtonKind, rect: Rect, color: Color )
{
	let cx = rect.x + rect.width  / 2.0;
	let cy = rect.y + rect.height / 2.0;
	let s  = rect.width.min( rect.height );
	let a  = s * 0.22;
	let w  = theme::STROKE_W;

	match kind
	{
		WindowButtonKind::Minimize =>
		{
			let y = cy + a * 0.65;
			canvas.draw_line( cx - a, y, cx + a, y, color, w );
		}
		WindowButtonKind::Maximize =>
		{
			let r = Rect { x: cx - a, y: cy - a, width: a * 2.0, height: a * 2.0 };
			canvas.stroke_rect( r, color, w, 2.0 );
		}
		WindowButtonKind::Restore =>
		{
			let back  = Rect { x: cx - a * 0.45, y: cy - a,        width: a * 1.55, height: a * 1.55 };
			let front = Rect { x: cx - a,        y: cy - a * 0.45, width: a * 1.55, height: a * 1.55 };
			canvas.stroke_rect( back,  color, w, 2.0 );
			canvas.stroke_rect( front, color, w, 2.0 );
		}
		WindowButtonKind::Close =>
		{
			canvas.draw_line( cx - a, cy - a, cx + a, cy + a, color, w );
			canvas.draw_line( cx + a, cy - a, cx - a, cy + a, color, w );
		}
	}
}

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

/// Create a window-decoration button.
pub fn window_button<Msg: Clone>( kind: WindowButtonKind ) -> WindowButton<Msg>
{
	WindowButton::new( kind )
}

/// Create the standard minimize / maximize-or-restore / close control group.
pub fn window_controls<Msg: Clone + 'static>(
	minimize:      Option<Msg>,
	maximize_kind: WindowButtonKind,
	maximize:      Option<Msg>,
	close:         Option<Msg>,
) -> Row<Msg>
{
	row::<Msg>()
		.spacing( 4.0 )
		.push( window_button( WindowButtonKind::Minimize ).on_press_maybe( minimize ) )
		.push( window_button( maximize_kind                ).on_press_maybe( maximize ) )
		.push( window_button( WindowButtonKind::Close    ).on_press_maybe( close    ) )
}
