use std::cell::RefCell;
use std::collections::HashMap;
use std::mem::ManuallyDrop;
use std::rc::Rc;
use reactive_core::{RwSignal, signal};
type ApplyMode = Rc<dyn Fn()>;
thread_local! {
static ACTIVE_MODE: ManuallyDrop<RwSignal<Option<String>>> = ManuallyDrop::new(signal(None));
static MODES: ManuallyDrop<RefCell<HashMap<String, ApplyMode>>> =
ManuallyDrop::new(RefCell::new(HashMap::new()));
}
pub fn register_mode(id: impl Into<String>, apply: impl Fn() + 'static) {
MODES.with(|m| m.borrow_mut().insert(id.into(), Rc::new(apply)));
}
pub fn set_mode(id: impl Into<String>) {
let id = id.into();
let apply = MODES.with(|m| m.borrow().get(&id).cloned());
if let Some(apply) = apply {
apply();
}
ACTIVE_MODE.with(|s| s.set(Some(id)));
}
fn use_mode() -> Option<String> {
ACTIVE_MODE.with(|s| s.get())
}
pub fn active_mode() -> Option<String> {
ACTIVE_MODE.with(|s| s.peek())
}
thread_local! {
static SCHEME_PAIR: ManuallyDrop<RefCell<Option<(String, String)>>> =
ManuallyDrop::new(RefCell::new(None));
}
fn set_light_dark(light: impl Into<String>, dark: impl Into<String>) {
SCHEME_PAIR.with(|p| *p.borrow_mut() = Some((light.into(), dark.into())));
}
pub(crate) fn is_dark() -> bool {
let active = use_mode();
SCHEME_PAIR.with(|p| {
p.borrow()
.as_ref()
.is_some_and(|(_, dark)| active.as_deref() == Some(dark.as_str()))
})
}
thread_local! {
static SYSTEM_DARK: ManuallyDrop<RwSignal<bool>> = ManuallyDrop::new(signal(false));
static FOLLOW: ManuallyDrop<RefCell<Option<reactive_core::Effect>>> =
ManuallyDrop::new(RefCell::new(None));
}
pub fn set_system_dark(dark: bool) {
SYSTEM_DARK.with(|s| s.set(dark));
}
pub fn follow_system(light: impl Into<String>, dark: impl Into<String>) {
let light = light.into();
let dark = dark.into();
set_light_dark(light.clone(), dark.clone());
let eff = reactive_core::effect(move || {
let want = if SYSTEM_DARK.with(|s| s.get()) {
&dark
} else {
&light
};
set_mode(want.clone());
});
FOLLOW.with(|f| *f.borrow_mut() = Some(eff));
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
fn reset() {
ACTIVE_MODE.with(|s| s.set(None));
MODES.with(|m| m.borrow_mut().clear());
SCHEME_PAIR.with(|p| *p.borrow_mut() = None);
FOLLOW.with(|f| *f.borrow_mut() = None);
SYSTEM_DARK.with(|s| s.set(false));
}
#[test]
fn set_mode_runs_apply_and_publishes_id() {
reset();
let hits = Rc::new(Cell::new(0));
let h = hits.clone();
register_mode("dark", move || h.set(h.get() + 1));
set_mode("dark");
assert_eq!(hits.get(), 1, "apply closure ran once");
assert_eq!(active_mode().as_deref(), Some("dark"));
}
#[test]
fn set_mode_publishes_even_without_registration() {
reset();
set_mode("unregistered");
assert_eq!(active_mode().as_deref(), Some("unregistered"));
}
#[test]
fn follow_system_drives_mode_from_os_scheme() {
reset();
register_mode("day", || {});
register_mode("night", || {});
follow_system("day", "night");
assert_eq!(
active_mode().as_deref(),
Some("day"),
"effect runs once with default SYSTEM_DARK=false → light"
);
set_system_dark(true);
assert_eq!(active_mode().as_deref(), Some("night"));
set_system_dark(false);
assert_eq!(active_mode().as_deref(), Some("day"));
}
#[test]
fn is_dark_false_for_unpaired_third_mode() {
reset();
set_light_dark("day", "night");
set_mode("pastel");
assert!(
!is_dark(),
"a third mode outside the pair is neither dark nor light"
);
}
#[test]
fn use_mode_is_reactive() {
reset();
let seen = Rc::new(RefCell::new(Vec::<Option<String>>::new()));
let s = seen.clone();
let _e = reactive_core::effect(move || s.borrow_mut().push(use_mode()));
set_mode("a");
set_mode("b");
let got = seen.borrow().clone();
assert_eq!(
got,
vec![None, Some("a".into()), Some("b".into())],
"effect re-ran on each mode switch"
);
}
}