//! Splash rendering: decode the brand SVG once, then paint each frame with
//! tiny-skia using a parametric fade-in and a low-amplitude pulse driven by
//! a wall-clock timeline. Output is RGBA8 pre-multiplied and is later copied
//! into the DRM dumb buffer (which the kernel expects as XRGB8888 — the
//! conversion happens in `drm.rs`).

use anyhow::{ anyhow, Context, Result };
use resvg::tiny_skia::{ Color, Pixmap, PixmapPaint, Transform };
use resvg::usvg;
use std::path::Path;
use std::time::Duration;

/// Brand asset baked into a single pixmap. Decoded once at startup; every
/// frame draws this with a different alpha + scale.
pub struct Logo
{
	pub pixmap: Pixmap,
	pub w:      u32,
	pub h:      u32,
}

/// Decode an SVG at `path` and rasterise it to a pixmap whose longest edge
/// is `max_edge` physical pixels. Aspect is preserved.
pub fn decode_svg( path: &Path, max_edge: u32 ) -> Result<Logo>
{
	let bytes  = std::fs::read( path )
		.with_context( || format!( "read {}", path.display() ) )?;
	let opts   = usvg::Options::default();
	let tree   = usvg::Tree::from_data( &bytes, &opts )
		.with_context( || format!( "parse {}", path.display() ) )?;

	let size = tree.size();
	let ( sw, sh ) = ( size.width(), size.height() );
	if sw <= 0.0 || sh <= 0.0
	{
		return Err( anyhow!( "svg {} has zero intrinsic size", path.display() ) );
	}
	let scale = ( max_edge as f32 ) / sw.max( sh );
	let w     = ( sw * scale ).ceil() as u32;
	let h     = ( sh * scale ).ceil() as u32;

	let mut pixmap = Pixmap::new( w, h )
		.ok_or_else( || anyhow!( "tiny_skia::Pixmap::new({w}, {h})" ) )?;
	resvg::render(
		&tree,
		Transform::from_scale( scale, scale ),
		&mut pixmap.as_mut(),
	);

	Ok( Logo { pixmap, w, h } )
}

/// Animation timeline.
///
/// * 0..fade_in:        alpha goes 0 → 1 with ease-out cubic, scale flat at 1.0.
/// * fade_in..duration: alpha stays 1, scale oscillates between 1.0 and 1.05
///                      on a `pulse_period` cycle.
pub struct Timeline
{
	pub fade_in:      Duration,
	pub pulse_period: Duration,
	pub duration:     Duration,
}

impl Timeline
{
	/// 2.5 s splash: 250 ms fade-in, 1 s pulse cycle, 2500 ms total visible.
	/// Chosen to bridge most of the time forge takes between the splash
	/// dropping DRM master and crustace's first frame landing on screen —
	/// with the destination VT already in `KD_GRAPHICS` (daemon does that
	/// before the `SwitchTo`), the splash's last frame freezes in place
	/// until forge's modeset replaces it, so the user only sees animation
	/// → frozen-logo → crustace, never a text console flash.
	pub fn default_short() -> Self
	{
		Self
		{
			fade_in:      Duration::from_millis( 250 ),
			pulse_period: Duration::from_millis( 1000 ),
			duration:     Duration::from_millis( 2500 ),
		}
	}

	/// Parameters for a frame at elapsed time `t`. Returns `(alpha, scale)`.
	pub fn at( &self, t: Duration ) -> ( f32, f32 )
	{
		let fade_in_s = self.fade_in.as_secs_f32();
		let t_s       = t.as_secs_f32();

		let alpha = if fade_in_s > 0.0 && t_s < fade_in_s
		{
			let p = ( t_s / fade_in_s ).clamp( 0.0, 1.0 );
			1.0 - ( 1.0 - p ).powi( 3 )
		} else {
			1.0
		};

		let period_s = self.pulse_period.as_secs_f32().max( 0.001 );
		let phase    = ( t_s / period_s ) * std::f32::consts::TAU;
		let scale    = 1.0 + 0.025 * phase.sin();

		( alpha, scale )
	}
}

/// Paint a frame directly into a slice viewed as `PixmapMut`. RGBA premul.
pub fn paint_pixmap(
	dst:   &mut resvg::tiny_skia::PixmapMut<'_>,
	bg:    Color,
	logo:  &Logo,
	alpha: f32,
	scale: f32,
)
{
	dst.fill( bg );

	let fb_w   = dst.width();
	let fb_h   = dst.height();
	let logo_w = logo.w as f32;
	let logo_h = logo.h as f32;

	// Centre, applying scale around the logo centroid.
	let scaled_w = logo_w * scale;
	let scaled_h = logo_h * scale;
	let tx = ( fb_w as f32 - scaled_w ) * 0.5;
	let ty = ( fb_h as f32 - scaled_h ) * 0.5;

	let mut paint   = PixmapPaint::default();
	paint.opacity   = alpha.clamp( 0.0, 1.0 );
	paint.quality   = resvg::tiny_skia::FilterQuality::Bilinear;

	let transform = Transform::from_scale( scale, scale ).post_translate( tx, ty );
	dst.draw_pixmap( 0, 0, logo.pixmap.as_ref(), &paint, transform, None );
}
