use serde_json::{json, Map, Value};
use super::{
all_blocks, blocks_by_tag, get_block, variants_of, BlockFacts, BlockpediaError, Result,
BLOCKS, BLOCK_TAGS,
};
pub const MAX_STATE_COMBINATIONS: usize = 4096;
fn normalize_mc_name(name: &str) -> String {
if name.contains(':') {
name.to_string()
} else {
format!("minecraft:{name}")
}
}
fn get_block_normalized(id: &str) -> Option<&'static BlockFacts> {
get_block(&normalize_mc_name(id))
}
fn facts_value(f: &BlockFacts) -> Value {
let mut properties = Map::new();
for (name, values) in f.properties {
properties.insert(name.to_string(), json!(values));
}
let mut default_state = Map::new();
for (name, value) in f.default_state {
default_state.insert(name.to_string(), Value::String(value.to_string()));
}
json!({
"id": f.id,
"kind": f.kind,
"base_block": f.base_block,
"tags": f.tags,
"full_cube": f.full_cube,
"transparent": f.transparent,
"has_block_entity": f.has_block_entity,
"emit_light": f.emit_light,
"color": f.extras.color.as_ref().map(|c| c.rgb),
"properties": properties,
"default_state": default_state,
})
}
pub fn block_facts_json(id: &str) -> Option<String> {
get_block_normalized(id).map(|f| facts_value(f).to_string())
}
pub fn all_block_ids_json() -> String {
let mut ids: Vec<&str> = BLOCKS.keys().copied().collect();
ids.sort_unstable();
json!(ids).to_string()
}
pub fn block_ids_by_tag_json(tag: &str) -> String {
let mut ids: Vec<&str> = blocks_by_tag(tag).map(|b| b.id).collect();
ids.sort_unstable();
json!(ids).to_string()
}
pub fn block_ids_by_kind_json(kind: &str) -> String {
let key = normalize_mc_name(kind);
let mut ids: Vec<&str> = all_blocks()
.filter(|b| b.kind == key)
.map(|b| b.id)
.collect();
ids.sort_unstable();
json!(ids).to_string()
}
pub fn variants_of_ids_json(base_id: &str) -> Option<String> {
let base = get_block_normalized(base_id)?;
let mut ids = vec![base.id];
ids.extend(variants_of(base.id).iter().map(|b| b.id));
Some(json!(ids).to_string())
}
pub fn all_tags_json() -> String {
let mut tags: Vec<&str> = BLOCK_TAGS.keys().copied().collect();
tags.sort_unstable();
json!(tags).to_string()
}
pub fn block_states_json_with_limit(id: &str, limit: usize) -> Result<String> {
let f = get_block_normalized(id).ok_or_else(|| BlockpediaError::block_not_found(id))?;
let combinations: usize = f
.properties
.iter()
.map(|(_, values)| values.len().max(1))
.product();
if combinations > limit {
return Err(BlockpediaError::custom(format!(
"{}: {} property-value combinations exceed the limit of {}",
f.id, combinations, limit
)));
}
let mut states: Vec<Map<String, Value>> = vec![Map::new()];
for (name, values) in f.properties {
let mut next = Vec::with_capacity(states.len() * values.len());
for state in &states {
for value in *values {
let mut s = state.clone();
s.insert(name.to_string(), Value::String(value.to_string()));
next.push(s);
}
}
states = next;
}
Ok(Value::Array(states.into_iter().map(Value::Object).collect()).to_string())
}
pub fn block_states_json(id: &str) -> Result<String> {
block_states_json_with_limit(id, MAX_STATE_COMBINATIONS)
}
pub fn block_count() -> usize {
BLOCKS.len()
}