use vize_carton::{Bump, BumpVec};
use super::transform::{find_bytes, find_matching_paren, rfind_byte};
pub(crate) fn apply_scoped_css<'a>(bump: &'a Bump, css: &str, scope_id: &str) -> &'a str {
let css_bytes = css.as_bytes();
let mut attr_selector = BumpVec::with_capacity_in(scope_id.len() + 2, bump);
attr_selector.push(b'[');
attr_selector.extend_from_slice(scope_id.as_bytes());
attr_selector.push(b']');
let attr_selector = bump.alloc_slice_copy(&attr_selector);
let mut output = BumpVec::with_capacity_in(css_bytes.len() * 2, bump);
let mut chars = css.char_indices().peekable();
let mut in_selector = true;
let mut in_string = false;
let mut string_char = b'"';
let mut in_comment = false;
let mut brace_depth = 0u32;
let mut last_selector_end = 0usize;
let mut in_at_rule = false;
let mut at_rule_depth = 0u32;
let mut pending_keyframes = false;
let mut keyframes_brace_depth: Option<u32> = None;
let mut saved_at_rule_depth: Option<u32> = None;
while let Some((i, c)) = chars.next() {
if in_comment {
if c == '*' {
if let Some(&(_, '/')) = chars.peek() {
chars.next();
in_comment = false;
}
}
continue;
}
if in_string {
if c as u8 == string_char {
let prev_byte = if i > 0 { css_bytes[i - 1] } else { 0 };
if prev_byte != b'\\' {
in_string = false;
}
}
if !in_selector && !in_at_rule {
output.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes());
}
continue;
}
match c {
'"' | '\'' => {
in_string = true;
string_char = c as u8;
if !in_selector && !in_at_rule {
output.push(c as u8);
}
}
'/' => {
if let Some(&(_, '*')) = chars.peek() {
chars.next();
in_comment = true;
} else if !in_selector && !in_at_rule {
output.push(b'/');
}
}
'@' if in_selector => {
in_at_rule = true;
in_selector = false;
let remaining = &css[i + 1..];
pending_keyframes = remaining.starts_with("keyframes")
|| remaining.starts_with("-webkit-keyframes")
|| remaining.starts_with("-moz-keyframes")
|| remaining.starts_with("-o-keyframes");
}
'@' => {
output.push(b'@');
}
';' if in_at_rule => {
let stmt = &css_bytes[last_selector_end..=i];
let stmt_str = unsafe { std::str::from_utf8_unchecked(stmt) }.trim();
output.extend_from_slice(stmt_str.as_bytes());
output.push(b'\n');
in_at_rule = false;
in_selector = true;
pending_keyframes = false;
last_selector_end = i + 1;
}
'{' => {
brace_depth += 1;
if in_at_rule {
in_at_rule = false;
let at_rule_header = &css_bytes[last_selector_end..i];
let at_rule_str =
unsafe { std::str::from_utf8_unchecked(at_rule_header) }.trim();
output.extend_from_slice(at_rule_str.as_bytes());
output.push(b'{');
if pending_keyframes {
saved_at_rule_depth = Some(at_rule_depth);
keyframes_brace_depth = Some(brace_depth);
pending_keyframes = false;
}
at_rule_depth = brace_depth;
in_selector = true;
last_selector_end = i + 1;
} else if keyframes_brace_depth.is_some_and(|d| brace_depth > d) {
let kf_part = &css_bytes[last_selector_end..i];
let kf_str = unsafe { std::str::from_utf8_unchecked(kf_part) }.trim();
output.extend_from_slice(kf_str.as_bytes());
output.push(b'{');
in_selector = false;
last_selector_end = i + 1;
} else if in_selector
&& (brace_depth == 1 || (at_rule_depth > 0 && brace_depth > at_rule_depth))
{
let selector_bytes = &css_bytes[last_selector_end..i];
let selector_str =
unsafe { std::str::from_utf8_unchecked(selector_bytes) }.trim();
scope_selector(&mut output, selector_str, attr_selector);
output.push(b'{');
in_selector = false;
last_selector_end = i + 1;
} else {
output.push(b'{');
}
}
'}' => {
brace_depth = brace_depth.saturating_sub(1);
output.push(b'}');
if keyframes_brace_depth.is_some_and(|d| brace_depth < d) {
keyframes_brace_depth = None;
if let Some(saved) = saved_at_rule_depth.take() {
at_rule_depth = saved;
}
}
if brace_depth == 0 {
in_selector = true;
last_selector_end = i + 1;
at_rule_depth = 0;
} else if at_rule_depth > 0 && brace_depth >= at_rule_depth {
in_selector = true;
last_selector_end = i + 1;
}
}
_ if in_selector || in_at_rule => {
}
_ => {
output.extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes());
}
}
}
if in_selector && last_selector_end < css_bytes.len() {
output.extend_from_slice(&css_bytes[last_selector_end..]);
}
unsafe { std::str::from_utf8_unchecked(bump.alloc_slice_copy(&output)) }
}
fn scope_selector(out: &mut BumpVec<u8>, selector: &str, attr_selector: &[u8]) {
if selector.is_empty() {
return;
}
if selector.starts_with('@') {
out.extend_from_slice(selector.as_bytes());
return;
}
let mut first = true;
for part in selector.split(',') {
if !first {
out.extend_from_slice(b", ");
}
first = false;
scope_single_selector(out, part.trim(), attr_selector);
}
}
fn scope_single_selector(out: &mut BumpVec<u8>, selector: &str, attr_selector: &[u8]) {
if selector.is_empty() {
return;
}
if let Some(pos) = selector.find(":deep(") {
transform_deep(out, selector, pos, attr_selector);
return;
}
if let Some(pos) = selector.find(":slotted(") {
transform_slotted(out, selector, pos, attr_selector);
return;
}
if let Some(pos) = selector.find(":global(") {
transform_global(out, selector, pos);
return;
}
let parts: Vec<&str> = selector.split_whitespace().collect();
if parts.is_empty() {
out.extend_from_slice(selector.as_bytes());
return;
}
for (i, part) in parts.iter().enumerate() {
if i > 0 {
out.push(b' ');
}
if i == parts.len() - 1 {
add_scope_to_element(out, part, attr_selector);
} else {
out.extend_from_slice(part.as_bytes());
}
}
}
pub(super) fn add_scope_to_element(out: &mut BumpVec<u8>, selector: &str, attr_selector: &[u8]) {
let bytes = selector.as_bytes();
if let Some(pseudo_pos) = find_bytes(bytes, b"::") {
out.extend_from_slice(&bytes[..pseudo_pos]);
out.extend_from_slice(attr_selector);
out.extend_from_slice(&bytes[pseudo_pos..]);
return;
}
if let Some(pseudo_pos) = rfind_byte(bytes, b':') {
if pseudo_pos > 0 && bytes[pseudo_pos - 1] != b'\\' {
out.extend_from_slice(&bytes[..pseudo_pos]);
out.extend_from_slice(attr_selector);
out.extend_from_slice(&bytes[pseudo_pos..]);
return;
}
}
out.extend_from_slice(bytes);
out.extend_from_slice(attr_selector);
}
pub(super) fn transform_deep(
out: &mut BumpVec<u8>,
selector: &str,
start: usize,
attr_selector: &[u8],
) {
let before = &selector[..start];
let after = &selector[start + 6..];
if let Some(end) = find_matching_paren(after) {
let inner = &after[..end];
let rest = &after[end + 1..];
if before.is_empty() {
out.extend_from_slice(attr_selector);
} else {
out.extend_from_slice(before.trim().as_bytes());
out.extend_from_slice(attr_selector);
}
out.push(b' ');
out.extend_from_slice(inner.as_bytes());
out.extend_from_slice(rest.as_bytes());
} else {
out.extend_from_slice(selector.as_bytes());
}
}
pub(super) fn transform_slotted(
out: &mut BumpVec<u8>,
selector: &str,
start: usize,
attr_selector: &[u8],
) {
let after = &selector[start + 9..];
if let Some(end) = find_matching_paren(after) {
let inner = &after.as_bytes()[..end];
let rest = &after.as_bytes()[end + 1..];
out.extend_from_slice(inner);
if attr_selector.last() == Some(&b']') {
out.extend_from_slice(&attr_selector[..attr_selector.len() - 1]);
out.extend_from_slice(b"-s]");
} else {
out.extend_from_slice(attr_selector);
out.extend_from_slice(b"-s");
}
out.extend_from_slice(rest);
} else {
out.extend_from_slice(selector.as_bytes());
}
}
pub(super) fn transform_global(out: &mut BumpVec<u8>, selector: &str, start: usize) {
let before = &selector[..start];
let after = &selector[start + 8..];
if let Some(end) = find_matching_paren(after) {
let inner = &after[..end];
let rest = &after[end + 1..];
out.extend_from_slice(before.as_bytes());
out.extend_from_slice(inner.as_bytes());
out.extend_from_slice(rest.as_bytes());
} else {
out.extend_from_slice(selector.as_bytes());
}
}