use fhir_types::codec::Object;
use fhir_types::codec::Value;
use crate::dataset::constraint::CodingPattern;
use crate::dataset::constraint::Pattern;
use crate::receive::conform::Unread;
pub(super) type Occurrence<'d> = (String, Option<&'d Value>);
pub(super) const READ: &[&str] = &["bdl-7", "bdl-8", "bdl-9", "bdl-10", "bdl-11"];
pub(super) fn entry_reference_rule(expression: &str) -> Option<(Vec<String>, String)> {
let compact: String = expression.chars().filter(|c| !c.is_whitespace()).collect();
let mut types = Vec::new();
for part in compact.split("resource.is(").skip(1) {
let (kind, _) = part.split_once(')')?;
let kind = kind.to_owned();
if !types.contains(&kind) {
types.push(kind);
}
}
let field = compact
.strip_suffix(".reference.exists())")?
.rsplit_once(".all(resource.")?
.1
.to_owned();
if types.is_empty() || !field.chars().all(|c| c.is_ascii_alphanumeric()) {
return None;
}
let alternatives = types
.iter()
.map(|kind| format!("resource.is({kind})"))
.collect::<Vec<_>>()
.join("or");
let rebuilt = format!(
"entry.where({alternatives}).empty()orentry.where({alternatives}).all(resource.{field}.reference.exists())"
);
(rebuilt == compact).then_some((types, field))
}
pub(super) fn objects<'d>(found: &[Occurrence<'d>]) -> Vec<(String, &'d Object)> {
found
.iter()
.filter_map(|(at, value)| {
value
.and_then(Value::as_object)
.map(|object| (at.clone(), object))
})
.collect()
}
pub(super) fn last_segment(id: &str) -> &str {
id.rsplit_once('.').map_or(id, |(_, last)| last)
}
pub(super) fn occurrences<'d>(
node: &'d Object,
location: &str,
name: &str,
types: &[String],
) -> Vec<Occurrence<'d>> {
let keys: Vec<String> = match name.strip_suffix("[x]") {
Some(stem) => types
.iter()
.map(|code| format!("{stem}{}", capitalized(code)))
.collect(),
None => vec![name.to_owned()],
};
let mut found = Vec::new();
for key in keys {
let at = format!("{location}.{key}");
let values = node.get(&key);
let siblings = node.get(&format!("_{key}"));
match (values, siblings) {
(Some(Value::Array(items)), _) => {
found.extend(items.iter().enumerate().map(|(index, item)| {
let item = (!item.is_null()).then_some(item);
(format!("{at}[{index}]"), item)
}));
}
(Some(value), _) => found.push((at, Some(value))),
(None, Some(Value::Array(items))) => {
found.extend((0..items.len()).map(|index| (format!("{at}[{index}]"), None)));
}
(None, Some(_)) => found.push((at, None)),
(None, None) => {}
}
}
found
}
fn capitalized(code: &str) -> String {
let mut characters = code.chars();
characters.next().map_or_else(String::new, |first| {
first.to_uppercase().chain(characters).collect()
})
}
pub(super) fn follow<'d>(value: &'d Value, path: &str) -> Option<&'d Value> {
path.split('.').try_fold(value, |at, key| {
let next = at.get(key)?;
match next {
Value::Array(items) => items.first(),
other => Some(other),
}
})
}
pub(super) fn matches(pattern: &Pattern, value: Option<&Value>) -> Option<bool> {
match pattern {
Pattern::Primitive(text) => Some(value.and_then(Value::as_str) == Some(text.as_str())),
Pattern::Concept { codings, text } => {
let Some(value) = value else {
return Some(false);
};
let carried: &[Value] = value
.get("coding")
.and_then(Value::as_array)
.unwrap_or_default();
let codes = codings
.iter()
.all(|wanted| carried.iter().any(|coding| coding_matches(wanted, coding)));
let texts = text
.as_deref()
.is_none_or(|wanted| value.get("text").and_then(Value::as_str) == Some(wanted));
Some(codes && texts)
}
Pattern::Coding(wanted) => Some(value.is_some_and(|coding| coding_matches(wanted, coding))),
_ => None,
}
}
fn coding_matches(wanted: &CodingPattern, coding: &Value) -> bool {
[
("system", wanted.system()),
("version", wanted.version()),
("code", wanted.code()),
("display", wanted.display()),
]
.into_iter()
.all(|(key, required)| {
required.is_none_or(|required| coding.get(key).and_then(Value::as_str) == Some(required))
})
}
pub(super) fn unread_pattern(pattern: &Pattern) -> Unread {
match pattern {
Pattern::Unread { key } => Unread::Pattern(key.clone()),
_ => Unread::Pattern(String::from("pattern")),
}
}