use crate::core::platform::Store;
use crate::core::tasks::BackgroundExecutor;
use crate::view::interactivity::{ClickEvent, EventContext};
pub use crate::view::persist::PersistValue;
use rustc_hash::FxHashMap;
use serde_json::Value;
use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;
pub struct State<T> {
inner: Rc<RefCell<T>>,
key: &'static str,
saver: Option<Rc<dyn Fn(&T)>>,
}
impl<T> Clone for State<T> {
fn clone(&self) -> Self {
Self { inner: Rc::clone(&self.inner), key: self.key, saver: self.saver.clone() }
}
}
impl<T> State<T> {
pub fn key(&self) -> &'static str {
self.key
}
pub fn read<R>(&self, read: impl FnOnce(&T) -> R) -> R {
read(&self.inner.borrow())
}
fn flush_save(&self) {
if let Some(saver) = &self.saver {
saver(&self.inner.borrow());
}
}
pub fn write(&self, write: impl FnOnce(&mut T)) {
write(&mut self.inner.borrow_mut());
self.flush_save();
}
pub fn with_mut<R>(&self, body: impl FnOnce(&mut T) -> R) -> R {
let result = body(&mut self.inner.borrow_mut());
self.flush_save();
result
}
pub fn update(&self, change: impl Fn(&mut T) + 'static) -> impl Fn(&ClickEvent, &mut EventContext) + 'static
where
T: 'static,
{
let state = self.clone();
move |_, cx| {
state.write(|value| change(value));
cx.notify();
}
}
}
impl<T: Clone> State<T> {
pub fn get(&self) -> T {
self.inner.borrow().clone()
}
pub fn set(&self, value: T) -> impl Fn(&ClickEvent, &mut EventContext) + 'static
where
T: 'static,
{
let state = self.clone();
move |_, cx| {
state.write(|current| *current = value.clone());
cx.notify();
}
}
}
impl State<bool> {
pub fn toggle(&self) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
let state = self.clone();
move |_, cx| {
state.write(|value| *value = !*value);
cx.notify();
}
}
}
impl State<String> {
pub fn take_trimmed(&self) -> Option<String> {
let trimmed = self.read(|text| text.trim().to_owned());
if trimmed.is_empty() {
return None;
}
self.write(String::clear);
Some(trimmed)
}
}
impl<T: 'static> State<Vec<T>> {
pub fn push(&self, item: T) {
self.write(|items| items.push(item));
}
pub fn remove_at(&self, index: usize) -> impl Fn(&ClickEvent, &mut EventContext) + 'static {
self.update(move |items| {
if index < items.len() {
items.remove(index);
}
})
}
}
#[derive(Default)]
pub(crate) struct StateStore {
values: FxHashMap<&'static str, Rc<dyn Any>>,
savers: FxHashMap<&'static str, Rc<dyn Any>>,
store: Option<Store>,
cache: Option<Value>,
}
impl StateStore {
pub fn with_store(store: Option<Store>) -> Self {
Self { values: FxHashMap::default(), savers: FxHashMap::default(), store, cache: None }
}
fn saver_for<T: 'static>(&self, key: &'static str) -> Option<Rc<dyn Fn(&T)>> {
self.savers.get(key)?.clone().downcast::<Rc<dyn Fn(&T)>>().ok().map(|rc| (*rc).clone())
}
pub fn get<T: 'static>(&self, key: &'static str) -> Option<State<T>> {
let existing = self.values.get(key)?;
let Some(cell) = existing.clone().downcast::<RefCell<T>>().ok() else {
panic!("el estado `{key}` se pidió como `{}` pero ya existía como otro tipo", std::any::type_name::<T>());
};
Some(State { inner: cell, key, saver: self.saver_for(key) })
}
pub fn insert<T: 'static>(&mut self, key: &'static str, value: T) -> State<T> {
let cell = Rc::new(RefCell::new(value));
self.values.insert(key, cell.clone());
State { inner: cell, key, saver: None }
}
pub fn get_or_insert<T: 'static>(&mut self, key: &'static str, default: T) -> State<T> {
self.get_or_insert_with(key, || default)
}
pub fn get_or_insert_with<T: 'static>(&mut self, key: &'static str, default: impl FnOnce() -> T) -> State<T> {
if let Some(state) = self.get::<T>(key) {
return state;
}
self.insert(key, default())
}
pub fn persisted<T: PersistValue>(
&mut self,
key: &'static str,
default: impl FnOnce() -> T,
background: &BackgroundExecutor,
) -> State<T> {
if let Some(state) = self.get::<T>(key) {
return state;
}
let loaded = self.store.as_ref().and_then(|store| {
let root = self.cache.get_or_insert_with(|| store.load());
root.get(key).and_then(T::from_json)
});
let initial = loaded.unwrap_or_else(default);
let cell = Rc::new(RefCell::new(initial));
self.values.insert(key, cell.clone());
let saver: Option<Rc<dyn Fn(&T)>> = self.store.clone().map(|store| {
let pool = background.clone();
let cb: Rc<dyn Fn(&T)> = Rc::new(move |val: &T| {
let json = val.to_json();
let store = store.clone();
pool.spawn(move || {
let _ = store.update(|root| root[key] = json);
});
});
self.savers.insert(key, Rc::new(cb.clone()));
cb
});
State { inner: cell, key, saver }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn el_estado_se_conserva_y_se_lee() {
let mut store = StateStore::default();
let count = store.get_or_insert("clics", 0u32);
count.write(|value| *value += 1);
assert_eq!(count.get(), 1);
let same = store.get_or_insert("clics", 99u32);
assert_eq!(same.get(), 1, "la clave devuelve el mismo valor, no el default");
}
#[test]
#[should_panic(expected = "ya existía como otro tipo")]
fn una_clave_con_otro_tipo_avisa_claro() {
let mut store = StateStore::default();
let _ = store.get_or_insert("dato", 1u32);
let _ = store.get_or_insert("dato", String::new());
}
}