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

//! Widget that hosts content rendered by an external GL producer.
//!
//! Reserves layout space and, at draw time, samples a caller-provided GL
//! texture into the LTK canvas via [`Canvas::draw_external_texture`]. The
//! producer (a web engine, a video decoder, …) keeps ownership of the
//! texture and updates it on its own cadence; LTK only composites.
//!
//! # Source closure contract
//!
//! [`ExternalSource::Texture`] carries an
//! `Arc<dyn Fn(&glow::Context, Rect) -> Option<glow::Texture>>` that LTK
//! invokes once per frame, with its GLES context current. The producer:
//!
//! 1. Reads `Rect` (in physical pixels of the host surface) — useful for
//!    sizing the inner viewport (e.g. resizing a `WPEToplevel` to match)
//!    and for translating input coordinates by `rect.origin`.
//! 2. May allocate / update GL textures against the supplied
//!    `glow::Context`.
//! 3. May bind extension-imported EGLImages onto a persistent texture.
//! 4. Returns the `glow::Texture` to sample, or `None` to paint
//!    transparent (e.g. while the first frame is still being produced).
//!
//! Returning `None` for one frame and `Some` for the next is fine; LTK
//! re-invokes on every redraw.
//!
//! # Use cases
//!
//! * `ltk-webkit` hosting a `WPEView`-rendered page.
//! * Video / media playback widgets that decode into a GL texture.
//! * Any embedding that already has a GLES producer and wants its output
//!   in-line with the rest of the LTK widget tree.
//!
//! # Example
//!
//! ```rust,no_run
//! # use std::sync::{ Arc, Mutex };
//! # use ltk::{ External, ExternalSource, Element };
//! # #[ derive( Clone ) ] enum Msg {}
//! # fn _ex() -> Element<Msg> {
//! let cached_texture: Arc<Mutex<Option<glow::Texture>>> = Arc::new( Mutex::new( None ) );
//! let cached = Arc::clone( &cached_texture );
//! External::new(
//!     800.0, 600.0,
//!     ExternalSource::Texture( Arc::new( move | _gl, _rect | -> Option<glow::Texture>
//!     {
//!         *cached.lock().ok()?
//!     } ) ),
//! ).into()
//! # }
//! ```

use std::sync::Arc;

use crate::render::Canvas;
use crate::types::Rect;

/// A widget that defers its pixels to an external GL texture producer.
///
/// The widget itself is non-interactive and produces no messages. Wrap it
/// in a [`crate::widget::pressable::Pressable`] (or similar) when input
/// capture is needed.
pub struct External
{
	/// Reserved width in logical pixels.
	pub( crate ) width:   f32,
	/// Reserved height in logical pixels.
	pub( crate ) height:  f32,
	/// Source of the GL texture sampled at draw time.
	pub( crate ) source:  ExternalSource,
	/// Opacity multiplier in `[0.0, 1.0]`.
	pub( crate ) opacity: f32,
}

/// Backends an [`External`] widget can pull pixels from.
///
/// The only variant today is [`ExternalSource::Texture`], a closure
/// returning the current GL texture name. Future variants (DMA-BUF
/// imports, software pixmaps for the CPU backend, …) can be added
/// without changing call sites.
#[ derive( Clone ) ]
pub enum ExternalSource
{
	/// Closure invoked once per frame with LTK's [`glow::Context`] current
	/// and the widget's laid-out rect (in physical pixels of the host
	/// surface). The producer can allocate textures, bind extension-
	/// imported EGLImages, etc., and returns the texture name to sample.
	/// Returning `None` paints transparent — useful while the producer
	/// is still warming up its first frame. The rect is exposed so
	/// embedders can size their inner viewport to match LTK's actual
	/// allocation (e.g. resize a WPEToplevel on layout change) and
	/// translate input coordinates by `rect.origin`.
	Texture( Arc<dyn Fn( &glow::Context, Rect ) -> Option<glow::Texture> + Send + Sync> ),

	/// Closure invoked once per frame with LTK's [`Canvas`] and the widget's
	/// laid-out rect, for immediate-mode CPU drawing (no GL texture). Used to
	/// host an Android `View`'s custom `onDraw` straight onto the LTK canvas.
	/// Works on both the GLES and software backends.
	Cpu( Arc<dyn Fn( &mut Canvas, Rect ) + Send + Sync> ),
}

impl External
{
	/// Build a new external-content widget reserving `width × height`
	/// logical pixels.
	pub fn new( width: f32, height: f32, source: ExternalSource ) -> Self
	{
		Self { width, height, source, opacity: 1.0 }
	}

	/// Build an external widget that paints via an immediate-mode CPU closure
	/// reserving `width × height` logical pixels.
	pub fn cpu( width: f32, height: f32, draw: impl Fn( &mut Canvas, Rect ) + Send + Sync + 'static ) -> Self
	{
		Self::new( width, height, ExternalSource::Cpu( Arc::new( draw ) ) )
	}

	/// Override the opacity multiplier. Default: `1.0`.
	pub fn opacity( mut self, opacity: f32 ) -> Self
	{
		self.opacity = opacity.clamp( 0.0, 1.0 );
		self
	}

	pub fn preferred_size( &self, _max_width: f32 ) -> ( f32, f32 )
	{
		( self.width, self.height )
	}

	pub fn draw( &self, canvas: &mut Canvas, rect: Rect )
	{
		match &self.source
		{
			// Immediate CPU drawing works against any backend; the producer
			// paints straight into the canvas at `rect`.
			ExternalSource::Cpu( draw ) =>
			{
				draw( canvas, rect );
			}
			// External GL content only renders against the GLES backend — the
			// software backend has no GL texture to sample. Skip silently.
			ExternalSource::Texture( get ) =>
			{
				let gl = match canvas
				{
					Canvas::Software( _ ) => return,
					Canvas::Gles( c )     => c.gl.clone(),
				};
				if let Some( tex ) = get( &gl, rect )
				{
					canvas.draw_external_texture( tex, rect, self.opacity );
				}
			}
		}
	}
}

#[ cfg( test ) ]
mod tests;
