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

//! Tooltip — short hint anchored below a widget.
//!
//! Like [`Toast`](super::toast::Toast), `Tooltip` is **not** an
//! [`Element`]; it is a builder that produces an [`OverlaySpec`] for
//! the application to return from [`App::overlays`](crate::App::overlays).
//! The overlay is rendered as an `xdg-popup` anchored to the widget
//! tagged with [`Tooltip::anchor_id`] in the previous frame's layout,
//! so the hint appears flush below the trigger and follows it across
//! resizes and re-layouts.
//!
//! Hover detection and the show / hide delay are the **application's
//! responsibility**. ltk does not currently expose pointer-hover
//! callbacks at the widget level, so the typical pattern is:
//!
//! 1. Track which widget the pointer is over via `App::on_tap` or
//!    a custom hover bookkeeping you maintain in `update`.
//! 2. Start a timer (via [`App::set_channel_sender`](crate::App::set_channel_sender))
//!    that fires a "show tooltip" message after the desired delay.
//! 3. Return the [`Tooltip::overlay`] from `overlays()` only while the
//!    tooltip should be visible.
//!
//! ```rust,no_run
//! # use ltk::{ tooltip, OverlaySpec, WidgetId };
//! # struct App { tooltip_for: Option<WidgetId> }
//! # impl App {
//! fn overlays( &self ) -> Vec<OverlaySpec<()>>
//! {
//!     match self.tooltip_for
//!     {
//!         Some( id ) => vec![ tooltip( "Save the document", id ).overlay() ],
//!         None       => vec![],
//!     }
//! }
//! # }
//! ```

use std::collections::hash_map::DefaultHasher;
use std::hash::{ Hash, Hasher };

use crate::app::{ Anchor, Layer, OverlayId, OverlaySpec };
use crate::types::{ Color, Length, WidgetId };
use super::Element;

mod theme;

#[ cfg( test ) ]
mod tests;

/// Builder for an `xdg-popup`-backed hint anchored to a widget.
pub struct Tooltip<Msg: Clone>
{
	/// Body text rendered inside the tooltip.
	pub( crate ) message:    String,
	/// Stable identifier of the widget the popup anchors to. The widget
	/// must have been built with `.id( anchor_id )` so the runtime can
	/// look its rect up in the previous frame's layout snapshot.
	pub( crate ) anchor_id:  WidgetId,
	/// Maximum width (logical pixels). The tooltip is single-line; long
	/// strings are clipped at the surface boundary.
	pub( crate ) max_width:  u32,
	/// Optional override for the tooltip background colour.
	pub( crate ) bg:         Option<Color>,
	/// Optional override for the body text colour.
	pub( crate ) fg:         Option<Color>,
	/// Optional message fired when the user taps outside the popup.
	pub( crate ) on_dismiss: Option<Msg>,
}

impl<Msg: Clone + 'static> Tooltip<Msg>
{
	/// Create a tooltip with the given message anchored to `anchor_id`.
	pub fn new( message: impl Into<String>, anchor_id: WidgetId ) -> Self
	{
		Self
		{
			message:    message.into(),
			anchor_id,
			max_width:  theme::MAX_W,
			bg:         None,
			fg:         None,
			on_dismiss: None,
		}
	}

	/// Override the maximum tooltip width. Defaults to 320 logical px.
	pub fn max_width( mut self, w: u32 ) -> Self
	{
		self.max_width = w;
		self
	}

	/// Override the background colour.
	pub fn background( mut self, c: Color ) -> Self
	{
		self.bg = Some( c );
		self
	}

	/// Override the body text colour.
	pub fn color( mut self, c: Color ) -> Self
	{
		self.fg = Some( c );
		self
	}

	/// Set the dismiss message fired on a tap outside the popup.
	pub fn on_dismiss( mut self, msg: Msg ) -> Self
	{
		self.on_dismiss = Some( msg );
		self
	}

	/// Build the [`OverlaySpec`] the application returns from
	/// [`App::overlays`](crate::App::overlays).
	pub fn overlay( self ) -> OverlaySpec<Msg>
	{
		use super::{ container, text };

		let mut hasher = DefaultHasher::new();
		"tooltip".hash( &mut hasher );
		self.anchor_id.0.hash( &mut hasher );
		let overlay_id = OverlayId( hasher.finish() as u32 );

		let bg = self.bg.unwrap_or_else( theme::bg );
		let fg = self.fg.unwrap_or_else( theme::text );

		let body: Element<Msg> = container(
				text( self.message.clone() )
					.size( Length::widget( theme::FONT_SIZE ) )
					.color( fg )
			)
			.background( bg )
			.padding_h( Length::widget( theme::PAD_H ) )
			.padding_v( Length::widget( theme::PAD_V ) )
			.radius( theme::RADIUS )
			.into();

		OverlaySpec
		{
			id:                 overlay_id,
			layer:              Layer::Overlay,
			anchor:             Anchor::TOP,
			// Height scales with the display so the fluid text always fits; the
			// caller-set max width stays fixed so wrapping is predictable.
			size:               ( Length::px( self.max_width as f32 ), Length::widget( theme::HEIGHT as f32 ) ),
			exclusive_zone:     0,
			keyboard_exclusive: false,
			input_region:       None,
			view:               body,
			on_dismiss:         self.on_dismiss,
			anchor_widget_id:   Some( self.anchor_id ),
		}
	}
}

/// Create a [`Tooltip`] anchored to the widget tagged with `anchor_id`.
///
/// ```rust,no_run
/// # use ltk::{ tooltip, OverlaySpec, WidgetId };
/// # fn _ex() -> OverlaySpec<()> {
/// tooltip( "Click to save", WidgetId( "btn/save" ) )
///     .max_width( 240 )
///     .overlay()
/// # }
/// ```
pub fn tooltip<Msg: Clone + 'static>(
	message:   impl Into<String>,
	anchor_id: WidgetId,
) -> Tooltip<Msg>
{
	Tooltip::new( message, anchor_id )
}
