use alloc::vec::Vec as StdVec;
use vize_l0::{Span, String, Vec};
use crate::expr::ExprRef;
use crate::op::{Attribute, BindOp, BindingOp, DynamicName, IfBranch, Op};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchKey {
pub kind: BranchKeyKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BranchKeyKind {
Static(Option<String>),
Dynamic {
source: String,
bind_index: Option<usize>,
},
}
impl BranchKey {
#[must_use]
pub fn collision_text(&self) -> Option<&str> {
match &self.kind {
BranchKeyKind::Static(value) => value.as_deref(),
BranchKeyKind::Dynamic { source, .. } if source.is_empty() => None,
BranchKeyKind::Dynamic { source, .. } => Some(source.as_str()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct IfFacts {
pub branches: StdVec<Option<BranchKey>>,
}
const _: () = {
const fn assert_owned<T: 'static>() {}
assert_owned::<BranchKey>();
assert_owned::<BranchKeyKind>();
assert_owned::<IfFacts>();
};
#[cfg(target_pointer_width = "64")]
const _: () = {
assert!(core::mem::size_of::<BranchKey>() == 48);
assert!(core::mem::size_of::<IfFacts>() == 24);
};
fn carrier_surface<'w, 'a>(
branch: &'w mut IfBranch<'a>,
) -> Option<(
&'w mut Vec<'a, Attribute<'a>>,
&'w mut Vec<'a, BindingOp<'a>>,
)> {
let branch_span = branch.span;
let [op] = branch.region.ops.as_mut_slice() else {
return None;
};
match op {
Op::Element(element) if element.span == branch_span => {
let element = &mut **element;
Some((&mut element.attributes, &mut element.bindings))
}
Op::Component(component) if component.span == branch_span => {
let component = &mut **component;
Some((&mut component.attributes, &mut component.bindings))
}
Op::Slot(slot) if slot.span == branch_span => {
let slot = &mut **slot;
Some((&mut slot.attributes, &mut slot.bindings))
}
Op::Element(_)
| Op::Component(_)
| Op::Slot(_)
| Op::Text(_)
| Op::Comment(_)
| Op::Interpolation(_)
| Op::JsxChildValue(_)
| Op::If(_)
| Op::For(_)
| Op::OriginalFor(_) => None,
}
}
fn key_bind<'b, 'a>(binding: &'b BindingOp<'a>) -> Option<&'b BindOp<'a>> {
match binding {
BindingOp::Bind(bind) => match bind.name {
Some(DynamicName::Static("key")) => Some(bind),
Some(DynamicName::Dynamic(ExprRef::Js(js))) if js.source == "key" => Some(bind),
_ => None,
},
BindingOp::On(_)
| BindingOp::Model(_)
| BindingOp::SlotContent(_)
| BindingOp::SlotsObject(_)
| BindingOp::VueDirective(_)
| BindingOp::VueCssBind(_)
| BindingOp::VueSync(_)
| BindingOp::VueSlotScope(_)
| BindingOp::VueOnce(_)
| BindingOp::VueMemo(_)
| BindingOp::VueShow(_)
| BindingOp::VueHtml(_)
| BindingOp::VueText(_)
| BindingOp::VueCloak(_) => None,
}
}
pub fn take_carrier_key(branch: &mut IfBranch<'_>) -> Option<BranchKey> {
let (attributes, bindings) = carrier_surface(branch)?;
let static_at = attributes
.iter()
.enumerate()
.find(|(_, attribute)| attribute.name == "key")
.map(|(index, attribute)| (attribute.span.start, index));
let dynamic_at = bindings
.iter()
.enumerate()
.find_map(|(index, binding)| key_bind(binding).map(|bind| (bind, index)));
match (static_at, dynamic_at) {
(Some((attr_start, index)), Some((bind, _))) if attr_start < bind.span.start => {
Some(take_static(attributes, index))
}
(Some((_, index)), None) => Some(take_static(attributes, index)),
(_, Some((bind, index))) => Some(read_dynamic(bind, index)),
(None, None) => None,
}
}
fn take_static<'a>(attributes: &mut Vec<'a, Attribute<'a>>, index: usize) -> BranchKey {
let attribute = attributes.remove(index);
BranchKey {
kind: BranchKeyKind::Static(attribute.value.map(String::from)),
span: attribute.span,
}
}
fn read_dynamic(bind: &BindOp<'_>, index: usize) -> BranchKey {
let source = bind
.value
.as_ref()
.map(|value| String::from(value.source()))
.unwrap_or_default();
BranchKey {
kind: BranchKeyKind::Dynamic {
source,
bind_index: Some(index),
},
span: bind.span,
}
}