use serde_json::Value as Json;
use crate::store::Store;
use crate::store::hierarchy::Hierarchy;
use crate::store::{NodeKind, SYSTEM_TAG_WORLD};
pub fn chapter(store: &Store, chapter_head: &str) -> Option<Json> {
let h = Hierarchy::load(store).ok()?;
let book = h
.iter()
.find(|n| n.kind == NodeKind::Book && n.system_tag.as_deref() == Some(SYSTEM_TAG_WORLD))?;
let head = chapter_head.trim();
let chapter = h.children_of(Some(book.id)).into_iter().find(|n| {
n.kind == NodeKind::Chapter
&& (n.title.eq_ignore_ascii_case(head) || n.slug.eq_ignore_ascii_case(head))
});
let mut map = serde_json::Map::new();
if let Some(chapter) = &chapter {
for pid in h.collect_subtree(chapter.id) {
let Some(node) = h.get(pid) else { continue };
if node.kind != NodeKind::Paragraph {
continue;
}
let Ok(Some(bytes)) = store.get_content(pid) else { continue };
if let Ok(Json::Object(obj)) = serde_json::from_slice::<Json>(&bytes) {
for (k, v) in obj {
map.insert(k, v);
}
}
}
}
if !map.is_empty() {
return Some(Json::Object(map));
}
recompile_chapter(store, head)
}
fn recompile_chapter(store: &Store, chapter_head: &str) -> Option<Json> {
use crate::world::compile::{
compile_astronomy, compile_climate, compile_demographics, compile_geology, compile_hydrology,
};
use crate::world::types::WorldDefinition;
let raw = std::fs::read_to_string(store.project_root().join("world.hjson")).ok()?;
let def = WorldDefinition::from_hjson(&raw).ok()?;
let value = match chapter_head.to_ascii_lowercase().as_str() {
"astronomy" => serde_json::to_value(compile_astronomy(&def.astronomy)),
"geology" => serde_json::to_value(compile_geology(&def)),
"climate" => {
let astro = compile_astronomy(&def.astronomy);
let geo = compile_geology(&def);
serde_json::to_value(compile_climate(&def, &astro, &geo))
}
"hydrology" => {
let astro = compile_astronomy(&def.astronomy);
let geo = compile_geology(&def);
let clim = compile_climate(&def, &astro, &geo);
serde_json::to_value(compile_hydrology(&geo, &clim))
}
"demographics" => {
let astro = compile_astronomy(&def.astronomy);
let geo = compile_geology(&def);
let clim = compile_climate(&def, &astro, &geo);
let hydro = compile_hydrology(&geo, &clim);
serde_json::to_value(compile_demographics(&clim, &hydro))
}
_ => return None,
};
match value.ok()? {
obj @ Json::Object(_) => Some(obj),
_ => None,
}
}
pub fn lookup(store: &Store, path: &str) -> Option<Json> {
let segs: Vec<&str> = path.split('/').map(str::trim).filter(|s| !s.is_empty()).collect();
let (head, rest) = segs.split_first()?;
let chapter = chapter(store, head)?;
index_json(&chapter, rest)
}
fn index_json<'a>(root: &'a Json, path: &[&str]) -> Option<Json> {
let mut cur = root;
for seg in path {
cur = match cur {
Json::Object(map) => map.get(*seg)?,
Json::Array(arr) => {
let i: usize = seg.parse().ok()?;
arr.get(i)?
}
_ => return None,
};
}
Some(cur.clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_object_then_array_then_field() {
let root = serde_json::json!({
"year_length_planet_days": 412.3,
"insolation_bands": [ { "annual_mean": 18.7 }, { "annual_mean": 9.1 } ],
});
assert_eq!(index_json(&root, &["year_length_planet_days"]).unwrap(), serde_json::json!(412.3));
assert_eq!(index_json(&root, &["insolation_bands", "1", "annual_mean"]).unwrap(), serde_json::json!(9.1));
assert!(index_json(&root, &["missing"]).is_none());
assert!(index_json(&root, &["insolation_bands", "9"]).is_none());
assert_eq!(index_json(&root, &[]).unwrap(), root);
}
}