use super::{transform_change_set, UnspecifiedIndexError};
use crate::cache::internal::TransformFuture;
use crate::list::{clear_or_remove_many, Change, ChangeAwareList, ChangeSet, IExtendedList, IList, ListChangeReason};
use ferroui_reactive::{Observable, ObservableExt};
use ferroui_system::threading::tasks::Task;
use ferroui_system::threading::CancellationToken;
use ferroui_system::{IObservable, ObservableError};
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
pub(crate) type ListAsyncTransformFactory<S, D> = Rc<dyn Fn(S, Option<D>, usize, CancellationToken) -> TransformFuture<D>>;
pub(crate) fn list_async_transform_factory<S: 'static, D: 'static, Fut: Future<Output = Result<D, ObservableError>> + 'static>(
factory: impl Fn(S, Option<D>, usize, CancellationToken) -> Fut + 'static,
) -> ListAsyncTransformFactory<S, D> {
Rc::new(move |item: S, previous: Option<D>, index: usize, token: CancellationToken| -> TransformFuture<D> { Box::pin(factory(item, previous, index, token)) })
}
#[derive(Clone)]
struct TransformedItemContainer<S, D> {
source: S,
destination: D,
}
impl<S: PartialEq, D> PartialEq for TransformedItemContainer<S, D> {
fn eq(&self, other: &Self) -> bool {
self.source == other.source
}
}
type State<S, D> = Rc<RefCell<ChangeAwareList<TransformedItemContainer<S, D>>>>;
pub(crate) fn transform_async<S: Clone + PartialEq + 'static, D: Clone + 'static>(
source: Rc<dyn IObservable<ChangeSet<S>>>,
factory: ListAsyncTransformFactory<S, D>,
transform_on_refresh: bool,
) -> Rc<dyn IObservable<ChangeSet<D>>> {
Observable::defer(move || {
let state: State<S, D> = Rc::new(RefCell::new(ChangeAwareList::new()));
let factory = factory.clone();
source
.select(move |changes: ChangeSet<S>| {
let (state, factory) = (state.clone(), factory.clone());
Observable::from_async_with_token(move |cancel: CancellationToken| {
let (state, factory, changes) = (state.clone(), factory.clone(), changes.clone());
async move {
transform(&state, &changes, &factory, transform_on_refresh, &cancel).await?;
let changed = state.borrow_mut().capture_changes();
Ok(transform_change_set(&changed, |container: &TransformedItemContainer<S, D>| container.destination.clone()))
}
})
})
.concat_inner::<ChangeSet<D>>()
})
}
fn container_factory<S: Clone + 'static, D: 'static>(
factory: &ListAsyncTransformFactory<S, D>,
item: &S,
previous: Option<D>,
index: usize,
cancel: &CancellationToken,
) -> impl Future<Output = Result<TransformedItemContainer<S, D>, ObservableError>> + 'static {
let source = item.clone();
let destination = factory(item.clone(), previous, index, cancel.clone());
async move { Ok(TransformedItemContainer { source, destination: destination.await? }) }
}
fn find_item_index<S: Clone + PartialEq, D: Clone>(transformed: &ChangeAwareList<TransformedItemContainer<S, D>>, source_item: &S) -> Option<usize> {
transformed.items().iter().position(|container| &container.source == source_item)
}
fn unspecified_index(message: &str) -> ObservableError {
Rc::new(UnspecifiedIndexError(message.to_owned()))
}
async fn transform<S: Clone + PartialEq + 'static, D: Clone + 'static>(
transformed: &State<S, D>,
changes: &ChangeSet<S>,
factory: &ListAsyncTransformFactory<S, D>,
transform_on_refresh: bool,
cancel: &CancellationToken,
) -> Result<(), ObservableError> {
for item in changes {
match item.reason() {
ListChangeReason::Add => {
let change = item.item();
let count = transformed.borrow().count();
match change.current_index() {
Some(index) if index < count => {
let container = container_factory(factory, change.current(), None, index, cancel).await?;
transformed.borrow_mut().insert(index, container);
}
_ => {
let container = container_factory(factory, change.current(), None, count, cancel).await?;
transformed.borrow_mut().add(container);
}
}
}
ListChangeReason::AddRange => {
let range = item.range();
let start_index = range.index().unwrap_or_else(|| transformed.borrow().count());
let tasks: Vec<Task<TransformedItemContainer<S, D>>> = range
.iter()
.enumerate()
.map(|(offset, source_item)| Task::spawn_local(container_factory(factory, source_item, None, offset + start_index, cancel)))
.collect();
let mut outcomes = Vec::with_capacity(tasks.len());
for task in tasks {
outcomes.push(task.await);
}
let containers = outcomes.into_iter().collect::<Result<Vec<_>, ObservableError>>()?;
IList::add_or_insert_range(&mut *transformed.borrow_mut(), containers, range.index());
}
ListChangeReason::Refresh => {
let change = item.item();
let index = change.current_index().or_else(|| find_item_index(&transformed.borrow(), change.current()));
let Some(index) = index else {
continue;
};
if transform_on_refresh {
let previous = transformed.borrow().get(index).destination.clone();
let container = container_factory(factory, change.current(), Some(previous), index, cancel).await?;
transformed.borrow_mut().set(index, container);
} else {
transformed.borrow_mut().refresh_at(index);
}
}
ListChangeReason::Replace => {
let change = item.item();
let previous_index = match change.previous_index() {
Some(index) => index,
None => {
let previous = change.previous().expect("a replacement has a previous item");
find_item_index(&transformed.borrow(), previous).ok_or_else(|| unspecified_index("Cannot find index of the replaced item"))?
}
};
let current_index = change.current_index().unwrap_or(previous_index);
if current_index == previous_index {
let previous = transformed.borrow().get(previous_index).destination.clone();
let container = container_factory(factory, change.current(), Some(previous), current_index, cancel).await?;
transformed.borrow_mut().set(current_index, container);
} else {
transformed.borrow_mut().remove_at(previous_index);
let container = container_factory(factory, change.current(), None, current_index, cancel).await?;
transformed.borrow_mut().insert(current_index, container);
}
}
ListChangeReason::Remove => {
let change = item.item();
let index = change.current_index().or_else(|| find_item_index(&transformed.borrow(), change.current()));
if let Some(index) = index {
transformed.borrow_mut().remove_at(index);
}
}
ListChangeReason::RemoveRange => {
let range = item.range();
match range.index() {
Some(index) => transformed.borrow_mut().remove_range(index, range.count()),
None => {
for current in range.iter() {
let index = find_item_index(&transformed.borrow(), current);
if let Some(index) = index {
transformed.borrow_mut().remove_at(index);
}
}
}
}
}
ListChangeReason::Clear => {
let mut list = transformed.borrow_mut();
let to_clear = Change::range_change(ListChangeReason::Clear, list.items().to_vec(), None);
clear_or_remove_many(&mut *list, &to_clear);
}
ListChangeReason::Moved => {
let change = item.item();
let (Some(current_index), Some(previous_index)) = (change.current_index(), change.previous_index()) else {
return Err(unspecified_index("Cannot move as an index was not specified"));
};
IExtendedList::move_index(&mut *transformed.borrow_mut(), previous_index, current_index);
}
}
}
Ok(())
}