use std::cell::RefCell;
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::{Rc, Weak};
use crabshell_reactive::{Memo, Signal, untrack};
use crate::cx::Cx;
use crate::tree::{NodeId, Tree, borrow};
use crate::widgets::Widget;
pub(crate) struct KeyedSlot {
pub(crate) tree: Weak<RefCell<Tree>>,
pub(crate) container: NodeId,
pub(crate) group: usize,
pub(crate) cx: Cx,
pub(crate) path: Rc<str>,
}
pub(crate) type KeyedMount = Box<dyn FnOnce(KeyedSlot)>;
struct Entry<T> {
cx: Cx,
value: Signal<T>,
node: NodeId,
}
pub(crate) fn mount<T, K, W>(
mut items: impl FnMut() -> Vec<T> + 'static,
mut key: impl FnMut(&T) -> K + 'static,
mut view: impl FnMut(&Cx, Memo<T>) -> W + 'static,
) -> KeyedMount
where
T: Clone + PartialEq + 'static,
K: Eq + Hash + 'static,
W: Widget,
{
Box::new(move |slot: KeyedSlot| {
let label = format!("{}.children", slot.path);
let mut entries: HashMap<K, Entry<T>> = HashMap::new();
let mut order: Vec<NodeId> = Vec::new();
let mut warned = false;
let cx = slot.cx.clone();
let name = label.clone();
cx.effect(move || {
let list = items();
let mut next: Vec<(K, T)> = Vec::with_capacity(list.len());
for item in list {
let item_key = key(&item);
if next.iter().any(|(kept, _)| *kept == item_key) {
if !warned {
tracing::warn!("{label}: two items have the same key; showing the first");
warned = true;
}
continue;
}
next.push((item_key, item));
}
let mut old = std::mem::take(&mut entries);
let mut added = Vec::new();
let mut kept = Vec::with_capacity(next.len());
for (item_key, item) in next {
match old.remove(&item_key) {
Some(entry) => {
entry.value.set(item);
kept.push((item_key, Some(entry)));
}
None => {
let cx = slot.cx.child();
let value = cx.signal(item);
let memo = cx.memo(move || value.get());
let element = untrack(|| Box::new(view(&cx, memo)).into_element());
added.push((cx, value, element));
kept.push((item_key, None));
}
}
}
let removed: Vec<Entry<T>> = old.into_values().collect();
let Some(tree) = slot.tree.upgrade() else {
return;
};
{
let mut tree = borrow(&tree);
let mut added = added.into_iter();
let mut nodes = Vec::with_capacity(kept.len());
for (item_key, entry) in kept {
let entry = match entry {
Some(entry) => entry,
None => {
let (cx, value, element) = added
.next()
.expect("invariant: every new key has a built element");
let node = tree.mount(element, &cx, slot.container);
Entry { cx, value, node }
}
};
nodes.push(entry.node);
entries.insert(item_key, entry);
}
for entry in &removed {
tree.unmount(entry.node);
}
if nodes != order {
tree.set_group(slot.container, slot.group, nodes.clone());
order = nodes;
}
}
for entry in removed {
entry.cx.owner().dispose();
}
})
.named(name);
})
}