// Full-screen session menu: Logout, Lock, Suspend, Reboot, Power Off over a
// translucent black backdrop. Opened from the quick-settings header button or
// from forge over IPC ("show_power_menu"); a press on the backdrop cancels.

use crate::assets;
use crate::message::Message;

use rust_i18n::t;
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::Arc;

#[derive( Debug, Clone, Copy, PartialEq, Eq )]
pub enum PowerAction
{
	Lock,
	Suspend,
	Logout,
	Reboot,
	PowerOff,
	Cancel,
}

type RgbaIcon = ( Arc<Vec<u8>>, u32, u32 );

/// Decode + pre-tint a menu glyph. The backdrop is always black, so the
/// glyphs are tinted white once at construction instead of per theme flip.
fn white_icon( svg: &[u8] ) -> Option<RgbaIcon>
{
	assets::decode_svg_bytes( svg, 72 ).map( |( raw, w, h )|
	{
		( Arc::new( ltk::tint_symbolic( &raw[..], ltk::Color::WHITE ) ), w, h )
	} )
}

pub struct PowerMenu
{
	pub visible: bool,
	logout:  Option<RgbaIcon>,
	lock:    Option<RgbaIcon>,
	suspend: Option<RgbaIcon>,
	reboot:  Option<RgbaIcon>,
	power:   Option<RgbaIcon>,
}

impl PowerMenu
{
	pub fn new() -> Self
	{
		Self
		{
			visible: false,
			logout:  white_icon( assets::LOGOUT_FILLED ),
			lock:    white_icon( assets::LOCK_FILLED ),
			suspend: white_icon( assets::SUSPEND_FILLED ),
			reboot:  white_icon( assets::REBOOT_FILLED ),
			power:   white_icon( assets::POWER_FILLED ),
		}
	}

	pub fn show( &mut self )
	{
		self.visible = true;
	}

	pub fn dismiss( &mut self )
	{
		self.visible = false;
	}

	/// Try the loginmanager daemon first (runs as root, no polkit required);
	/// fall back to busctl → logind when the daemon socket is unreachable.
	fn daemon_power_call( method: &str )
	{
		let sock_path = std::env::var( "LOGINMANAGER_SOCK" )
			.unwrap_or_else( |_| "/run/loginmanager/ctrl.sock".to_string() );

		if let Ok( mut stream ) = UnixStream::connect( &sock_path )
		{
			let body = format!( r#"{{"type":"{}"}}"#, method );
			let len  = ( body.len() as u32 ).to_le_bytes();
			let _ = stream.write_all( &len );
			let _ = stream.write_all( body.as_bytes() );
			let _ = stream.flush();
			return;
		}
		// Fallback: call logind directly with interactive=false.  This works
		// when the session is recognised as "active" by polkit.
		if let Err( e ) = crate::spawn::spawn_reaped(
			Command::new( "busctl" ).args( [
				"call", "--system",
				"org.freedesktop.login1",
				"/org/freedesktop/login1",
				"org.freedesktop.login1.Manager",
				method,
				"b", "false",
			] ),
		)
		{
			tracing::error!( "Failed to invoke logind {method}: {e}" );
		}
	}

	pub fn execute( action: PowerAction )
	{
		let method = match action
		{
			PowerAction::PowerOff => "PowerOff",
			PowerAction::Reboot   => "Reboot",
			PowerAction::Suspend  => "Suspend",
			PowerAction::Lock | PowerAction::Logout | PowerAction::Cancel => return,
		};
		Self::daemon_power_call( method );
	}

	fn entry( icon: &Option<RgbaIcon>, label: String, action: PowerAction ) -> ltk::Element<Message>
	{
		let icon_sz = ltk::Length::dp( 26.0 );
		let icon_el: ltk::Element<Message> = match icon
		{
			Some( ( rgba, w, h ) ) =>
			{
				ltk::img_widget( Arc::clone( rgba ), *w, *h ).size( icon_sz, icon_sz ).into()
			}
			None => ltk::spacer().width( icon_sz ).into(),
		};

		let row = ltk::row::<Message>()
			.padding( 0.0 )
			.spacing( ltk::Length::dp( 18.0 ) )
			.push( icon_el )
			.push(
				ltk::text( label )
					.size( 19.0 )
					.color( ltk::Color::WHITE ),
			);

		ltk::pressable(
			ltk::container::<Message>( row )
				.padding_h( ltk::Length::dp( 28.0 ) )
				.padding_v( ltk::Length::dp( 14.0 ) ),
		)
		.on_press( Message::PowerAction( action ) )
		.into()
	}

	pub fn view( &self ) -> ltk::Element<Message>
	{
		let items = ltk::column::<Message>()
			.padding( 0.0 )
			.spacing( ltk::Length::dp( 10.0 ) )
			.fit_content()
			.push( Self::entry( &self.logout,  t!( "power_log_out" ).to_string(),     PowerAction::Logout ) )
			.push( Self::entry( &self.lock,    t!( "power_lock_screen" ).to_string(), PowerAction::Lock ) )
			.push( Self::entry( &self.suspend, t!( "power_suspend" ).to_string(),     PowerAction::Suspend ) )
			.push( Self::entry( &self.reboot,  t!( "power_restart" ).to_string(),     PowerAction::Reboot ) )
			.push( Self::entry( &self.power,   t!( "power_off" ).to_string(),         PowerAction::PowerOff ) );

		// Plain spacers: `Element::Flex` is only handled by row layout,
		// so a flex-wrapped spacer in the outer column would collapse
		// and pin the items to the top instead of centering them.
		let centered = ltk::column::<Message>()
			.padding( 0.0 )
			.spacing( 0.0 )
			.push( ltk::spacer() )
			.push(
				ltk::row::<Message>()
					.padding( 0.0 )
					.spacing( 0.0 )
					.push( ltk::flex( ltk::spacer() ) )
					.push( items )
					.push( ltk::flex( ltk::spacer() ) ),
			)
			.push( ltk::spacer() );

		ltk::pressable(
			ltk::container::<Message>( centered )
				.background( ltk::Color::rgba( 0.0, 0.0, 0.0, 0.8 ) ),
		)
		.on_press( Message::PowerAction( PowerAction::Cancel ) )
		.into()
	}
}
