use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use crate::dynamic::{DynamicNode, DynamicSchema};
use crate::transform::Step;
static SCHEMA_CACHE: OnceLock<Mutex<HashMap<String, Arc<DynamicSchema>>>> = OnceLock::new();
pub fn get_or_create_schema(schema_json: &str) -> Result<Arc<DynamicSchema>, String> {
let cache = SCHEMA_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
let mut guard = cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(existing) = guard.get(schema_json) {
return Ok(Arc::clone(existing));
}
let schema_val: serde_json::Value =
serde_json::from_str(schema_json).map_err(|e| format!("Invalid schema JSON: {e}"))?;
let schema =
DynamicSchema::from_json(&schema_val).map_err(|e| format!("Invalid schema: {e}"))?;
let arc = Arc::new(schema);
guard.insert(schema_json.to_owned(), Arc::clone(&arc));
Ok(arc)
}
pub struct Editor {
schema: Arc<DynamicSchema>,
doc: DynamicNode,
version: usize,
}
impl Editor {
pub fn new(schema_json: &str, doc_json: &str) -> Result<Self, String> {
let schema = get_or_create_schema(schema_json)?;
let doc_val: serde_json::Value =
serde_json::from_str(doc_json).map_err(|e| format!("Invalid document JSON: {e}"))?;
let doc = schema
.node_from_json(&doc_val)
.map_err(|e| format!("Invalid document: {e}"))?;
Ok(Editor {
schema,
doc,
version: 0,
})
}
pub fn apply_step(&mut self, step_json: &str) -> Result<bool, String> {
let result = {
let schema = &self.schema;
let doc = &self.doc;
schema.with_types(|| -> Result<Option<DynamicNode>, String> {
let step: Step<crate::dynamic::types::Dyn> = serde_json::from_str(step_json)
.map_err(|e| format!("Invalid step JSON: {e}"))?;
Ok(step.apply(doc).ok())
})
}?;
if let Some(new_doc) = result {
self.doc = new_doc;
self.version += 1;
Ok(true)
} else {
Ok(false)
}
}
pub fn apply_steps_json(&mut self, steps_json: &str) -> Result<bool, String> {
let steps: Vec<Step<crate::dynamic::types::Dyn>> = {
let schema = &self.schema;
schema.with_types(|| {
serde_json::from_str(steps_json).map_err(|e| format!("Invalid steps JSON: {e}"))
})?
};
let mut snapshot: Option<(DynamicNode, usize)> = if steps.len() > 1 {
Some((self.doc.clone(), self.version))
} else {
None
};
for step in steps {
let result = {
let schema = &self.schema;
let doc = &self.doc;
schema.with_types(|| step.apply(doc))
};
match result {
Ok(new_doc) => {
self.doc = new_doc;
self.version += 1;
}
Err(_) => {
if let Some((snap_doc, snap_version)) = snapshot.take() {
self.doc = snap_doc;
self.version = snap_version;
}
return Ok(false);
}
}
}
Ok(true)
}
pub fn apply_steps(&mut self, steps: &[String]) -> Result<bool, String> {
let parsed: Vec<Step<crate::dynamic::types::Dyn>> = {
let schema = &self.schema;
schema.with_types(|| {
steps
.iter()
.map(|s| {
serde_json::from_str::<Step<crate::dynamic::types::Dyn>>(s)
.map_err(|e| format!("Invalid step JSON: {e}"))
})
.collect::<Result<Vec<_>, _>>()
})?
};
let mut snapshot: Option<(DynamicNode, usize)> = if parsed.len() > 1 {
Some((self.doc.clone(), self.version))
} else {
None
};
for step in parsed {
let result = {
let schema = &self.schema;
let doc = &self.doc;
schema.with_types(|| step.apply(doc))
};
match result {
Ok(new_doc) => {
self.doc = new_doc;
self.version += 1;
}
Err(_) => {
if let Some((snap_doc, snap_version)) = snapshot.take() {
self.doc = snap_doc;
self.version = snap_version;
}
return Ok(false);
}
}
}
Ok(true)
}
pub fn reset(&mut self, doc_json: &str) -> Result<(), String> {
let doc_val: serde_json::Value =
serde_json::from_str(doc_json).map_err(|e| format!("Invalid document JSON: {e}"))?;
let doc = self
.schema
.node_from_json(&doc_val)
.map_err(|e| format!("Invalid document: {e}"))?;
self.doc = doc;
self.version = 0;
Ok(())
}
pub fn doc_json(&self, skip_defaults: bool) -> Result<String, String> {
let val = self.schema.with_types(|| self.doc.to_json(skip_defaults));
serde_json::to_string(&val).map_err(|e| format!("Serialization error: {e}"))
}
pub fn version(&self) -> usize {
self.version
}
pub fn schema(&self) -> &Arc<DynamicSchema> {
&self.schema
}
pub fn doc(&self) -> &DynamicNode {
&self.doc
}
pub fn doc_mut(&mut self) -> &mut DynamicNode {
&mut self.doc
}
}