use std::collections::{BTreeMap, BTreeSet};
use crate::ast::document::Document;
use crate::ast::value::dim_to_px;
use crate::diagnostics::Diagnostic;
pub(in crate::validate::check) fn check_variants(
doc: &Document,
page_ids: &BTreeSet<&str>,
page_node_ids: &BTreeMap<&str, BTreeSet<String>>,
diagnostics: &mut Vec<Diagnostic>,
) {
let mut seen_variant_ids: BTreeSet<&str> = BTreeSet::new();
for variant in &doc.variants {
if !seen_variant_ids.insert(variant.id.as_str()) {
diagnostics.push(Diagnostic::error(
"variant.duplicate_id",
format!(
"variant '{}': id is declared more than once; \
variant ids must be unique within the variants block",
variant.id
),
variant.source_span,
Some(variant.id.clone()),
));
}
let source_known = page_ids.contains(variant.source.as_str());
if !source_known {
diagnostics.push(Diagnostic::error(
"variant.unknown_source",
format!(
"variant '{}': source page '{}' does not exist in this document",
variant.id, variant.source
),
variant.source_span,
Some(variant.id.clone()),
));
}
match dim_to_px(variant.w.value, &variant.w.unit) {
None => {
diagnostics.push(Diagnostic::error(
"variant.invalid_dimension",
format!(
"variant '{}': width uses an unresolvable unit; \
allowed units are px and pt",
variant.id
),
variant.source_span,
Some(variant.id.clone()),
));
}
Some(px) if px <= 0.0 => {
diagnostics.push(Diagnostic::error(
"variant.invalid_dimension",
format!(
"variant '{}': width must be a strictly positive value (got {})",
variant.id, px
),
variant.source_span,
Some(variant.id.clone()),
));
}
Some(_) => {}
}
match dim_to_px(variant.h.value, &variant.h.unit) {
None => {
diagnostics.push(Diagnostic::error(
"variant.invalid_dimension",
format!(
"variant '{}': height uses an unresolvable unit; \
allowed units are px and pt",
variant.id
),
variant.source_span,
Some(variant.id.clone()),
));
}
Some(px) if px <= 0.0 => {
diagnostics.push(Diagnostic::error(
"variant.invalid_dimension",
format!(
"variant '{}': height must be a strictly positive value (got {})",
variant.id, px
),
variant.source_span,
Some(variant.id.clone()),
));
}
Some(_) => {}
}
if source_known {
let node_ids = page_node_ids.get(variant.source.as_str());
for ov in &variant.overrides {
let resolved = node_ids.is_some_and(|ids| ids.contains(&ov.node));
if !resolved {
diagnostics.push(Diagnostic::error(
"variant.override_unknown_node",
format!(
"variant '{}': override targets node '{}' which does not exist \
in source page '{}'",
variant.id, ov.node, variant.source
),
ov.source_span,
Some(variant.id.clone()),
));
}
}
}
for ov in &variant.overrides {
for prop_name in ov.unknown_props.keys() {
let hint = if prop_name == "id" {
format!(
"variant '{}': override for node '{}' has unknown property '{}'; \
did you mean `node=` instead of `id=`?",
variant.id, ov.node, prop_name
)
} else {
format!(
"variant '{}': override for node '{}' has unknown property '{}'; \
recognized override properties are: node, visible, text, fill, x, y, w, h",
variant.id, ov.node, prop_name
)
};
diagnostics.push(Diagnostic::warning(
"variant.override_unknown_property",
hint,
ov.source_span,
Some(variant.id.clone()),
));
}
}
}
}