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

//! Blocking D-Bus client to UPower's aggregated `DisplayDevice`
//! (`org.freedesktop.UPower` on the system bus) — the single source for the
//! battery figures, shared with forge and the lock screen so all three
//! display the same value.

use std::sync::OnceLock;

use zbus::blocking::{ Connection, Proxy };

const DEST:         &str = "org.freedesktop.UPower";
const DISPLAY_PATH: &str = "/org/freedesktop/UPower/devices/DisplayDevice";
const DEVICE_IFACE: &str = "org.freedesktop.UPower.Device";

const STATE_CHARGING:      u32 = 1;
const STATE_FULLY_CHARGED: u32 = 4;

pub struct BatteryInfo
{
	pub percent:  u8,
	pub charging: bool,
	pub present:  bool,
	/// Positive seconds until empty, negative seconds until full.
	pub time_remaining_secs: Option<i64>,
}

fn conn() -> Option<Connection>
{
	static CONN: OnceLock<Option<Connection>> = OnceLock::new();
	CONN.get_or_init( || Connection::system().ok() ).clone()
}

pub fn battery() -> Option<BatteryInfo>
{
	let c = conn()?;
	let p = Proxy::new( &c, DEST, DISPLAY_PATH, DEVICE_IFACE ).ok()?;
	let percent  = p.get_property::<f64>( "Percentage" ).ok()?;
	let state    = p.get_property::<u32>( "State" ).unwrap_or( 0 );
	let present  = p.get_property::<bool>( "IsPresent" ).unwrap_or( false );
	let to_empty = p.get_property::<i64>( "TimeToEmpty" ).unwrap_or( 0 );
	let to_full  = p.get_property::<i64>( "TimeToFull" ).unwrap_or( 0 );
	let time_remaining_secs =
		if to_full > 0 { Some( -to_full ) }
		else if to_empty > 0 { Some( to_empty ) }
		else { None };
	Some( BatteryInfo
	{
		percent:  percent.round().clamp( 0.0, 100.0 ) as u8,
		charging: matches!( state, STATE_CHARGING | STATE_FULLY_CHARGED ),
		present,
		time_remaining_secs,
	})
}
