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

//! Auto-hide dock animation state.
//!
//! Forge's bottom-edge recogniser pumps `reveal_dock` / `hide_dock` IPC
//! messages whenever the cursor crosses in or out of a thin band at the
//! screen bottom. This module translates that pair into a slide-up /
//! slide-down animation with a grace period on hide, so a brief excursion
//! out of the band does not snap the dock away mid-interaction.

use std::time::{ Duration, Instant };

/// Frame-rate-independent progress speeds (units per second).
const REVEAL_SPEED: f32 = 6.0;
const HIDE_SPEED:   f32 = 4.0;

/// Grace before a `Hide` request actually starts the slide-down. Lets the
/// cursor briefly leave the hot band (overshoot, jitter at the edge) and
/// come back without losing the panel.
const HIDE_GRACE: Duration = Duration::from_millis( 400 );

pub struct DockReveal
{
	progress:   f32,
	target:     Option<f32>,
	last_tick:  Option<Instant>,
	hide_after: Option<Instant>,
}

impl DockReveal
{
	pub fn new() -> Self
	{
		Self { progress: 0.0, target: None, last_tick: None, hide_after: None }
	}

	pub fn progress( &self ) -> f32 { self.progress }

	/// True while the panel is at all on-screen — drives whether the
	/// overlay surface should be mounted at all in `App::overlays`.
	pub fn is_shown( &self ) -> bool
	{
		self.progress > 0.0 || self.target == Some( 1.0 )
	}

	pub fn is_animating( &self ) -> bool
	{
		self.target.is_some() || self.hide_after.is_some()
	}

	pub fn reveal( &mut self )
	{
		self.hide_after = None;
		if self.target == Some( 1.0 ) && self.progress >= 1.0 { return; }
		self.target    = Some( 1.0 );
		self.last_tick = None;
	}

	pub fn schedule_hide( &mut self )
	{
		if self.progress == 0.0 && self.target != Some( 1.0 ) { return; }
		self.hide_after = Some( Instant::now() + HIDE_GRACE );
	}

	pub fn tick_animation( &mut self ) -> bool
	{
		let mut active = false;
		if let Some( deadline ) = self.hide_after
		{
			active = true;
			if Instant::now() >= deadline
			{
				self.hide_after = None;
				self.target     = Some( 0.0 );
				self.last_tick  = None;
			}
		}

		let Some( target ) = self.target else { return active; };
		let now = Instant::now();
		let dt  = self.last_tick.map( |t| now.duration_since( t ).as_secs_f32() ).unwrap_or( 0.0 );
		self.last_tick = Some( now );

		let speed = if target > self.progress { REVEAL_SPEED } else { HIDE_SPEED };
		let step  = speed * dt;
		if ( target - self.progress ).abs() <= step
		{
			self.progress  = target;
			self.target    = None;
			self.last_tick = None;
		}
		else if target > self.progress
		{
			self.progress += step;
		}
		else
		{
			self.progress -= step;
		}
		true
	}
}
