use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, Instant};
pub const GRACE: Duration = Duration::from_secs(8);
static MANUAL: AtomicBool = AtomicBool::new(false);
fn down_since() -> &'static Mutex<Option<Instant>> {
static DOWN: OnceLock<Mutex<Option<Instant>>> = OnceLock::new();
DOWN.get_or_init(|| {
arm();
Mutex::new(Some(Instant::now()))
})
}
fn arm() {
let _ = std::thread::Builder::new()
.name("koan-offline".into())
.spawn(|| {
std::thread::sleep(GRACE);
crate::remote::devices::touch();
});
}
pub fn set_manual(on: bool) {
if MANUAL.swap(on, Ordering::AcqRel) != on {
crate::remote::devices::touch();
}
}
pub fn manual() -> bool {
MANUAL.load(Ordering::Acquire)
}
pub fn link_changed(up: bool) {
let mut down = down_since().lock().unwrap_or_else(|e| e.into_inner());
match (up, down.is_some()) {
(true, true) => *down = None,
(false, false) => {
*down = Some(Instant::now());
arm();
}
_ => {}
}
}
pub fn woke() {
let mut down = down_since().lock().unwrap_or_else(|e| e.into_inner());
if down.is_some() {
*down = Some(Instant::now());
arm();
}
}
pub fn active() -> bool {
manual() || cut_off()
}
pub fn cut_off() -> bool {
if !cfg!(target_os = "ios") || !crate::quiet::awake() {
return false;
}
let remote = &crate::config::Config::cached().remote;
if !remote.enabled || remote.url.is_empty() {
return false;
}
if crate::remote::profile::current().is_some_and(|p| !p.offers(crate::remote::profile::LINK)) {
return false;
}
down_since()
.lock()
.unwrap_or_else(|e| e.into_inner())
.is_some_and(|at| at.elapsed() >= GRACE)
}