use serde_json::Value as JsonValue;
use toml_edit::{Array, Item, Value};
use crate::error::{Result, RototoError};
use super::engine::WorkingTree;
use super::operation::ChangeRecord;
use super::paths::{checked_id, list_path};
use super::value::{json_from_toml_value, toml_value_from_json};
pub(super) fn add_member(
work: &mut WorkingTree<'_>,
list_id: &str,
value: &JsonValue,
) -> Result<ChangeRecord> {
checked_id("list", list_id)?;
checked_member(value)?;
let new_member = toml_value_from_json(value)?;
let path = list_path(list_id);
let mut document = work.parse_existing(&path, &format!("list `{list_id}`"))?;
let members = members_mut(&mut document, &path)?;
let before = members_json(members);
if members
.iter()
.any(|member| json_from_toml_value(member) == *value)
{
return Err(RototoError::new(format!(
"{value} is already a member of list `{list_id}`"
)));
}
members.push(new_member);
let after = members_json(members);
work.write(path, document.to_string());
Ok(ChangeRecord {
operation: "add_member",
address: format!("list={list_id}#/members"),
before: Some(before),
after: Some(after),
})
}
pub(super) fn remove_member(
work: &mut WorkingTree<'_>,
list_id: &str,
value: &JsonValue,
) -> Result<ChangeRecord> {
checked_id("list", list_id)?;
let path = list_path(list_id);
let mut document = work.parse_existing(&path, &format!("list `{list_id}`"))?;
let members = members_mut(&mut document, &path)?;
let before = members_json(members);
let Some(index) = members
.iter()
.position(|member| json_from_toml_value(member) == *value)
else {
return Err(RototoError::new(format!(
"{value} is not a member of list `{list_id}`"
)));
};
let removed_prefix = members
.get(index)
.and_then(|member| member.decor().prefix().cloned());
members.remove(index);
if index == 0
&& let (Some(prefix), Some(first)) = (removed_prefix, members.get_mut(0))
{
first.decor_mut().set_prefix(prefix);
}
let after = members_json(members);
work.write(path, document.to_string());
Ok(ChangeRecord {
operation: "remove_member",
address: format!("list={list_id}#/members"),
before: Some(before),
after: Some(after),
})
}
fn checked_member(value: &JsonValue) -> Result<()> {
match value {
JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::String(_) => Ok(()),
_ => Err(RototoError::new(
"list members are scalar values (string, int, number, or bool)",
)),
}
}
fn members_mut<'a>(document: &'a mut toml_edit::DocumentMut, path: &str) -> Result<&'a mut Array> {
let root = document.as_table_mut();
if root.get("members").is_none() {
root.insert("members", Item::Value(Value::Array(Array::new())));
}
match root.get_mut("members") {
Some(Item::Value(Value::Array(array))) => Ok(array),
_ => Err(RototoError::new(format!(
"members in {path} is not an array"
))),
}
}
fn members_json(members: &Array) -> JsonValue {
JsonValue::Array(members.iter().map(json_from_toml_value).collect())
}