use std::collections::{BTreeMap, BTreeSet};
use std::pin::Pin;
use crate::bmap;
use crate::entities::custom_command::CustomCommand;
use crate::entities::environment::with_empty_env;
pub type EditFn<T> = fn(&mut T) -> Pin<Box<dyn Future<Output = anyhow::Result<()>>>>;
pub type CreateFn<T> = fn() -> Pin<Box<dyn Future<Output = anyhow::Result<T>>>>;
pub type CopyFn<T> = fn(&T) -> Pin<Box<dyn Future<Output = anyhow::Result<T>>>>;
#[allow(clippy::too_many_arguments)]
pub async fn edit_entities<T: Clone>(
entities: &mut Vec<T>,
registry: Option<&Vec<T>>,
title: &str,
show_entity: impl Fn(&T) -> String,
mut edit: Option<impl AsyncFnMut(&mut T) -> anyhow::Result<()>>,
mut create: Option<impl AsyncFnMut() -> anyhow::Result<T>>,
mut copy: Option<impl AsyncFnMut(&T) -> anyhow::Result<T>>,
reorder: bool,
) -> anyhow::Result<()> {
fn remove_entity<T>(entities: &mut Vec<T>, show_entity: &impl Fn(&T) -> String) -> anyhow::Result<()> {
let mut options = vec![];
for (pos, item) in entities.iter().enumerate() {
let item = format!("Remove #{}: {}", pos + 1, show_entity(item));
options.push(item);
}
let selected = inquire_reorder::Select::new("Remove element:", options.clone()).prompt_skippable()?;
let selected = if let Some(s) = selected {
s
} else {
return Ok(());
};
for (pos, item) in entities.iter().enumerate() {
if selected == format!("Remove #{}: {}", pos + 1, show_entity(item)) {
entities.remove(pos);
break;
}
}
Ok(())
}
async fn copy_entity<T>(
entities: &mut Vec<T>,
show_entity: &impl Fn(&T) -> String,
copy: &mut Option<impl AsyncFnMut(&T) -> anyhow::Result<T>>,
) -> anyhow::Result<()> {
let mut options = vec![];
for (pos, item) in entities.iter().enumerate() {
let item = format!("Copy #{}: {}", pos + 1, show_entity(item));
options.push(item);
}
let selected = inquire_reorder::Select::new("Copy element:", options.clone()).prompt_skippable()?;
let selected = if let Some(s) = selected {
s
} else {
return Ok(());
};
for (pos, item) in entities.iter().enumerate() {
if selected == format!("Copy #{}: {}", pos + 1, show_entity(item)) {
let new = copy.as_mut().unwrap()(item).await?;
entities.push(new);
break;
}
}
Ok(())
}
fn reorder_entities<T>(entities: &mut Vec<T>, show_entity: &impl Fn(&T) -> String) -> anyhow::Result<()> {
let mut values = bmap!();
let mut options = vec![];
for (pos, item) in entities.iter_mut().enumerate() {
let item = format!("#{}: {}", pos + 1, show_entity(item));
options.push(item.clone());
values.insert(item, pos);
}
let reordered = inquire_reorder::Reorder::new("Reorder elements:", options).prompt_skippable()?;
let reordered = if let Some(r) = reordered {
r
} else {
return Ok(());
};
let mut new_entities = vec![];
let mut old_entities = std::mem::take(entities)
.into_iter()
.map(|item| Some(item))
.collect::<Vec<_>>();
for each in reordered {
let pos = *values.get(&each).unwrap();
let item = old_entities[pos].take().unwrap();
new_entities.push(item);
}
*entities = new_entities;
Ok(())
}
async fn add_entity<T: Clone>(
registry: &Option<&Vec<T>>,
show_entity: &impl Fn(&T) -> String,
create: &mut Option<impl AsyncFnMut() -> anyhow::Result<T>>,
) -> anyhow::Result<T> {
if let Some(registry) = registry
&& !registry.is_empty()
{
let mut options = vec![];
let mut registry_map = BTreeMap::new();
for (idx, item) in registry.iter().enumerate() {
let display = format!("From registry: {}", show_entity(item));
options.push(display.clone());
registry_map.insert(display, idx);
}
if create.is_some() {
options.push("Create new".to_string());
}
let selection = inquire_reorder::Select::new("Add entity:", options).prompt()?;
if selection == "Create new" {
if let Some(create_fn) = create.as_mut() {
create_fn().await
} else {
anyhow::bail!("No create function provided")
}
} else {
let pos = *registry_map.get(&selection).unwrap();
Ok(registry[pos].clone())
}
} else if let Some(create_fn) = create.as_mut() {
create_fn().await
} else {
anyhow::bail!("No create function provided")
}
}
loop {
let mut options = Vec::with_capacity(entities.len() + 4);
let mut values = BTreeMap::new();
for (pos, item) in entities.iter().enumerate() {
let view = format!("#{}: {}", pos + 1, show_entity(item));
options.push(view.clone());
values.insert(view, pos);
}
if create.is_some() || registry.as_ref().is_some_and(|r| !r.is_empty()) {
options.push("Add".to_string());
}
if copy.is_some() {
options.push("Copy".to_string());
}
options.push("Remove".to_string());
if reorder && entities.len() > 1 {
options.push("Reorder".to_string());
}
if let Ok(Some(selection)) = inquire_reorder::Select::new(title, options.clone()).prompt_skippable() {
if selection.eq("Add") {
let entity = add_entity(®istry, &show_entity, &mut create).await?;
entities.push(entity);
} else if selection.eq("Remove") {
remove_entity(entities, &show_entity)?;
} else if selection.eq("Reorder") {
reorder_entities(entities, &show_entity)?;
} else if selection.eq("Copy") {
copy_entity(entities, &show_entity, &mut copy).await?;
} else if let Some(edit) = edit.as_mut()
&& let Some((_, pos)) = values.iter_mut().find(|(k, _)| k.as_str().eq(selection.as_str()))
{
let _ = edit(&mut entities[*pos]).await;
}
} else {
break;
}
}
Ok(())
}
pub async fn edit_btreemap_entities<K: Clone + Ord, V: Clone>(
entities: &mut BTreeMap<K, V>,
registry: Option<&BTreeMap<K, V>>,
title: &str,
show_entity: impl Fn(&(K, V)) -> String,
edit: Option<impl AsyncFnMut(&mut (K, V)) -> anyhow::Result<()>>,
create: Option<impl AsyncFnMut() -> anyhow::Result<(K, V)>>,
copy: Option<impl AsyncFnMut(&(K, V)) -> anyhow::Result<(K, V)>>,
) -> anyhow::Result<()> {
let mut opts = vec![];
for pair in entities.iter() {
opts.push((pair.0.clone(), pair.1.clone()));
}
let mut reg_opts = vec![];
let registry = btreemap_registry_to_vec(&mut reg_opts, registry);
edit_entities(&mut opts, registry, title, show_entity, edit, create, copy, false).await?;
let mut vals = bmap!();
for (k, v) in opts {
vals.insert(k, v);
}
if !vals.is_empty() {
*entities = vals;
}
Ok(())
}
pub fn btreemap_registry_to_vec<'a, K: Clone, V: Clone>(
reg_opts: &'a mut Vec<(K, V)>,
registry: Option<&'a BTreeMap<K, V>>,
) -> Option<&'a Vec<(K, V)>> {
if let Some(registry) = registry {
for pair in registry {
reg_opts.push((pair.0.clone(), pair.1.clone()));
}
Some(reg_opts)
} else {
None
}
}
pub fn btreemap_registry_to_vec_only_values<'a, K, V: Clone>(
reg_opts: &'a mut Vec<V>,
registry: Option<&'a BTreeMap<K, V>>,
) -> Option<&'a Vec<V>> {
if let Some(registry) = registry {
for pair in registry {
reg_opts.push(pair.1.clone());
}
Some(reg_opts)
} else {
None
}
}
pub fn btreeset_registry_to_vec<'a, V: Clone>(
reg_opts: &'a mut Vec<V>,
registry: Option<&'a BTreeSet<V>>,
) -> Option<&'a Vec<V>> {
if let Some(registry) = registry {
for item in registry.iter() {
reg_opts.push(item.clone());
}
Some(reg_opts)
} else {
None
}
}
pub async fn edit_btreeset_entities<T: Clone + Ord>(
entities: &mut BTreeSet<T>,
registry: Option<&BTreeSet<T>>,
title: &str,
show_entity: impl Fn(&T) -> String,
edit: Option<impl AsyncFnMut(&mut T) -> anyhow::Result<()>>,
create: Option<impl AsyncFnMut() -> anyhow::Result<T>>,
copy: Option<impl AsyncFnMut(&T) -> anyhow::Result<T>>,
) -> anyhow::Result<()> {
let mut en2 = entities.clone();
let mut en2_vec = en2.into_iter().collect::<Vec<_>>();
let registry = registry.map(|set| set.iter().cloned().collect::<Vec<_>>());
edit_entities(
&mut en2_vec,
registry.as_ref(),
title,
show_entity,
edit,
create,
copy,
false,
)
.await?;
en2 = en2_vec.into_iter().collect::<BTreeSet<_>>();
*entities = en2;
Ok(())
}
pub async fn edit_with_nano(
prompt: impl AsRef<str>,
text: String,
syntax: Option<&'static str>,
) -> anyhow::Result<String> {
let suffix = uuid::Uuid::new_v4().simple().to_string();
let filepath = format!("/tmp/depl.{suffix}.{}", syntax.unwrap_or("txt"));
let prompt = prompt
.as_ref()
.split('\n')
.map(|s| format!("# {s}"))
.collect::<Vec<_>>()
.join("\n");
let content = prompt.clone() + "\n\n" + text.as_str() + "\n";
std::fs::write(&filepath, content.as_bytes())?;
with_empty_env(async |env| CustomCommand::run_simple_observer(&env, format!("nano {filepath}")).await).await?;
let mut content = std::fs::read_to_string(&filepath)?;
std::fs::remove_file(&filepath)?;
content = content
.replace(&prompt, "")
.replace("\n\n", "\n")
.replace("\n\n", "\n")
.trim()
.to_string();
Ok(content)
}