use std::collections::{BTreeMap, BTreeSet};
use zenith_core::{LibraryDef, ProvenanceDef, Token, TokenLiteral, TokenType, TokenValue};
use super::add::{
AddError, collect_all_ids, copy_tokens, load_pack_document, unique_id, unknown_package_error,
};
use super::registry::{LibraryPack, is_exportable_token};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokenAddOutcome {
pub pkg_id: String,
pub item: String,
pub token_id: String,
pub apply_property: &'static str,
pub dep_token_ids: Vec<String>,
pub provenance_id: String,
pub warnings: Vec<String>,
}
pub(crate) fn collect_filter_dep_ids(
filter_token: &Token,
pack_tokens: &[Token],
) -> BTreeSet<String> {
let mut seeds: Vec<String> = Vec::new();
if let TokenValue::Literal(TokenLiteral::Filter(lit)) = &filter_token.value {
for op in &lit.ops {
if op.kind == zenith_core::FilterKind::Duotone {
if let Some(s) = &op.shadow {
seeds.push(s.clone());
}
if let Some(h) = &op.highlight {
seeds.push(h.clone());
}
}
}
}
let mut deps: BTreeSet<String> = BTreeSet::new();
let mut stack: Vec<String> = seeds;
while let Some(id) = stack.pop() {
if !deps.insert(id.clone()) {
continue;
}
if let Some(tok) = pack_tokens.iter().find(|t| t.id == id)
&& let TokenValue::Reference { token_id } = &tok.value
{
stack.push(token_id.clone());
}
}
deps
}
pub fn materialize_token(
target: &mut zenith_core::Document,
packs: &[LibraryPack],
pkg_id: &str,
item: &str,
id_base: &str,
) -> Result<TokenAddOutcome, AddError> {
let pack = packs
.iter()
.find(|p| p.id == pkg_id)
.ok_or_else(|| unknown_package_error(pkg_id, packs))?;
let pack_doc = load_pack_document(pack)?;
let item_token = pack_doc
.tokens
.tokens
.iter()
.find(|t| t.id == item && is_exportable_token(&t.token_type))
.ok_or_else(|| {
let available: Vec<&str> = pack_doc
.tokens
.tokens
.iter()
.filter(|t| is_exportable_token(&t.token_type))
.map(|t| t.id.as_str())
.collect();
AddError::new(format!(
"unknown token item '{}' in package '{}' (available: {})",
item,
pkg_id,
if available.is_empty() {
"none".to_owned()
} else {
available.join(", ")
}
))
})?;
let mut warnings: Vec<String> = Vec::new();
let apply_property = match item_token.token_type {
TokenType::Mask => "mask",
TokenType::Color
| TokenType::Dimension
| TokenType::Number
| TokenType::FontFamily
| TokenType::FontWeight
| TokenType::Gradient
| TokenType::Shadow
| TokenType::Filter
| TokenType::Unknown(_) => "filter",
};
let dep_ids = collect_filter_dep_ids(item_token, &pack_doc.tokens.tokens);
if target.tokens.format.is_empty() {
target.tokens.format = pack_doc.tokens.format.clone();
}
let mut to_copy: Vec<Token> = Vec::with_capacity(dep_ids.len() + 1);
for dep_id in &dep_ids {
if let Some(tok) = pack_doc.tokens.tokens.iter().find(|t| &t.id == dep_id) {
to_copy.push(tok.clone());
}
}
to_copy.push(item_token.clone());
copy_tokens(&to_copy, &mut target.tokens.tokens, &mut warnings);
if !target.libraries.iter().any(|l| l.id == pkg_id) {
target.libraries.push(LibraryDef {
id: pkg_id.to_owned(),
version: pack.version.clone(),
hash: None,
source_span: None,
unknown_props: BTreeMap::new(),
});
}
let token_id = item.to_owned();
let provenance_id = if let Some(existing) = target
.provenance
.iter()
.find(|p| p.node == token_id && p.library == pkg_id && p.item.as_deref() == Some(item))
{
existing.id.clone()
} else {
let all_ids = collect_all_ids(target);
let provenance_id = unique_id(&format!("prov.{}", id_base), &all_ids);
target.provenance.push(ProvenanceDef {
id: provenance_id.clone(),
node: token_id.clone(),
library: pkg_id.to_owned(),
item: Some(item.to_owned()),
linked: Some(true),
source_span: None,
unknown_props: BTreeMap::new(),
});
provenance_id
};
Ok(TokenAddOutcome {
pkg_id: pkg_id.to_owned(),
item: item.to_owned(),
token_id,
apply_property,
dep_token_ids: dep_ids.into_iter().collect(),
provenance_id,
warnings,
})
}