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

use super::app_data::AppData;
use super::surface::SurfaceFocus;
use crate::app::App;
use crate::types::{ Length, Rect };

pub const TOOLTIP_DELAY: std::time::Duration = std::time::Duration::from_millis( 600 );

#[ derive( Clone ) ]
pub struct TooltipPending
{
	pub focus:    SurfaceFocus,
	pub flat_idx: usize,
	pub deadline: std::time::Instant,
	pub text:     String,
	pub anchor:   Rect,
}

#[ derive( Clone ) ]
pub struct TooltipVisible
{
	pub focus:  SurfaceFocus,
	pub anchor: Rect,
	pub text:   String,
}

impl<A: App> AppData<A>
{
	pub( crate ) fn arm_tooltip( &mut self, focus: SurfaceFocus, flat_idx: usize )
	{
		let Some( ss ) = self.try_surface( focus ) else { return };
		let Some( w ) = crate::tree::find_widget( &ss.frame.widget_rects, flat_idx ) else { return };
		let Some( text ) = w.tooltip.clone() else
		{
			self.tooltip_pending = None;
			return;
		};
		let anchor = w.rect;
		if let Some( ref p ) = self.tooltip_pending
		{
			if p.focus == focus && p.flat_idx == flat_idx { return; }
		}
		self.tooltip_pending = Some( TooltipPending
		{
			focus,
			flat_idx,
			deadline: std::time::Instant::now() + TOOLTIP_DELAY,
			text,
			anchor,
		} );
		if self.tooltip_visible.is_some()
		{
			self.tooltip_visible = None;
			self.overlays_dirty  = true;
		}
	}

	pub( crate ) fn cancel_tooltip( &mut self )
	{
		let was_visible = self.tooltip_visible.take().is_some();
		self.tooltip_pending = None;
		if was_visible { self.overlays_dirty = true; }
	}

	pub( crate ) fn next_tooltip_wakeup( &self ) -> Option<std::time::Duration>
	{
		let p = self.tooltip_pending.as_ref()?;
		Some( p.deadline.saturating_duration_since( std::time::Instant::now() ) )
	}

	pub( crate ) fn check_tooltip_deadline( &mut self )
	{
		let Some( p ) = self.tooltip_pending.as_ref() else { return };
		if std::time::Instant::now() < p.deadline { return; }
		let p = self.tooltip_pending.take().unwrap();
		self.tooltip_visible = Some( TooltipVisible
		{
			focus:  p.focus,
			anchor: p.anchor,
			text:   p.text,
		} );
		self.overlays_dirty = true;
		self.main.request_redraw();
	}

	pub( crate ) fn tooltip_overlay( &self ) -> Option<crate::app::OverlaySpec<A::Message>>
	{
		let v = self.tooltip_visible.as_ref()?;

		use std::hash::{ Hash, Hasher };
		let mut hasher = std::collections::hash_map::DefaultHasher::new();
		"ltk-tooltip".hash( &mut hasher );
		let id = crate::app::OverlayId( hasher.finish() as u32 );

		// Layout runs in the overlay's physical buffer space, so every
		// coordinate below is physical. The overlay's own scale wins once
		// the surface exists; before that, main's is the best predictor.
		let sf = self.overlays.get( &id )
			.map( |ss| ss.scale_factor )
			.unwrap_or( self.main.scale_factor )
			.max( 1 ) as f32;
		let ts = crate::types::text_scale();

		let ( ox, oy ) = self.surface_offset_for( v.focus );
		let src_scale = self.try_surface( v.focus )?.scale_factor.max( 1 ) as f32;
		let anchor_x = ( ox + v.anchor.x / src_scale ) * sf;
		let anchor_y = ( oy + v.anchor.y / src_scale ) * sf;
		let anchor_w = v.anchor.width  / src_scale * sf;
		let anchor_h = v.anchor.height / src_scale * sf;

		let palette = crate::theme::palette();
		let bg_col  = crate::types::Color::rgba( palette.text_primary.r, palette.text_primary.g, palette.text_primary.b, 0.95 );
		let pill: crate::widget::Element<A::Message> = crate::widget::container(
				crate::widget::text( v.text.clone() )
					.size( 13.0 )
					.color( palette.bg )
			)
			.background( bg_col )
			.padding_h( 12.0 * sf )
			.padding_v( 6.0 * sf )
			.radius( 8.0 * sf )
			.into();

		let estimated_w = ( v.text.chars().count() as f32 * 7.5 * ts * sf + 24.0 * sf )
			.clamp( 40.0 * sf, 320.0 * sf );
		let estimated_h = 16.0 * ts * sf + 12.0 * sf;
		let gap    = 6.0 * sf;
		let margin = 4.0 * sf;
		let mut x = anchor_x + ( anchor_w - estimated_w ) / 2.0;
		let mut y = anchor_y - estimated_h - gap;
		let sw = self.main.width  as f32 * sf;
		let sh = self.main.height as f32 * sf;
		x = x.clamp( margin, ( sw - estimated_w - margin ).max( margin ) );
		if y < margin { y = anchor_y + anchor_h + gap; }
		if y + estimated_h > sh - margin { y = ( sh - estimated_h - margin ).max( margin ); }

		let view: crate::widget::Element<A::Message> = crate::layout::stack::stack()
			.push_translated( pill, crate::layout::stack::HAlign::Start, crate::layout::stack::VAlign::Top, x, y )
			.into();

		Some( crate::app::OverlaySpec
		{
			id,
			layer:              crate::app::Layer::Overlay,
			anchor:             crate::app::Anchor::ALL,
			size:               ( Length::px( 0.0 ), Length::px( 0.0 ) ),
			exclusive_zone:     -1,
			keyboard_exclusive: false,
			input_region:       Some( Vec::new() ),
			view,
			on_dismiss:         None,
			anchor_widget_id:   None,
		} )
	}
}
