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

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

/// A non-scrollable clipped viewport for revealing only part of a child tree.
///
/// Unlike `Scroll`, this widget does not own
/// any gesture state. It simply renders its child into an off-screen canvas
/// and blits the result into the allocated rect, clipping anything outside.
pub struct Viewport<Msg: Clone>
{
	/// The child element to render inside the viewport.
	pub( crate ) child: Box<Element<Msg>>,
	/// Optional fixed width in logical pixels. When omitted the
	/// viewport reports `max_width` as its preferred width — i.e. it
	/// stretches to fill whatever horizontal slice the parent layout
	/// gives it. Set explicitly when the viewport has to coexist with
	/// `flex` siblings (the flex would otherwise lose every pixel to
	/// the viewport's `max_width` claim) or whenever the inner content
	/// has its own intrinsic width and the viewport is just a vertical
	/// clip.
	pub( crate ) width: Option<f32>,
	/// Optional fixed height in logical pixels. When omitted the viewport
	/// reports the child's natural height.
	pub( crate ) height: Option<f32>,
	/// Logical pixels at the bottom edge that fade to transparent during the
	/// blit. Zero leaves the bottom hard-edged. Useful for slide-in panels so
	/// the leading edge of the animation does not knife-cut against the layer
	/// below it.
	pub( crate ) fade_bottom: f32,
	/// Resolve the child's viewport-relative and fluid `Length`s against
	/// this viewport's own rect instead of the inherited root layout
	/// viewport. See [`Self::local_viewport`].
	pub( crate ) local_viewport: bool,
}

impl<Msg: Clone> Viewport<Msg>
{
	pub fn new( child: impl Into<Element<Msg>> ) -> Self
	{
		Self { child: Box::new( child.into() ), width: None, height: None, fade_bottom: 0.0, local_viewport: false }
	}

	/// Set a fixed viewport width in logical pixels. Mirrors
	/// [`Self::height`]: with an explicit value the viewport
	/// reports `w` as its preferred width and the child is laid
	/// out against `w` rather than the parent-supplied `max_width`.
	pub fn width( mut self, w: f32 ) -> Self
	{
		self.width = Some( w.max( 0.0 ) );
		self
	}

	/// Set a fixed viewport height in logical pixels.
	pub fn height( mut self, h: f32 ) -> Self
	{
		self.height = Some( h.max( 0.0 ) );
		self
	}

	/// Feather the bottom `px` rows of the viewport so its lower edge dissolves
	/// to transparent. Drawn by the GLES blit shader as a linear alpha ramp
	/// over the bottom band; the software backend currently renders a hard
	/// edge regardless.
	pub fn fade_bottom( mut self, px: f32 ) -> Self
	{
		self.fade_bottom = px.max( 0.0 );
		self
	}

	/// Resolve the child's viewport-relative (`vw` / `vh` / `vmin`) and
	/// fluid `Length`s against this viewport's own rect instead of the
	/// root surface the sub-canvas normally inherits. For a fixed-size
	/// floating mini-UI (e.g. a phone-shaped panel pinned to a corner of
	/// a desktop-wide surface) whose content is calibrated against the
	/// panel rect and must not scale with the host surface. Scroll-like
	/// clips should keep the default inheritance so content renders at
	/// the same size inside and outside the clip.
	pub fn local_viewport( mut self ) -> Self
	{
		self.local_viewport = true;
		self
	}

	pub fn preferred_size( &self, max_width: f32, canvas: &Canvas ) -> ( f32, f32 )
	{
		let inner_w = self.width.unwrap_or( max_width );
		let child_h = self.child.preferred_size( inner_w, canvas ).1;
		( self.width.unwrap_or( max_width ), self.height.unwrap_or( child_h ) )
	}

	/// No-op — rendering is handled by `layout_and_draw` in `draw.rs`.
	pub fn draw( &self ) {}

	pub( crate ) fn map_msg<U>( self, f: &super::MapFn<Msg, U> ) -> Viewport<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		Viewport
		{
			child:          Box::new( self.child.map_arc( f ) ),
			width:          self.width,
			height:         self.height,
			fade_bottom:    self.fade_bottom,
			local_viewport: self.local_viewport,
		}
	}
}

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

/// Create a clipped viewport wrapping `child`.
pub fn viewport<Msg: Clone>( child: impl Into<Element<Msg>> ) -> Viewport<Msg>
{
	Viewport::new( child )
}

#[ cfg( test ) ]
mod tests;
