//! Generic launch splash.
//!
//! When the shell launches an app, a placeholder card is shown until
//! the compositor reports the app's first toplevel (the same trigger
//! the dock uses to draw its running-app indicator). The state lives
//! here; the actual rendering is in [`view`]. Two layouts: portrait
//! fills the whole screen; landscape draws a centred 50% × 50% card.

pub mod view;

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

use crate::launcher::AppEntry;

/// One full icon-pulse cycle: base → +`PULSE_AMPLITUDE` → base.
pub const PULSE_PERIOD:    Duration = Duration::from_millis( 1400 );
/// Peak extra scale at the apex of the pulse (0.10 = +10%).
pub const PULSE_AMPLITUDE: f32      = 0.10;
/// Landscape card size as a fraction of the screen on each axis.
pub const LANDSCAPE_CARD_FRAC: f32  = 0.30;

#[ derive( Clone ) ]
pub struct SplashEntry
{
	pub desktop_app_id: String,
	pub name:           String,
	pub icon:           Option<PathBuf>,
	pub exec:           String,
	pub terminal:       bool,
	pub started_at:     Instant,
	pub timeout:        Duration,
}

#[ derive( Default ) ]
pub struct SplashState
{
	entries: Vec<SplashEntry>,
}

impl SplashState
{
	pub fn new() -> Self { Self::default() }

	/// Register a pending splash for `app`. A second start for the same
	/// `app_id` refreshes the timer in place instead of stacking a
	/// duplicate; a different app appends so the most recent launch
	/// renders on top.
	pub fn start( &mut self, app: &AppEntry, timeout: Duration )
	{
		self.entries.retain( |e| e.desktop_app_id != app.app_id );
		self.entries.push( SplashEntry {
			desktop_app_id: app.app_id.clone(),
			name:           app.name.clone(),
			icon:           app.icon.clone(),
			exec:           app.exec.clone(),
			terminal:       app.terminal,
			started_at:     Instant::now(),
			timeout,
		} );
	}

	/// Drop matches by `.desktop` id, by shared `exec`, or — when the new
	/// toplevel resolved to a terminal emulator — by `terminal` flag.
	pub fn dismiss(
		&mut self,
		desktop_app_id:                &str,
		exec:                          Option<&str>,
		resolved_is_terminal_emulator: bool,
	)
	{
		self.entries.retain( |e|
		{
			let id_match   = e.desktop_app_id == desktop_app_id;
			let exec_match = exec
				.map( |x| !x.is_empty() && e.exec == x )
				.unwrap_or( false );
			let term_match = resolved_is_terminal_emulator && e.terminal;
			!( id_match || exec_match || term_match )
		} );
	}

	/// Drop any splash whose timeout has elapsed — the app never opened
	/// (crashed, refused to launch, blocked on a prompt) and the splash
	/// would otherwise stay forever.
	pub fn gc( &mut self, now: Instant )
	{
		self.entries.retain( |e| now.duration_since( e.started_at ) < e.timeout );
	}

	pub fn current( &self ) -> Option<&SplashEntry>
	{
		self.entries.last()
	}

	pub fn is_active( &self ) -> bool
	{
		!self.entries.is_empty()
	}
}
