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

//! Position a child element below the on-screen rect of another widget,
//! looked up from the *previous* frame's [`LaidOutWidget`] snapshot.
//!
//! The classic use case is a combo / dropdown popup that should appear
//! flush below its trigger. The trigger's rect is not known in `view()`
//! time — it's assigned during the layout pass — so the popup widget
//! cannot place itself there directly. `AnchoredOverlay` solves this
//! with a one-frame-old anchor: the trigger carries a stable
//! [`WidgetId`], the popup wraps in `AnchoredOverlay` referencing the
//! same id, and at draw time the wrapper looks up the trigger's rect
//! from the runtime's persisted `widget_rects` (frame N − 1) and
//! overrides the rect that its parent gave it.
//!
//! When the anchor is not found (first frame after open, missing id,
//! widget went off-screen) the wrapper falls back to drawing the child
//! in the parent-supplied rect — which, for a typical Stack-overlay
//! root in a `view()`, means the full surface, giving a sane modal
//! fallback until the next frame fixes the position.

use crate::types::{ Rect, WidgetId };

use super::Element;

#[ cfg( test ) ]
mod tests;

/// A wrapper that re-positions its child relative to an anchor widget
/// found in the previous frame's layout snapshot.
pub struct AnchoredOverlay<Msg: Clone>
{
	/// The element to draw at the anchored position.
	pub( crate ) child:     Box<Element<Msg>>,
	/// Stable identifier of the widget whose rect provides the anchor.
	pub( crate ) anchor_id: WidgetId,
	/// Vertical pixel gap between the bottom of the anchor and the top
	/// of the child.
	pub( crate ) gap:       f32,
}

impl<Msg: Clone> AnchoredOverlay<Msg>
{
	/// Wrap `child` so it draws anchored below the widget that carries
	/// `anchor_id` in its `.id( … )` builder. `gap` is the vertical
	/// space (logical pixels) between the anchor's bottom edge and the
	/// child's top edge.
	pub fn new( child: impl Into<Element<Msg>>, anchor_id: WidgetId, gap: f32 ) -> Self
	{
		Self
		{
			child:     Box::new( child.into() ),
			anchor_id,
			gap,
		}
	}

	/// Compute the draw rect for the child, given the anchor rect (when
	/// available) and the parent-supplied fallback.
	///
	/// Anchor available → place the child flush below the anchor with
	/// `gap` spacing, preserving the anchor's width.
	/// Anchor missing   → return the parent rect verbatim, so the child
	/// renders modal-style as a fallback.
	pub fn resolve_rect( anchor: Option<Rect>, gap: f32, fallback: Rect ) -> Rect
	{
		match anchor
		{
			Some( a ) => Rect
			{
				x:      a.x,
				y:      a.y + a.height + gap,
				width:  a.width,
				height: fallback.height,
			},
			None => fallback,
		}
	}

	pub( crate ) fn map_msg<U>( self, f: &super::MapFn<Msg, U> ) -> AnchoredOverlay<U>
	where
		U: Clone + 'static,
		Msg: 'static,
	{
		AnchoredOverlay
		{
			child:     Box::new( self.child.map_arc( f ) ),
			anchor_id: self.anchor_id,
			gap:       self.gap,
		}
	}
}

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