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)));
}
pub fn init_mode(default: impl Into<String>) {
let already_set = ACTIVE_MODE.with(|s| s.peek().is_some());
if !already_set {
set_mode(default);
}
}
pub 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));
}
pub 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 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()))
})
}
pub fn set_dark(on: bool) {
let target = SCHEME_PAIR.with(|p| {
p.borrow()
.as_ref()
.map(|(light, dark)| if on { dark.clone() } else { light.clone() })
});
if let Some(target) = target {
set_mode(target);
}
}
pub fn toggle_dark() {
let currently_dark = SCHEME_PAIR.with(|p| {
p.borrow()
.as_ref()
.is_some_and(|(_, dark)| active_mode().as_deref() == Some(dark.as_str()))
});
set_dark(!currently_dark);
}
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 init_mode_does_not_clobber_existing_selection() {
reset();
set_mode("midnight");
init_mode("modern");
assert_eq!(
active_mode().as_deref(),
Some("midnight"),
"init must keep a selection already made (e.g. restored across hot reload)"
);
}
#[test]
fn init_mode_applies_default_when_empty() {
reset();
init_mode("modern");
assert_eq!(active_mode().as_deref(), Some("modern"));
}
#[test]
fn set_and_toggle_dark_switch_between_the_pair() {
reset();
register_mode("day", || {});
register_mode("night", || {});
set_light_dark("day", "night");
set_dark(true);
assert_eq!(active_mode().as_deref(), Some("night"));
set_dark(false);
assert_eq!(active_mode().as_deref(), Some("day"));
toggle_dark();
assert_eq!(active_mode().as_deref(), Some("night"));
toggle_dark();
assert_eq!(active_mode().as_deref(), Some("day"));
}
#[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 dark_helpers_are_noops_without_a_pair() {
reset();
set_dark(true);
toggle_dark();
assert_eq!(active_mode(), None, "no pair set → nothing to switch to");
}
#[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"
);
}
}