Skip to main content

omena_scss_eval/
native_css.rs

1use cstree::text::{TextRange, TextSize};
2use omena_abstract_value::{
3    CssValueValidationClassV0, RegisteredPropertySyntaxV0, parse_registered_property_syntax_v0,
4    validate_registered_property_value_v0,
5};
6use omena_cascade::{
7    StaticSupportsAssumptionV0, StaticSupportsEvalVerdictV0, StaticSupportsEvalWitnessV0,
8    evaluate_static_supports_condition,
9};
10use omena_parser::{LexedToken, StyleDialect, lex};
11use omena_syntax::{
12    SyntaxKind,
13    ident::{AuthoredPropertyTextV0, CanonicalCustomPropertyNameV0, PropertyNameV0},
14};
15use omena_transform_cst::{IrNodeKindV0, IrNodeV0, TransformIrV0};
16use serde::Serialize;
17use std::collections::BTreeMap;
18
19use crate::control_flow::{analyze_scss_control_flow_values, build_scss_control_flow_graph};
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct OmenaScssEvalNativeCssFunctionSurfaceV0 {
24    pub schema_version: &'static str,
25    pub product: &'static str,
26    pub mode: &'static str,
27    pub dialect: &'static str,
28    pub function_count: usize,
29    pub parameter_count: usize,
30    pub typed_parameter_count: usize,
31    pub supported_parameter_syntax_count: usize,
32    pub unsupported_parameter_syntax_count: usize,
33    pub result_count: usize,
34    pub functions: Vec<OmenaScssEvalNativeCssFunctionV0>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct OmenaScssEvalNativeCssFunctionV0 {
40    pub name: String,
41    pub source_span_start: usize,
42    pub source_span_end: usize,
43    pub name_span_start: usize,
44    pub name_span_end: usize,
45    pub return_syntax_source: Option<String>,
46    pub return_syntax: Option<RegisteredPropertySyntaxV0>,
47    pub parameter_count: usize,
48    pub typed_parameter_count: usize,
49    pub result_count: usize,
50    pub parameters: Vec<OmenaScssEvalNativeCssFunctionParameterV0>,
51    pub results: Vec<OmenaScssEvalNativeCssFunctionResultV0>,
52}
53
54#[derive(Debug, Clone, Serialize)]
55#[serde(rename_all = "camelCase")]
56pub struct OmenaScssEvalNativeCssFunctionParameterV0 {
57    pub name: AuthoredPropertyTextV0,
58    pub property_key: CanonicalCustomPropertyNameV0,
59    pub source_span_start: usize,
60    pub source_span_end: usize,
61    pub name_span_start: usize,
62    pub name_span_end: usize,
63    pub syntax_source: Option<String>,
64    pub syntax: Option<RegisteredPropertySyntaxV0>,
65    pub default_value: Option<String>,
66}
67
68impl PartialEq for OmenaScssEvalNativeCssFunctionParameterV0 {
69    fn eq(&self, other: &Self) -> bool {
70        self.property_key == other.property_key
71            && self.source_span_start == other.source_span_start
72            && self.source_span_end == other.source_span_end
73            && self.name_span_start == other.name_span_start
74            && self.name_span_end == other.name_span_end
75            && self.syntax_source == other.syntax_source
76            && self.syntax == other.syntax
77            && self.default_value == other.default_value
78    }
79}
80
81impl Eq for OmenaScssEvalNativeCssFunctionParameterV0 {}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OmenaScssEvalNativeCssFunctionResultV0 {
86    pub value: String,
87    pub source_span_start: usize,
88    pub source_span_end: usize,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct OmenaScssEvalNativeCssIfFunctionDecisionSurfaceV0 {
94    pub schema_version: &'static str,
95    pub product: &'static str,
96    pub mode: &'static str,
97    pub dialect: &'static str,
98    pub function_count: usize,
99    pub foldable_function_count: usize,
100    pub preserved_function_count: usize,
101    pub static_supports_branch_count: usize,
102    pub runtime_branch_count: usize,
103    pub functions: Vec<OmenaScssEvalNativeCssIfFunctionDecisionV0>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
107#[serde(rename_all = "camelCase")]
108pub struct OmenaScssEvalNativeCssIfFunctionDecisionV0 {
109    pub source_span_start: usize,
110    pub source_span_end: usize,
111    pub branch_count: usize,
112    pub decision: &'static str,
113    pub reason: &'static str,
114    pub selected_branch_index: Option<usize>,
115    pub selected_value: Option<String>,
116    pub branches: Vec<OmenaScssEvalNativeCssIfFunctionBranchV0>,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
120#[serde(rename_all = "camelCase")]
121pub struct OmenaScssEvalNativeCssIfFunctionBranchV0 {
122    pub branch_index: usize,
123    pub condition: String,
124    pub condition_kind: &'static str,
125    pub verdict: &'static str,
126    pub value: Option<String>,
127    pub source_span_start: usize,
128    pub source_span_end: usize,
129    pub supports_witness: Option<StaticSupportsEvalWitnessV0>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
133#[serde(rename_all = "camelCase")]
134pub struct OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0 {
135    pub schema_version: &'static str,
136    pub product: &'static str,
137    pub mode: &'static str,
138    pub dialect: &'static str,
139    pub call_count: usize,
140    pub foldable_call_count: usize,
141    pub preserved_call_count: usize,
142    pub structural_error_count: usize,
143    pub runtime_dependent_call_count: usize,
144    pub missing_result_count: usize,
145    pub calls: Vec<OmenaScssEvalNativeCssFunctionCallEvaluationV0>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct OmenaScssEvalNativeCssFunctionCallEvaluationV0 {
151    pub name: String,
152    pub source_span_start: usize,
153    pub source_span_end: usize,
154    pub argument_count: usize,
155    pub matched_function_count: usize,
156    pub matched_function_source_span_start: Option<usize>,
157    pub matched_function_source_span_end: Option<usize>,
158    pub decision: &'static str,
159    pub reason: &'static str,
160    pub evaluated_value: Option<String>,
161    pub arguments: Vec<OmenaScssEvalNativeCssFunctionCallArgumentV0>,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
165#[serde(rename_all = "camelCase")]
166pub struct OmenaScssEvalNativeCssFunctionCallArgumentV0 {
167    pub argument_index: usize,
168    pub value: String,
169    pub source_span_start: usize,
170    pub source_span_end: usize,
171    pub static_value: bool,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
175#[serde(rename_all = "camelCase")]
176pub struct OmenaScssEvalNativeCssStaticEditPlanV0 {
177    pub schema_version: &'static str,
178    pub product: &'static str,
179    pub mode: &'static str,
180    pub dialect: &'static str,
181    pub edit_count: usize,
182    pub when_rule_edit_count: usize,
183    pub if_rule_edit_count: usize,
184    pub if_function_edit_count: usize,
185    pub function_call_edit_count: usize,
186    pub output_changed: bool,
187    pub edited_css: String,
188    pub edits: Vec<OmenaScssEvalNativeCssStaticEditV0>,
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
192#[serde(rename_all = "camelCase")]
193pub struct OmenaScssEvalNativeCssStaticEditV0 {
194    pub start: usize,
195    pub end: usize,
196    pub replacement: String,
197    pub edit_kind: &'static str,
198}
199
200pub fn summarize_native_css_function_surface(
201    source: &str,
202    dialect: StyleDialect,
203) -> Option<OmenaScssEvalNativeCssFunctionSurfaceV0> {
204    if dialect != StyleDialect::Css {
205        return None;
206    }
207    let lexed = lex(source, dialect);
208    let tokens = lexed.tokens();
209    let functions = collect_native_css_functions(source, tokens);
210    let parameter_count = functions
211        .iter()
212        .map(|function| function.parameter_count)
213        .sum();
214    let typed_parameter_count = functions
215        .iter()
216        .map(|function| function.typed_parameter_count)
217        .sum();
218    let supported_parameter_syntax_count = functions
219        .iter()
220        .flat_map(|function| function.parameters.iter())
221        .filter(|parameter| parameter_syntax_is_supported(parameter.syntax.as_ref()))
222        .count();
223    let unsupported_parameter_syntax_count = functions
224        .iter()
225        .flat_map(|function| function.parameters.iter())
226        .filter(|parameter| {
227            matches!(
228                parameter.syntax,
229                Some(RegisteredPropertySyntaxV0::Unsupported { .. })
230            )
231        })
232        .count();
233    let result_count = functions.iter().map(|function| function.result_count).sum();
234
235    Some(OmenaScssEvalNativeCssFunctionSurfaceV0 {
236        schema_version: "0",
237        product: "omena-scss-eval.native-css-function-surface",
238        mode: "recognitionOnly",
239        dialect: "css",
240        function_count: functions.len(),
241        parameter_count,
242        typed_parameter_count,
243        supported_parameter_syntax_count,
244        unsupported_parameter_syntax_count,
245        result_count,
246        functions,
247    })
248}
249
250pub fn summarize_native_css_function_call_evaluations(
251    source: &str,
252    dialect: StyleDialect,
253) -> Option<OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0> {
254    if dialect != StyleDialect::Css {
255        return None;
256    }
257    let lexed = lex(source, dialect);
258    let tokens = lexed.tokens();
259    let functions = collect_native_css_functions(source, tokens);
260    let calls = collect_native_css_function_call_evaluations(source, tokens, &functions);
261    let call_count = calls.len();
262    let foldable_call_count = calls
263        .iter()
264        .filter(|call| call.decision == "foldToStaticValue")
265        .count();
266    let preserved_call_count = calls
267        .iter()
268        .filter(|call| call.decision == "preserveVerbatim")
269        .count();
270    let structural_error_count = calls
271        .iter()
272        .filter(|call| call.decision == "structuralError")
273        .count();
274    let runtime_dependent_call_count = calls
275        .iter()
276        .filter(|call| call.reason.contains("runtime") || call.reason.contains("cascade"))
277        .count();
278    let missing_result_count = calls
279        .iter()
280        .filter(|call| call.reason == "function has no result declaration")
281        .count();
282
283    Some(OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0 {
284        schema_version: "0",
285        product: "omena-scss-eval.native-css-function-call-evaluations",
286        mode: "oracleOnlyPruneButKeep",
287        dialect: "css",
288        call_count,
289        foldable_call_count,
290        preserved_call_count,
291        structural_error_count,
292        runtime_dependent_call_count,
293        missing_result_count,
294        calls,
295    })
296}
297
298pub fn summarize_native_css_static_edit_plan(
299    source: &str,
300    dialect: StyleDialect,
301) -> Option<OmenaScssEvalNativeCssStaticEditPlanV0> {
302    if dialect != StyleDialect::Css {
303        return None;
304    }
305
306    let if_function_decisions = summarize_native_css_if_function_decisions(source, dialect)?;
307    let function_call_evaluations =
308        summarize_native_css_function_call_evaluations(source, dialect)?;
309    let mut edits = Vec::new();
310    let lexed = lex(source, dialect);
311    let tokens = lexed.tokens();
312    let functions = collect_native_css_functions(source, tokens);
313    let when_rule_truthiness_by_start = native_css_branch_truthiness_by_start(source, "@when");
314    let if_rule_truthiness_by_start = native_css_branch_truthiness_by_start(source, "@if");
315    let if_function_truthiness_by_start = native_css_branch_truthiness_by_start(source, "if()");
316    edits.extend(native_css_conditional_rule_static_edits(
317        source,
318        tokens,
319        "@when",
320        "whenRuleBranchFold",
321        &when_rule_truthiness_by_start,
322    ));
323    edits.extend(native_css_conditional_rule_static_edits(
324        source,
325        tokens,
326        "@if",
327        "ifRuleBranchFold",
328        &if_rule_truthiness_by_start,
329    ));
330    edits.extend(native_css_if_function_static_edits(
331        &if_function_decisions,
332        &if_function_truthiness_by_start,
333        &functions,
334    ));
335    edits.extend(native_css_function_call_static_edits(
336        &function_call_evaluations,
337        &functions,
338    ));
339    let edits = normalize_native_css_static_edits(source, edits)?;
340    let edited_css = apply_native_css_static_edits(source, &edits);
341    let edit_count = edits.len();
342    let when_rule_edit_count = edits
343        .iter()
344        .filter(|edit| edit.edit_kind == "whenRuleBranchFold")
345        .count();
346    let if_rule_edit_count = edits
347        .iter()
348        .filter(|edit| edit.edit_kind == "ifRuleBranchFold")
349        .count();
350    let if_function_edit_count = edits
351        .iter()
352        .filter(|edit| edit.edit_kind == "ifFunctionValueFold")
353        .count();
354    let function_call_edit_count = edits
355        .iter()
356        .filter(|edit| edit.edit_kind == "functionCallValueFold")
357        .count();
358    let output_changed = edited_css != source;
359
360    Some(OmenaScssEvalNativeCssStaticEditPlanV0 {
361        schema_version: "0",
362        product: "omena-scss-eval.native-css-static-edit-plan",
363        mode: "staticSubsetPruneButKeep",
364        dialect: "css",
365        edit_count,
366        when_rule_edit_count,
367        if_rule_edit_count,
368        if_function_edit_count,
369        function_call_edit_count,
370        output_changed,
371        edited_css,
372        edits,
373    })
374}
375
376pub fn summarize_native_css_static_edit_plan_from_transform_ir(
377    ir: &TransformIrV0,
378    dialect: StyleDialect,
379) -> Option<OmenaScssEvalNativeCssStaticEditPlanV0> {
380    if dialect != StyleDialect::Css {
381        return None;
382    }
383
384    let source = ir.source_text();
385    let functions = collect_native_css_functions_from_transform_ir(source, dialect, ir);
386    let if_function_decisions = native_css_if_function_decision_surface(
387        collect_native_css_if_function_decisions_from_transform_ir(source, dialect, ir),
388    );
389    let function_call_evaluations = native_css_function_call_evaluation_surface(
390        collect_native_css_function_call_evaluations_from_transform_ir(
391            source, dialect, ir, &functions,
392        ),
393    );
394    let when_rule_truthiness_by_start = native_css_branch_truthiness_by_start(source, "@when");
395    let if_rule_truthiness_by_start = native_css_branch_truthiness_by_start(source, "@if");
396    let if_function_truthiness_by_start = native_css_branch_truthiness_by_start(source, "if()");
397    let mut edits = native_css_conditional_rule_static_edits_from_transform_ir(
398        source,
399        dialect,
400        ir,
401        "@when",
402        "whenRuleBranchFold",
403        &when_rule_truthiness_by_start,
404    );
405    edits.extend(native_css_conditional_rule_static_edits_from_transform_ir(
406        source,
407        dialect,
408        ir,
409        "@if",
410        "ifRuleBranchFold",
411        &if_rule_truthiness_by_start,
412    ));
413    edits.extend(native_css_if_function_static_edits(
414        &if_function_decisions,
415        &if_function_truthiness_by_start,
416        &functions,
417    ));
418    edits.extend(native_css_function_call_static_edits(
419        &function_call_evaluations,
420        &functions,
421    ));
422    native_css_static_edit_plan_from_edits(source, edits)
423}
424
425fn native_css_if_function_decision_surface(
426    functions: Vec<OmenaScssEvalNativeCssIfFunctionDecisionV0>,
427) -> OmenaScssEvalNativeCssIfFunctionDecisionSurfaceV0 {
428    let function_count = functions.len();
429    let foldable_function_count = functions
430        .iter()
431        .filter(|function| function.decision == "foldToStaticValue")
432        .count();
433    let preserved_function_count = function_count.saturating_sub(foldable_function_count);
434    let static_supports_branch_count = functions
435        .iter()
436        .flat_map(|function| function.branches.iter())
437        .filter(|branch| branch.condition_kind == "supports")
438        .count();
439    let runtime_branch_count = functions
440        .iter()
441        .flat_map(|function| function.branches.iter())
442        .filter(|branch| matches!(branch.condition_kind, "media" | "style"))
443        .count();
444
445    OmenaScssEvalNativeCssIfFunctionDecisionSurfaceV0 {
446        schema_version: "0",
447        product: "omena-scss-eval.native-css-if-function-decisions",
448        mode: "oracleOnlyPruneButKeep",
449        dialect: "css",
450        function_count,
451        foldable_function_count,
452        preserved_function_count,
453        static_supports_branch_count,
454        runtime_branch_count,
455        functions,
456    }
457}
458
459fn native_css_function_call_evaluation_surface(
460    calls: Vec<OmenaScssEvalNativeCssFunctionCallEvaluationV0>,
461) -> OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0 {
462    let call_count = calls.len();
463    let foldable_call_count = calls
464        .iter()
465        .filter(|call| call.decision == "foldToStaticValue")
466        .count();
467    let preserved_call_count = calls
468        .iter()
469        .filter(|call| call.decision == "preserveVerbatim")
470        .count();
471    let structural_error_count = calls
472        .iter()
473        .filter(|call| call.decision == "structuralError")
474        .count();
475    let runtime_dependent_call_count = calls
476        .iter()
477        .filter(|call| call.reason.contains("runtime") || call.reason.contains("cascade"))
478        .count();
479    let missing_result_count = calls
480        .iter()
481        .filter(|call| call.reason == "function has no result declaration")
482        .count();
483
484    OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0 {
485        schema_version: "0",
486        product: "omena-scss-eval.native-css-function-call-evaluations",
487        mode: "oracleOnlyPruneButKeep",
488        dialect: "css",
489        call_count,
490        foldable_call_count,
491        preserved_call_count,
492        structural_error_count,
493        runtime_dependent_call_count,
494        missing_result_count,
495        calls,
496    }
497}
498
499fn native_css_static_edit_plan_from_edits(
500    source: &str,
501    edits: Vec<OmenaScssEvalNativeCssStaticEditV0>,
502) -> Option<OmenaScssEvalNativeCssStaticEditPlanV0> {
503    let edits = normalize_native_css_static_edits(source, edits)?;
504    let edited_css = apply_native_css_static_edits(source, &edits);
505    let edit_count = edits.len();
506    let when_rule_edit_count = edits
507        .iter()
508        .filter(|edit| edit.edit_kind == "whenRuleBranchFold")
509        .count();
510    let if_rule_edit_count = edits
511        .iter()
512        .filter(|edit| edit.edit_kind == "ifRuleBranchFold")
513        .count();
514    let if_function_edit_count = edits
515        .iter()
516        .filter(|edit| edit.edit_kind == "ifFunctionValueFold")
517        .count();
518    let function_call_edit_count = edits
519        .iter()
520        .filter(|edit| edit.edit_kind == "functionCallValueFold")
521        .count();
522    let output_changed = edited_css != source;
523
524    Some(OmenaScssEvalNativeCssStaticEditPlanV0 {
525        schema_version: "0",
526        product: "omena-scss-eval.native-css-static-edit-plan",
527        mode: "staticSubsetPruneButKeep",
528        dialect: "css",
529        edit_count,
530        when_rule_edit_count,
531        if_rule_edit_count,
532        if_function_edit_count,
533        function_call_edit_count,
534        output_changed,
535        edited_css,
536        edits,
537    })
538}
539
540pub fn summarize_native_css_if_function_decisions(
541    source: &str,
542    dialect: StyleDialect,
543) -> Option<OmenaScssEvalNativeCssIfFunctionDecisionSurfaceV0> {
544    if dialect != StyleDialect::Css {
545        return None;
546    }
547    let lexed = lex(source, dialect);
548    let tokens = lexed.tokens();
549    Some(native_css_if_function_decision_surface(
550        collect_native_css_if_function_decisions(source, tokens),
551    ))
552}
553
554fn collect_native_css_functions(
555    source: &str,
556    tokens: &[LexedToken],
557) -> Vec<OmenaScssEvalNativeCssFunctionV0> {
558    tokens
559        .iter()
560        .enumerate()
561        .filter_map(|(index, token)| {
562            (token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case("@function"))
563                .then(|| collect_native_css_function(source, tokens, index))
564                .flatten()
565        })
566        .collect()
567}
568
569fn collect_native_css_if_function_decisions(
570    source: &str,
571    tokens: &[LexedToken],
572) -> Vec<OmenaScssEvalNativeCssIfFunctionDecisionV0> {
573    tokens
574        .iter()
575        .enumerate()
576        .filter_map(|(index, token)| {
577            (token.kind == SyntaxKind::Ident && token.text.eq_ignore_ascii_case("if"))
578                .then(|| collect_native_css_if_function_decision(source, tokens, index))
579                .flatten()
580        })
581        .collect()
582}
583
584fn collect_native_css_functions_from_transform_ir(
585    source: &str,
586    dialect: StyleDialect,
587    ir: &TransformIrV0,
588) -> Vec<OmenaScssEvalNativeCssFunctionV0> {
589    let mut functions_by_key = BTreeMap::new();
590    for node in active_transform_ir_nodes_by_kind(ir, &[IrNodeKindV0::AtRule]) {
591        let Some(tokens) = lex_native_css_transform_ir_node(source, dialect, node) else {
592            continue;
593        };
594        let Some(at_keyword_index) = next_non_trivia_token_index(&tokens, 0) else {
595            continue;
596        };
597        let Some(at_keyword) = tokens.get(at_keyword_index) else {
598            continue;
599        };
600        if at_keyword.kind != SyntaxKind::AtKeyword
601            || !at_keyword.text.eq_ignore_ascii_case("@function")
602        {
603            continue;
604        }
605        let Some(function) = collect_native_css_function(source, &tokens, at_keyword_index) else {
606            continue;
607        };
608        functions_by_key
609            .entry((
610                function.source_span_start,
611                function.source_span_end,
612                function.name.clone(),
613            ))
614            .or_insert(function);
615    }
616    functions_by_key.into_values().collect()
617}
618
619fn collect_native_css_if_function_decisions_from_transform_ir(
620    source: &str,
621    dialect: StyleDialect,
622    ir: &TransformIrV0,
623) -> Vec<OmenaScssEvalNativeCssIfFunctionDecisionV0> {
624    let mut functions_by_span = BTreeMap::new();
625    for node in active_transform_ir_nodes_by_kind(
626        ir,
627        &[
628            IrNodeKindV0::Value,
629            IrNodeKindV0::Declaration,
630            IrNodeKindV0::AtRule,
631        ],
632    ) {
633        let Some(tokens) = lex_native_css_transform_ir_node(source, dialect, node) else {
634            continue;
635        };
636        for (index, token) in tokens.iter().enumerate() {
637            if token.kind != SyntaxKind::Ident || !token.text.eq_ignore_ascii_case("if") {
638                continue;
639            }
640            let Some(function) = collect_native_css_if_function_decision(source, &tokens, index)
641            else {
642                continue;
643            };
644            functions_by_span
645                .entry((function.source_span_start, function.source_span_end))
646                .or_insert(function);
647        }
648    }
649    functions_by_span.into_values().collect()
650}
651
652fn collect_native_css_function_call_evaluations_from_transform_ir(
653    source: &str,
654    dialect: StyleDialect,
655    ir: &TransformIrV0,
656    functions: &[OmenaScssEvalNativeCssFunctionV0],
657) -> Vec<OmenaScssEvalNativeCssFunctionCallEvaluationV0> {
658    let mut calls_by_key = BTreeMap::new();
659    for node in active_transform_ir_nodes_by_kind(
660        ir,
661        &[
662            IrNodeKindV0::Value,
663            IrNodeKindV0::Declaration,
664            IrNodeKindV0::AtRule,
665        ],
666    ) {
667        if !ir_node_source(source, node).is_some_and(|node_source| node_source.contains("--")) {
668            continue;
669        }
670        let Some(tokens) = lex_native_css_transform_ir_node(source, dialect, node) else {
671            continue;
672        };
673        for (index, token) in tokens.iter().enumerate() {
674            if !matches!(
675                token.kind,
676                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
677            ) || !token.text.starts_with("--")
678                || native_css_function_name_is_declaration(&tokens, index)
679            {
680                continue;
681            }
682            let Some(call) =
683                collect_native_css_function_call_evaluation(source, &tokens, functions, index)
684            else {
685                continue;
686            };
687            calls_by_key
688                .entry((
689                    call.source_span_start,
690                    call.source_span_end,
691                    call.name.clone(),
692                ))
693                .or_insert(call);
694        }
695    }
696    calls_by_key.into_values().collect()
697}
698
699fn native_css_conditional_rule_static_edits_from_transform_ir(
700    source: &str,
701    dialect: StyleDialect,
702    ir: &TransformIrV0,
703    at_rule_name: &'static str,
704    edit_kind: &'static str,
705    truthiness_by_start: &BTreeMap<usize, bool>,
706) -> Vec<OmenaScssEvalNativeCssStaticEditV0> {
707    let mut edits_by_key = BTreeMap::new();
708    for node in active_transform_ir_nodes_by_kind(ir, &[IrNodeKindV0::AtRule]) {
709        let span_end =
710            native_css_conditional_rule_span_end(source, dialect, ir, node, at_rule_name)
711                .unwrap_or(node.source_span_end);
712        let Some(tokens) =
713            lex_native_css_transform_ir_span(source, dialect, node.source_span_start, span_end)
714        else {
715            continue;
716        };
717        let Some(at_rule_index) = next_non_trivia_token_index(&tokens, 0) else {
718            continue;
719        };
720        let Some(at_rule_token) = tokens.get(at_rule_index) else {
721            continue;
722        };
723        if at_rule_token.kind != SyntaxKind::AtKeyword
724            || !at_rule_token.text.eq_ignore_ascii_case(at_rule_name)
725        {
726            continue;
727        }
728        let Some((edit, _)) = collect_native_css_conditional_rule_static_edit(
729            source,
730            &tokens,
731            at_rule_index,
732            edit_kind,
733            truthiness_by_start,
734        ) else {
735            continue;
736        };
737        edits_by_key
738            .entry((edit.start, edit.end, edit.edit_kind))
739            .or_insert(edit);
740    }
741    edits_by_key.into_values().collect()
742}
743
744fn native_css_conditional_rule_span_end(
745    source: &str,
746    dialect: StyleDialect,
747    ir: &TransformIrV0,
748    node: &IrNodeV0,
749    at_rule_name: &str,
750) -> Option<usize> {
751    let tokens = lex_native_css_transform_ir_node(source, dialect, node)?;
752    let at_rule_index = next_non_trivia_token_index(&tokens, 0)?;
753    let at_rule_token = tokens.get(at_rule_index)?;
754    if at_rule_token.kind != SyntaxKind::AtKeyword
755        || !at_rule_token.text.eq_ignore_ascii_case(at_rule_name)
756    {
757        return None;
758    }
759    let else_node = immediate_native_css_else_at_rule_node(source, dialect, ir, node)?;
760    Some(else_node.source_span_end)
761}
762
763fn immediate_native_css_else_at_rule_node<'a>(
764    source: &str,
765    dialect: StyleDialect,
766    ir: &'a TransformIrV0,
767    node: &IrNodeV0,
768) -> Option<&'a IrNodeV0> {
769    let next = ir
770        .nodes
771        .iter()
772        .filter(|candidate| {
773            !candidate.deleted && candidate.source_span_start >= node.source_span_end
774        })
775        .min_by_key(|candidate| candidate.source_span_start)?;
776    if next.kind != IrNodeKindV0::AtRule
777        || !source_span_is_trivia_only(
778            source,
779            dialect,
780            node.source_span_end,
781            next.source_span_start,
782        )
783    {
784        return None;
785    }
786    let tokens = lex_native_css_transform_ir_node(source, dialect, next)?;
787    let at_keyword_index = next_non_trivia_token_index(&tokens, 0)?;
788    let at_keyword = tokens.get(at_keyword_index)?;
789    (at_keyword.kind == SyntaxKind::AtKeyword && at_keyword.text.eq_ignore_ascii_case("@else"))
790        .then_some(next)
791}
792
793fn active_transform_ir_nodes_by_kind<'a>(
794    ir: &'a TransformIrV0,
795    kinds: &[IrNodeKindV0],
796) -> Vec<&'a IrNodeV0> {
797    let mut nodes = ir
798        .nodes
799        .iter()
800        .filter(|node| !node.deleted && kinds.contains(&node.kind))
801        .collect::<Vec<_>>();
802    nodes.sort_by_key(|node| (node.source_span_start, node.source_span_end, node.node_id));
803    nodes
804}
805
806fn ir_node_source<'source>(source: &'source str, node: &IrNodeV0) -> Option<&'source str> {
807    source.get(node.source_span_start..node.source_span_end)
808}
809
810fn lex_native_css_transform_ir_node(
811    source: &str,
812    dialect: StyleDialect,
813    node: &IrNodeV0,
814) -> Option<Vec<LexedToken>> {
815    lex_native_css_transform_ir_span(
816        source,
817        dialect,
818        node.source_span_start,
819        node.source_span_end,
820    )
821}
822
823fn lex_native_css_transform_ir_span(
824    source: &str,
825    dialect: StyleDialect,
826    source_span_start: usize,
827    source_span_end: usize,
828) -> Option<Vec<LexedToken>> {
829    if source_span_start > source_span_end
830        || source_span_end > source.len()
831        || !source.is_char_boundary(source_span_start)
832        || !source.is_char_boundary(source_span_end)
833    {
834        return None;
835    }
836    let source_slice = source.get(source_span_start..source_span_end)?;
837    lex(source_slice, dialect)
838        .tokens()
839        .iter()
840        .map(|token| shift_lexed_token(token, source_span_start))
841        .collect()
842}
843
844fn shift_lexed_token(token: &LexedToken, source_span_start: usize) -> Option<LexedToken> {
845    let shifted_start = token_start(token).checked_add(source_span_start)?;
846    let shifted_end = token_end(token).checked_add(source_span_start)?;
847    Some(LexedToken {
848        kind: token.kind,
849        range: TextRange::new(
850            TextSize::from(u32::try_from(shifted_start).ok()?),
851            TextSize::from(u32::try_from(shifted_end).ok()?),
852        ),
853        text: token.text.clone(),
854    })
855}
856
857fn source_span_is_trivia_only(
858    source: &str,
859    dialect: StyleDialect,
860    source_span_start: usize,
861    source_span_end: usize,
862) -> bool {
863    lex_native_css_transform_ir_span(source, dialect, source_span_start, source_span_end)
864        .is_some_and(|tokens| tokens.iter().all(|token| token.kind.is_trivia()))
865}
866
867fn native_css_conditional_rule_static_edits(
868    source: &str,
869    tokens: &[LexedToken],
870    at_rule_name: &'static str,
871    edit_kind: &'static str,
872    truthiness_by_start: &BTreeMap<usize, bool>,
873) -> Vec<OmenaScssEvalNativeCssStaticEditV0> {
874    let mut edits = Vec::new();
875    let mut index = 0usize;
876    while index < tokens.len() {
877        let Some(token) = tokens.get(index) else {
878            break;
879        };
880        if token.kind == SyntaxKind::AtKeyword
881            && token.text.eq_ignore_ascii_case(at_rule_name)
882            && let Some((edit, next_index)) = collect_native_css_conditional_rule_static_edit(
883                source,
884                tokens,
885                index,
886                edit_kind,
887                truthiness_by_start,
888            )
889        {
890            edits.push(edit);
891            index = next_index.saturating_add(1);
892            continue;
893        }
894        index += 1;
895    }
896    edits
897}
898
899fn collect_native_css_conditional_rule_static_edit(
900    source: &str,
901    tokens: &[LexedToken],
902    at_rule_index: usize,
903    edit_kind: &'static str,
904    truthiness_by_start: &BTreeMap<usize, bool>,
905) -> Option<(OmenaScssEvalNativeCssStaticEditV0, usize)> {
906    let block_start_index = next_matching_token_index(tokens, at_rule_index + 1, |token| {
907        matches!(
908            token.kind,
909            SyntaxKind::LeftBrace | SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
910        )
911    })?;
912    if tokens.get(block_start_index)?.kind != SyntaxKind::LeftBrace {
913        return None;
914    }
915    let block_end_index = matching_token_index(
916        tokens,
917        block_start_index,
918        SyntaxKind::LeftBrace,
919        SyntaxKind::RightBrace,
920    )?;
921    let else_rule = collect_immediate_native_css_else_rule(source, tokens, block_end_index + 1);
922
923    let source_span_start = token_start(tokens.get(at_rule_index)?);
924    match truthiness_by_start.get(&source_span_start).copied() {
925        Some(true) => {
926            let source_span_end = else_rule
927                .as_ref()
928                .map(|else_rule| else_rule.source_span_end)
929                .unwrap_or_else(|| token_end(&tokens[block_end_index]));
930            let replacement =
931                block_inner_source(source, tokens, block_start_index, block_end_index)?;
932            Some((
933                OmenaScssEvalNativeCssStaticEditV0 {
934                    start: source_span_start,
935                    end: source_span_end,
936                    replacement,
937                    edit_kind,
938                },
939                else_rule
940                    .as_ref()
941                    .map(|else_rule| else_rule.block_end_index)
942                    .unwrap_or(block_end_index),
943            ))
944        }
945        Some(false) => {
946            let Some(else_rule) = else_rule else {
947                return Some((
948                    OmenaScssEvalNativeCssStaticEditV0 {
949                        start: source_span_start,
950                        end: token_end(tokens.get(block_end_index)?),
951                        replacement: String::new(),
952                        edit_kind,
953                    },
954                    block_end_index,
955                ));
956            };
957            if !else_rule.header_text.trim().is_empty() {
958                return None;
959            }
960            let replacement = block_inner_source(
961                source,
962                tokens,
963                else_rule.block_start_index,
964                else_rule.block_end_index,
965            )?;
966            Some((
967                OmenaScssEvalNativeCssStaticEditV0 {
968                    start: source_span_start,
969                    end: else_rule.source_span_end,
970                    replacement,
971                    edit_kind,
972                },
973                else_rule.block_end_index,
974            ))
975        }
976        _ => None,
977    }
978}
979
980fn native_css_branch_truthiness_by_start(
981    source: &str,
982    at_rule_name: &str,
983) -> BTreeMap<usize, bool> {
984    let Some(graph) = build_scss_control_flow_graph(source, StyleDialect::Css) else {
985        return BTreeMap::new();
986    };
987    let Some(analysis) = analyze_scss_control_flow_values(source, StyleDialect::Css) else {
988        return BTreeMap::new();
989    };
990    let source_start_by_when_node_key = graph
991        .blocks
992        .iter()
993        .filter(|block| block.block.at_rule_name.eq_ignore_ascii_case(at_rule_name))
994        .map(|block| {
995            (
996                block.node_key.as_str().to_string(),
997                block.block.source_span_start,
998            )
999        })
1000        .collect::<BTreeMap<_, _>>();
1001
1002    analysis
1003        .blocks
1004        .iter()
1005        .filter_map(|block| {
1006            let truthy = match block.transfer_truthiness {
1007                Some("truthy") => true,
1008                Some("falsey") => false,
1009                _ => return None,
1010            };
1011            source_start_by_when_node_key
1012                .get(block.node_key.as_str())
1013                .copied()
1014                .map(|source_span_start| (source_span_start, truthy))
1015        })
1016        .collect()
1017}
1018
1019#[derive(Debug, Clone, PartialEq, Eq)]
1020struct NativeCssElseRuleV0 {
1021    header_text: String,
1022    block_start_index: usize,
1023    block_end_index: usize,
1024    source_span_end: usize,
1025}
1026
1027fn collect_immediate_native_css_else_rule(
1028    source: &str,
1029    tokens: &[LexedToken],
1030    start_index: usize,
1031) -> Option<NativeCssElseRuleV0> {
1032    let else_index = next_non_trivia_token_index(tokens, start_index)?;
1033    let else_token = tokens.get(else_index)?;
1034    if else_token.kind != SyntaxKind::AtKeyword || !else_token.text.eq_ignore_ascii_case("@else") {
1035        return None;
1036    }
1037    let block_start_index = next_matching_token_index(tokens, else_index + 1, |token| {
1038        matches!(
1039            token.kind,
1040            SyntaxKind::LeftBrace | SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
1041        )
1042    })?;
1043    if tokens.get(block_start_index)?.kind != SyntaxKind::LeftBrace {
1044        return None;
1045    }
1046    let block_end_index = matching_token_index(
1047        tokens,
1048        block_start_index,
1049        SyntaxKind::LeftBrace,
1050        SyntaxKind::RightBrace,
1051    )?;
1052    Some(NativeCssElseRuleV0 {
1053        header_text: trimmed_source_between_tokens(
1054            source,
1055            tokens,
1056            else_index + 1,
1057            block_start_index,
1058        )
1059        .unwrap_or_default(),
1060        block_start_index,
1061        block_end_index,
1062        source_span_end: token_end(tokens.get(block_end_index)?),
1063    })
1064}
1065
1066fn block_inner_source(
1067    source: &str,
1068    tokens: &[LexedToken],
1069    block_start_index: usize,
1070    block_end_index: usize,
1071) -> Option<String> {
1072    let start = token_end(tokens.get(block_start_index)?);
1073    let end = token_start(tokens.get(block_end_index)?);
1074    source.get(start..end).map(ToString::to_string)
1075}
1076
1077fn native_css_if_function_static_edits(
1078    surface: &OmenaScssEvalNativeCssIfFunctionDecisionSurfaceV0,
1079    truthiness_by_start: &BTreeMap<usize, bool>,
1080    functions: &[OmenaScssEvalNativeCssFunctionV0],
1081) -> Vec<OmenaScssEvalNativeCssStaticEditV0> {
1082    surface
1083        .functions
1084        .iter()
1085        .filter(|function| {
1086            !native_css_if_function_is_inside_function_declaration(function, functions)
1087        })
1088        .filter_map(|function| {
1089            let replacement = function.selected_value.as_ref()?;
1090            (function.decision == "foldToStaticValue"
1091                && native_css_if_function_edge_ir_allows_fold(function, truthiness_by_start))
1092            .then(|| OmenaScssEvalNativeCssStaticEditV0 {
1093                start: function.source_span_start,
1094                end: function.source_span_end,
1095                replacement: replacement.clone(),
1096                edit_kind: "ifFunctionValueFold",
1097            })
1098        })
1099        .collect()
1100}
1101
1102fn native_css_if_function_is_inside_function_declaration(
1103    function: &OmenaScssEvalNativeCssIfFunctionDecisionV0,
1104    functions: &[OmenaScssEvalNativeCssFunctionV0],
1105) -> bool {
1106    functions.iter().any(|declaration| {
1107        declaration.source_span_start <= function.source_span_start
1108            && function.source_span_end <= declaration.source_span_end
1109    })
1110}
1111
1112fn native_css_if_function_edge_ir_allows_fold(
1113    function: &OmenaScssEvalNativeCssIfFunctionDecisionV0,
1114    truthiness_by_start: &BTreeMap<usize, bool>,
1115) -> bool {
1116    let Some(edge_ir_truthy) = truthiness_by_start
1117        .get(&function.source_span_start)
1118        .copied()
1119    else {
1120        return false;
1121    };
1122    match function.selected_branch_index {
1123        Some(0) => edge_ir_truthy,
1124        Some(selected_branch_index) => {
1125            !edge_ir_truthy
1126                && function
1127                    .branches
1128                    .get(selected_branch_index)
1129                    .is_some_and(|branch| branch.verdict == "else")
1130                && function
1131                    .branches
1132                    .iter()
1133                    .take(selected_branch_index)
1134                    .all(|branch| branch.verdict == "alwaysFalse")
1135        }
1136        None => false,
1137    }
1138}
1139
1140fn native_css_function_call_static_edits(
1141    surface: &OmenaScssEvalNativeCssFunctionCallEvaluationSurfaceV0,
1142    functions: &[OmenaScssEvalNativeCssFunctionV0],
1143) -> Vec<OmenaScssEvalNativeCssStaticEditV0> {
1144    surface
1145        .calls
1146        .iter()
1147        .filter(|call| !native_css_function_call_is_inside_function_declaration(call, functions))
1148        .filter_map(|call| {
1149            let replacement = call.evaluated_value.as_ref()?;
1150            (call.decision == "foldToStaticValue").then(|| OmenaScssEvalNativeCssStaticEditV0 {
1151                start: call.source_span_start,
1152                end: call.source_span_end,
1153                replacement: replacement.clone(),
1154                edit_kind: "functionCallValueFold",
1155            })
1156        })
1157        .collect()
1158}
1159
1160fn native_css_function_call_is_inside_function_declaration(
1161    call: &OmenaScssEvalNativeCssFunctionCallEvaluationV0,
1162    functions: &[OmenaScssEvalNativeCssFunctionV0],
1163) -> bool {
1164    functions.iter().any(|function| {
1165        function.source_span_start <= call.source_span_start
1166            && call.source_span_end <= function.source_span_end
1167    })
1168}
1169
1170fn normalize_native_css_static_edits(
1171    source: &str,
1172    mut edits: Vec<OmenaScssEvalNativeCssStaticEditV0>,
1173) -> Option<Vec<OmenaScssEvalNativeCssStaticEditV0>> {
1174    edits.sort_by_key(|edit| edit.start);
1175    edits.dedup_by(|left, right| {
1176        left.start == right.start
1177            && left.end == right.end
1178            && left.replacement == right.replacement
1179            && left.edit_kind == right.edit_kind
1180    });
1181    let mut normalized: Vec<OmenaScssEvalNativeCssStaticEditV0> = Vec::new();
1182    let mut index = 0usize;
1183    while index < edits.len() {
1184        let mut edit = edits.get(index)?.clone();
1185        if edit.start > edit.end || edit.end > source.len() {
1186            return None;
1187        }
1188        let mut contained = Vec::new();
1189        let mut next_index = index + 1;
1190        while let Some(next_edit) = edits.get(next_index)
1191            && next_edit.start < edit.end
1192        {
1193            if next_edit.start > next_edit.end || next_edit.end > source.len() {
1194                return None;
1195            }
1196            if next_edit.end <= edit.end {
1197                contained.push(next_edit.clone());
1198                next_index += 1;
1199                continue;
1200            }
1201            return None;
1202        }
1203        compose_contained_native_css_static_edits(source, &mut edit, &contained)?;
1204        normalized.push(edit);
1205        index = next_index;
1206    }
1207    Some(normalized)
1208}
1209
1210fn compose_contained_native_css_static_edits(
1211    source: &str,
1212    outer: &mut OmenaScssEvalNativeCssStaticEditV0,
1213    contained: &[OmenaScssEvalNativeCssStaticEditV0],
1214) -> Option<()> {
1215    if !matches!(outer.edit_kind, "whenRuleBranchFold" | "ifRuleBranchFold") || contained.is_empty()
1216    {
1217        return Some(());
1218    }
1219    let Some((replacement_source_start, replacement_source_end)) =
1220        native_css_static_edit_replacement_source_span(source, outer)
1221    else {
1222        return Some(());
1223    };
1224    for inner in contained.iter().rev().filter(|inner| {
1225        replacement_source_start <= inner.start && inner.end <= replacement_source_end
1226    }) {
1227        let relative_start = inner.start - replacement_source_start;
1228        let relative_end = inner.end - replacement_source_start;
1229        if !outer.replacement.is_char_boundary(relative_start)
1230            || !outer.replacement.is_char_boundary(relative_end)
1231        {
1232            return None;
1233        }
1234        outer
1235            .replacement
1236            .replace_range(relative_start..relative_end, inner.replacement.as_str());
1237    }
1238    Some(())
1239}
1240
1241fn native_css_static_edit_replacement_source_span(
1242    source: &str,
1243    edit: &OmenaScssEvalNativeCssStaticEditV0,
1244) -> Option<(usize, usize)> {
1245    if edit.replacement.is_empty() {
1246        return None;
1247    }
1248    let edited_source = source.get(edit.start..edit.end)?;
1249    let mut matches = edited_source.match_indices(edit.replacement.as_str());
1250    let (relative_start, _) = matches.next()?;
1251    if matches.next().is_some() {
1252        return None;
1253    }
1254    let start = edit.start + relative_start;
1255    Some((start, start + edit.replacement.len()))
1256}
1257
1258fn apply_native_css_static_edits(
1259    source: &str,
1260    edits: &[OmenaScssEvalNativeCssStaticEditV0],
1261) -> String {
1262    let mut output = source.to_string();
1263    for edit in edits.iter().rev() {
1264        output.replace_range(edit.start..edit.end, edit.replacement.as_str());
1265    }
1266    output
1267}
1268
1269fn collect_native_css_function_call_evaluations(
1270    source: &str,
1271    tokens: &[LexedToken],
1272    functions: &[OmenaScssEvalNativeCssFunctionV0],
1273) -> Vec<OmenaScssEvalNativeCssFunctionCallEvaluationV0> {
1274    tokens
1275        .iter()
1276        .enumerate()
1277        .filter_map(|(index, token)| {
1278            (matches!(
1279                token.kind,
1280                SyntaxKind::Ident | SyntaxKind::CustomPropertyName
1281            ) && token.text.starts_with("--")
1282                && !native_css_function_name_is_declaration(tokens, index))
1283            .then(|| collect_native_css_function_call_evaluation(source, tokens, functions, index))
1284            .flatten()
1285        })
1286        .collect()
1287}
1288
1289fn collect_native_css_function_call_evaluation(
1290    source: &str,
1291    tokens: &[LexedToken],
1292    functions: &[OmenaScssEvalNativeCssFunctionV0],
1293    name_index: usize,
1294) -> Option<OmenaScssEvalNativeCssFunctionCallEvaluationV0> {
1295    let name = tokens.get(name_index)?;
1296    let left_paren_index = next_non_trivia_token_index(tokens, name_index + 1)?;
1297    if tokens.get(left_paren_index)?.kind != SyntaxKind::LeftParen {
1298        return None;
1299    }
1300    let right_paren_index = matching_token_index(
1301        tokens,
1302        left_paren_index,
1303        SyntaxKind::LeftParen,
1304        SyntaxKind::RightParen,
1305    )?;
1306    let arguments = collect_native_css_function_call_arguments(
1307        source,
1308        tokens,
1309        left_paren_index + 1,
1310        right_paren_index,
1311    );
1312    let matches = functions
1313        .iter()
1314        .filter(|function| function.name == name.text)
1315        .collect::<Vec<_>>();
1316    let matched_function_count = matches.len();
1317    let (
1318        decision,
1319        reason,
1320        evaluated_value,
1321        matched_function_source_span_start,
1322        matched_function_source_span_end,
1323    ) = decide_native_css_function_call(&arguments, &matches, functions);
1324
1325    Some(OmenaScssEvalNativeCssFunctionCallEvaluationV0 {
1326        name: name.text.clone(),
1327        source_span_start: token_start(name),
1328        source_span_end: token_end(tokens.get(right_paren_index)?),
1329        argument_count: arguments.len(),
1330        matched_function_count,
1331        matched_function_source_span_start,
1332        matched_function_source_span_end,
1333        decision,
1334        reason,
1335        evaluated_value,
1336        arguments,
1337    })
1338}
1339
1340fn collect_native_css_function_call_arguments(
1341    source: &str,
1342    tokens: &[LexedToken],
1343    start_index: usize,
1344    end_index: usize,
1345) -> Vec<OmenaScssEvalNativeCssFunctionCallArgumentV0> {
1346    split_top_level_ranges(tokens, start_index, end_index, SyntaxKind::Comma)
1347        .into_iter()
1348        .enumerate()
1349        .filter_map(|(argument_index, (start, end))| {
1350            let value = trimmed_source_between_tokens(source, tokens, start, end)?;
1351            let source_span_start = first_non_trivia_token_index_until(tokens, start, end)
1352                .and_then(|index| tokens.get(index))
1353                .map(token_start)?;
1354            let source_span_end = previous_non_trivia_token_index_until(tokens, start, end)
1355                .and_then(|index| tokens.get(index))
1356                .map(token_end)
1357                .unwrap_or(source_span_start);
1358            Some(OmenaScssEvalNativeCssFunctionCallArgumentV0 {
1359                argument_index,
1360                static_value: native_css_if_value_is_fully_static(&value),
1361                value,
1362                source_span_start,
1363                source_span_end,
1364            })
1365        })
1366        .collect()
1367}
1368
1369fn decide_native_css_function_call(
1370    arguments: &[OmenaScssEvalNativeCssFunctionCallArgumentV0],
1371    matches: &[&OmenaScssEvalNativeCssFunctionV0],
1372    functions: &[OmenaScssEvalNativeCssFunctionV0],
1373) -> (
1374    &'static str,
1375    &'static str,
1376    Option<String>,
1377    Option<usize>,
1378    Option<usize>,
1379) {
1380    let Some(function) = matches.first().copied() else {
1381        return (
1382            "preserveVerbatim",
1383            "function resolution is unavailable",
1384            None,
1385            None,
1386            None,
1387        );
1388    };
1389    if matches.len() != 1 {
1390        return (
1391            "preserveVerbatim",
1392            "function resolution is ambiguous",
1393            None,
1394            None,
1395            None,
1396        );
1397    }
1398    if arguments.len() > function.parameters.len() {
1399        return (
1400            "structuralError",
1401            "call has more arguments than declared parameters",
1402            None,
1403            Some(function.source_span_start),
1404            Some(function.source_span_end),
1405        );
1406    }
1407    if native_css_function_required_argument_is_missing(arguments, function) {
1408        return (
1409            "structuralError",
1410            "required argument is missing",
1411            None,
1412            Some(function.source_span_start),
1413            Some(function.source_span_end),
1414        );
1415    }
1416    if function.results.is_empty() {
1417        return (
1418            "structuralError",
1419            "function has no result declaration",
1420            None,
1421            Some(function.source_span_start),
1422            Some(function.source_span_end),
1423        );
1424    }
1425    if function.results.len() != 1 {
1426        return (
1427            "preserveVerbatim",
1428            "function has multiple result declarations",
1429            None,
1430            Some(function.source_span_start),
1431            Some(function.source_span_end),
1432        );
1433    }
1434    if native_css_function_has_guaranteed_result_cycle(function, functions) {
1435        return (
1436            "structuralError",
1437            "function result call graph contains a guaranteed cycle",
1438            None,
1439            Some(function.source_span_start),
1440            Some(function.source_span_end),
1441        );
1442    }
1443
1444    let Some(bindings) = bind_native_css_function_arguments(arguments, function) else {
1445        return (
1446            "preserveVerbatim",
1447            "argument or default value depends on runtime or cascade state",
1448            None,
1449            Some(function.source_span_start),
1450            Some(function.source_span_end),
1451        );
1452    };
1453    match native_css_function_parameter_syntax_gate(&bindings, function) {
1454        NativeCssFunctionSyntaxGateV0::Accepts => {}
1455        NativeCssFunctionSyntaxGateV0::Rejects { reason } => {
1456            return (
1457                "structuralError",
1458                reason,
1459                None,
1460                Some(function.source_span_start),
1461                Some(function.source_span_end),
1462            );
1463        }
1464        NativeCssFunctionSyntaxGateV0::Unknown { reason } => {
1465            return (
1466                "preserveVerbatim",
1467                reason,
1468                None,
1469                Some(function.source_span_start),
1470                Some(function.source_span_end),
1471            );
1472        }
1473    }
1474    let result = &function.results[0].value;
1475    let Some(evaluated_value) = evaluate_native_css_function_result_value(result, &bindings) else {
1476        return (
1477            "preserveVerbatim",
1478            "result value depends on runtime or cascade state",
1479            None,
1480            Some(function.source_span_start),
1481            Some(function.source_span_end),
1482        );
1483    };
1484    match native_css_function_return_syntax_gate(evaluated_value.as_str(), function) {
1485        NativeCssFunctionSyntaxGateV0::Accepts => {}
1486        NativeCssFunctionSyntaxGateV0::Rejects { reason } => {
1487            return (
1488                "structuralError",
1489                reason,
1490                None,
1491                Some(function.source_span_start),
1492                Some(function.source_span_end),
1493            );
1494        }
1495        NativeCssFunctionSyntaxGateV0::Unknown { reason } => {
1496            return (
1497                "preserveVerbatim",
1498                reason,
1499                None,
1500                Some(function.source_span_start),
1501                Some(function.source_span_end),
1502            );
1503        }
1504    }
1505
1506    (
1507        "foldToStaticValue",
1508        "unique function call resolved to a static result value",
1509        Some(evaluated_value),
1510        Some(function.source_span_start),
1511        Some(function.source_span_end),
1512    )
1513}
1514
1515fn native_css_function_has_guaranteed_result_cycle(
1516    function: &OmenaScssEvalNativeCssFunctionV0,
1517    functions: &[OmenaScssEvalNativeCssFunctionV0],
1518) -> bool {
1519    native_css_result_reaches_function(
1520        function.name.as_str(),
1521        function.name.as_str(),
1522        functions,
1523        &mut Vec::new(),
1524    )
1525}
1526
1527fn native_css_result_reaches_function(
1528    target_name: &str,
1529    current_name: &str,
1530    functions: &[OmenaScssEvalNativeCssFunctionV0],
1531    visited: &mut Vec<String>,
1532) -> bool {
1533    if visited.iter().any(|name| name == current_name) {
1534        return false;
1535    }
1536    visited.push(current_name.to_string());
1537    let Some(function) = native_css_unique_function_by_name(functions, current_name) else {
1538        return false;
1539    };
1540    for result in &function.results {
1541        let Some(next_name) = native_css_exact_function_call_name(result.value.as_str()) else {
1542            continue;
1543        };
1544        if next_name == target_name {
1545            return true;
1546        }
1547        if native_css_result_reaches_function(target_name, next_name, functions, visited) {
1548            return true;
1549        }
1550    }
1551    false
1552}
1553
1554fn native_css_unique_function_by_name<'a>(
1555    functions: &'a [OmenaScssEvalNativeCssFunctionV0],
1556    name: &str,
1557) -> Option<&'a OmenaScssEvalNativeCssFunctionV0> {
1558    let mut matches = functions.iter().filter(|function| function.name == name);
1559    let first = matches.next()?;
1560    matches.next().is_none().then_some(first)
1561}
1562
1563fn native_css_exact_function_call_name(value: &str) -> Option<&str> {
1564    let trimmed = value.trim();
1565    let open_index = trimmed.find('(')?;
1566    let name = trimmed[..open_index].trim();
1567    if !name.starts_with("--") {
1568        return None;
1569    }
1570    let call_tail = &trimmed[open_index..];
1571    let close_index = matching_closing_paren_byte_index(call_tail)?;
1572    call_tail[close_index + 1..]
1573        .trim()
1574        .is_empty()
1575        .then_some(name)
1576}
1577
1578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1579enum NativeCssFunctionSyntaxGateV0 {
1580    Accepts,
1581    Rejects { reason: &'static str },
1582    Unknown { reason: &'static str },
1583}
1584
1585type NativeCssFunctionBindingV0 = (CanonicalCustomPropertyNameV0, String);
1586
1587fn native_css_function_parameter_syntax_gate(
1588    bindings: &[NativeCssFunctionBindingV0],
1589    function: &OmenaScssEvalNativeCssFunctionV0,
1590) -> NativeCssFunctionSyntaxGateV0 {
1591    for parameter in &function.parameters {
1592        let Some(syntax) = parameter.syntax_source.as_deref() else {
1593            continue;
1594        };
1595        let Some((_, value)) = bindings
1596            .iter()
1597            .find(|(name, _)| name == &parameter.property_key)
1598        else {
1599            continue;
1600        };
1601        match validate_registered_property_value_v0(syntax, value).class {
1602            CssValueValidationClassV0::Valid => {}
1603            CssValueValidationClassV0::Invalid => {
1604                return NativeCssFunctionSyntaxGateV0::Rejects {
1605                    reason: "argument does not match parameter syntax",
1606                };
1607            }
1608            CssValueValidationClassV0::NotValidatable => {
1609                return NativeCssFunctionSyntaxGateV0::Unknown {
1610                    reason: "argument syntax match is unknown",
1611                };
1612            }
1613        }
1614    }
1615    NativeCssFunctionSyntaxGateV0::Accepts
1616}
1617
1618fn native_css_function_return_syntax_gate(
1619    evaluated_value: &str,
1620    function: &OmenaScssEvalNativeCssFunctionV0,
1621) -> NativeCssFunctionSyntaxGateV0 {
1622    let Some(syntax) = function.return_syntax_source.as_deref() else {
1623        return NativeCssFunctionSyntaxGateV0::Accepts;
1624    };
1625    match validate_registered_property_value_v0(syntax, evaluated_value).class {
1626        CssValueValidationClassV0::Valid => NativeCssFunctionSyntaxGateV0::Accepts,
1627        CssValueValidationClassV0::Invalid => NativeCssFunctionSyntaxGateV0::Rejects {
1628            reason: "result value does not match return syntax",
1629        },
1630        CssValueValidationClassV0::NotValidatable => NativeCssFunctionSyntaxGateV0::Unknown {
1631            reason: "result return syntax match is unknown",
1632        },
1633    }
1634}
1635
1636fn bind_native_css_function_arguments(
1637    arguments: &[OmenaScssEvalNativeCssFunctionCallArgumentV0],
1638    function: &OmenaScssEvalNativeCssFunctionV0,
1639) -> Option<Vec<NativeCssFunctionBindingV0>> {
1640    function
1641        .parameters
1642        .iter()
1643        .enumerate()
1644        .map(|(index, parameter)| {
1645            if let Some(argument) = arguments.get(index) {
1646                return argument
1647                    .static_value
1648                    .then(|| (parameter.property_key.clone(), argument.value.clone()));
1649            }
1650            let default_value = parameter.default_value.as_deref()?;
1651            native_css_if_value_is_fully_static(default_value)
1652                .then(|| (parameter.property_key.clone(), default_value.to_string()))
1653        })
1654        .collect()
1655}
1656
1657fn native_css_function_required_argument_is_missing(
1658    arguments: &[OmenaScssEvalNativeCssFunctionCallArgumentV0],
1659    function: &OmenaScssEvalNativeCssFunctionV0,
1660) -> bool {
1661    function
1662        .parameters
1663        .iter()
1664        .enumerate()
1665        .any(|(index, parameter)| index >= arguments.len() && parameter.default_value.is_none())
1666}
1667
1668fn evaluate_native_css_function_result_value(
1669    result: &str,
1670    bindings: &[NativeCssFunctionBindingV0],
1671) -> Option<String> {
1672    if let Some(parameter_name) = extract_exact_var_reference(result) {
1673        let parameter_key =
1674            omena_syntax::ident::PropertyNameV0::canonical_custom_key(parameter_name);
1675        return bindings
1676            .iter()
1677            .find_map(|(name, value)| (name == &parameter_key).then(|| value.clone()));
1678    }
1679    if let Some(value) = evaluate_native_css_function_result_if_value(result) {
1680        return Some(value);
1681    }
1682    if native_css_if_value_is_fully_static(result) {
1683        return Some(result.trim().to_string());
1684    }
1685    None
1686}
1687
1688fn evaluate_native_css_function_result_if_value(result: &str) -> Option<String> {
1689    let trimmed = result.trim();
1690    if !trimmed
1691        .get(..2)
1692        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("if"))
1693    {
1694        return None;
1695    }
1696    let source = format!(".native-result {{ value: {trimmed}; }}");
1697    let value_start = source.find(trimmed)?;
1698    let decisions = summarize_native_css_if_function_decisions(&source, StyleDialect::Css)?;
1699    let decision = decisions.functions.first()?;
1700    if decisions.functions.len() != 1
1701        || decision.decision != "foldToStaticValue"
1702        || decision.source_span_start != value_start
1703        || decision.source_span_end != value_start + trimmed.len()
1704    {
1705        return None;
1706    }
1707    let truthiness_by_start = native_css_branch_truthiness_by_start(&source, "if()");
1708    native_css_if_function_edge_ir_allows_fold(decision, &truthiness_by_start)
1709        .then(|| decision.selected_value.clone())
1710        .flatten()
1711}
1712
1713fn collect_native_css_if_function_decision(
1714    source: &str,
1715    tokens: &[LexedToken],
1716    if_index: usize,
1717) -> Option<OmenaScssEvalNativeCssIfFunctionDecisionV0> {
1718    let left_paren_index = next_non_trivia_token_index(tokens, if_index + 1)?;
1719    if tokens.get(left_paren_index)?.kind != SyntaxKind::LeftParen {
1720        return None;
1721    }
1722    let right_paren_index = matching_token_index(
1723        tokens,
1724        left_paren_index,
1725        SyntaxKind::LeftParen,
1726        SyntaxKind::RightParen,
1727    )?;
1728    let branches = collect_native_css_if_function_branches(
1729        source,
1730        tokens,
1731        left_paren_index + 1,
1732        right_paren_index,
1733    );
1734    if branches.is_empty() {
1735        return None;
1736    }
1737    let (decision, reason, selected_branch_index, selected_value) =
1738        decide_native_css_if_function(&branches);
1739
1740    Some(OmenaScssEvalNativeCssIfFunctionDecisionV0 {
1741        source_span_start: token_start(tokens.get(if_index)?),
1742        source_span_end: token_end(tokens.get(right_paren_index)?),
1743        branch_count: branches.len(),
1744        decision,
1745        reason,
1746        selected_branch_index,
1747        selected_value,
1748        branches,
1749    })
1750}
1751
1752fn collect_native_css_if_function_branches(
1753    source: &str,
1754    tokens: &[LexedToken],
1755    start_index: usize,
1756    end_index: usize,
1757) -> Vec<OmenaScssEvalNativeCssIfFunctionBranchV0> {
1758    split_top_level_ranges(tokens, start_index, end_index, SyntaxKind::Semicolon)
1759        .into_iter()
1760        .enumerate()
1761        .filter_map(|(branch_index, (start, end))| {
1762            collect_native_css_if_function_branch(source, tokens, branch_index, start, end)
1763        })
1764        .collect()
1765}
1766
1767fn collect_native_css_if_function_branch(
1768    source: &str,
1769    tokens: &[LexedToken],
1770    branch_index: usize,
1771    start_index: usize,
1772    end_index: usize,
1773) -> Option<OmenaScssEvalNativeCssIfFunctionBranchV0> {
1774    let condition_end = first_top_level_token_index(tokens, start_index, end_index, |token| {
1775        token.kind == SyntaxKind::Colon
1776    })?;
1777    let condition = trimmed_source_between_tokens(source, tokens, start_index, condition_end)?;
1778    let value = trimmed_source_between_tokens(source, tokens, condition_end + 1, end_index);
1779    let (condition_kind, verdict, supports_witness) =
1780        classify_native_css_if_function_condition(&condition);
1781    let source_span_start = first_non_trivia_token_index_until(tokens, start_index, end_index)
1782        .and_then(|index| tokens.get(index))
1783        .map(token_start)?;
1784    let source_span_end = previous_non_trivia_token_index_until(tokens, start_index, end_index)
1785        .and_then(|index| tokens.get(index))
1786        .map(token_end)
1787        .unwrap_or(source_span_start);
1788
1789    Some(OmenaScssEvalNativeCssIfFunctionBranchV0 {
1790        branch_index,
1791        condition,
1792        condition_kind,
1793        verdict,
1794        value,
1795        source_span_start,
1796        source_span_end,
1797        supports_witness,
1798    })
1799}
1800
1801fn classify_native_css_if_function_condition(
1802    condition: &str,
1803) -> (
1804    &'static str,
1805    &'static str,
1806    Option<StaticSupportsEvalWitnessV0>,
1807) {
1808    if condition.trim().eq_ignore_ascii_case("else") {
1809        return ("else", "else", None);
1810    }
1811    if let Some(inner) = extract_named_function_inner(condition, "supports") {
1812        let normalized_condition = normalize_supports_condition_for_if(inner);
1813        let witness = evaluate_static_supports_condition(
1814            &normalized_condition,
1815            StaticSupportsAssumptionV0::ModernBrowser,
1816        );
1817        let verdict = static_supports_verdict_label(witness.verdict);
1818        return ("supports", verdict, Some(witness));
1819    }
1820    if extract_named_function_inner(condition, "media").is_some() {
1821        return ("media", "runtime", None);
1822    }
1823    if extract_named_function_inner(condition, "style").is_some() {
1824        return ("style", "runtime", None);
1825    }
1826
1827    ("unknown", "unknown", None)
1828}
1829
1830fn decide_native_css_if_function(
1831    branches: &[OmenaScssEvalNativeCssIfFunctionBranchV0],
1832) -> (&'static str, &'static str, Option<usize>, Option<String>) {
1833    for branch in branches {
1834        match branch.verdict {
1835            "alwaysFalse" => continue,
1836            "alwaysTrue" | "else" => {
1837                let Some(value) = branch.value.as_deref() else {
1838                    return (
1839                        "preserveVerbatim",
1840                        "selected branch has an empty token stream",
1841                        None,
1842                        None,
1843                    );
1844                };
1845                if native_css_if_value_is_fully_static(value) {
1846                    return (
1847                        "foldToStaticValue",
1848                        "all earlier branches are statically false and the selected value is static",
1849                        Some(branch.branch_index),
1850                        Some(value.to_string()),
1851                    );
1852                }
1853                return (
1854                    "preserveVerbatim",
1855                    "selected branch value depends on runtime or cascade state",
1856                    None,
1857                    None,
1858                );
1859            }
1860            "runtime" => {
1861                return (
1862                    "preserveVerbatim",
1863                    "encountered runtime condition before a static winner",
1864                    None,
1865                    None,
1866                );
1867            }
1868            _ => {
1869                return (
1870                    "preserveVerbatim",
1871                    "encountered unknown condition before a static winner",
1872                    None,
1873                    None,
1874                );
1875            }
1876        }
1877    }
1878
1879    (
1880        "preserveVerbatim",
1881        "no statically selected branch",
1882        None,
1883        None,
1884    )
1885}
1886
1887fn collect_native_css_function(
1888    source: &str,
1889    tokens: &[LexedToken],
1890    at_keyword_index: usize,
1891) -> Option<OmenaScssEvalNativeCssFunctionV0> {
1892    let name_index = next_non_trivia_token_index(tokens, at_keyword_index + 1)?;
1893    let name = tokens.get(name_index)?;
1894    if !name.text.starts_with("--") {
1895        return None;
1896    }
1897    let left_paren_index = next_non_trivia_token_index(tokens, name_index + 1)?;
1898    if tokens.get(left_paren_index)?.kind != SyntaxKind::LeftParen {
1899        return None;
1900    }
1901    let right_paren_index = matching_token_index(
1902        tokens,
1903        left_paren_index,
1904        SyntaxKind::LeftParen,
1905        SyntaxKind::RightParen,
1906    )?;
1907    let block_start_index = next_matching_token_index(tokens, right_paren_index + 1, |token| {
1908        matches!(
1909            token.kind,
1910            SyntaxKind::LeftBrace | SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
1911        )
1912    })?;
1913    if tokens.get(block_start_index)?.kind != SyntaxKind::LeftBrace {
1914        return None;
1915    }
1916    let block_end_index = matching_token_index(
1917        tokens,
1918        block_start_index,
1919        SyntaxKind::LeftBrace,
1920        SyntaxKind::RightBrace,
1921    )
1922    .unwrap_or(block_start_index);
1923    let parameters = collect_native_css_function_parameters(
1924        source,
1925        tokens,
1926        left_paren_index + 1,
1927        right_paren_index,
1928    );
1929    let (return_syntax_source, return_syntax) = collect_native_css_function_return_syntax(
1930        source,
1931        tokens,
1932        right_paren_index,
1933        block_start_index,
1934    );
1935    let results =
1936        collect_native_css_function_results(source, tokens, block_start_index, block_end_index);
1937    let source_span_start = token_start(tokens.get(at_keyword_index)?);
1938    let source_span_end = token_end(tokens.get(block_end_index)?);
1939    let typed_parameter_count = parameters
1940        .iter()
1941        .filter(|parameter| parameter.syntax.is_some())
1942        .count();
1943
1944    Some(OmenaScssEvalNativeCssFunctionV0 {
1945        name: name.text.clone(),
1946        source_span_start,
1947        source_span_end,
1948        name_span_start: token_start(name),
1949        name_span_end: token_end(name),
1950        return_syntax_source,
1951        return_syntax,
1952        parameter_count: parameters.len(),
1953        typed_parameter_count,
1954        result_count: results.len(),
1955        parameters,
1956        results,
1957    })
1958}
1959
1960fn collect_native_css_function_parameters(
1961    source: &str,
1962    tokens: &[LexedToken],
1963    start_index: usize,
1964    end_index: usize,
1965) -> Vec<OmenaScssEvalNativeCssFunctionParameterV0> {
1966    split_top_level_ranges(tokens, start_index, end_index, SyntaxKind::Comma)
1967        .into_iter()
1968        .filter_map(|(start, end)| {
1969            collect_native_css_function_parameter(source, tokens, start, end)
1970        })
1971        .collect()
1972}
1973
1974fn collect_native_css_function_parameter(
1975    source: &str,
1976    tokens: &[LexedToken],
1977    start_index: usize,
1978    end_index: usize,
1979) -> Option<OmenaScssEvalNativeCssFunctionParameterV0> {
1980    let name_index = next_non_trivia_token_index_until(tokens, start_index, end_index)?;
1981    let name = tokens.get(name_index)?;
1982    if !name.text.starts_with("--") {
1983        return None;
1984    }
1985    let separator_index = first_top_level_token_index(tokens, name_index + 1, end_index, |token| {
1986        matches!(token.kind, SyntaxKind::Colon)
1987    });
1988    let syntax_end_index = separator_index.unwrap_or(end_index);
1989    let syntax_source =
1990        trimmed_source_between_tokens(source, tokens, name_index + 1, syntax_end_index);
1991    let syntax = syntax_source
1992        .as_deref()
1993        .map(parse_registered_property_syntax_v0);
1994    let default_value = separator_index
1995        .and_then(|index| trimmed_source_between_tokens(source, tokens, index + 1, end_index));
1996    let source_span_start = token_start(name);
1997    let source_span_end = previous_non_trivia_token_index_until(tokens, start_index, end_index)
1998        .and_then(|index| tokens.get(index))
1999        .map(token_end)
2000        .unwrap_or_else(|| token_end(name));
2001
2002    Some(OmenaScssEvalNativeCssFunctionParameterV0 {
2003        name: AuthoredPropertyTextV0::new(name.text.clone()),
2004        property_key: PropertyNameV0::canonical_custom_key(name.text.clone()),
2005        source_span_start,
2006        source_span_end,
2007        name_span_start: token_start(name),
2008        name_span_end: token_end(name),
2009        syntax_source,
2010        syntax,
2011        default_value,
2012    })
2013}
2014
2015fn collect_native_css_function_return_syntax(
2016    source: &str,
2017    tokens: &[LexedToken],
2018    right_paren_index: usize,
2019    block_start_index: usize,
2020) -> (Option<String>, Option<RegisteredPropertySyntaxV0>) {
2021    let Some(returns_index) =
2022        first_top_level_token_index(tokens, right_paren_index + 1, block_start_index, |token| {
2023            token.kind == SyntaxKind::Ident && token.text.eq_ignore_ascii_case("returns")
2024        })
2025    else {
2026        return (None, None);
2027    };
2028    let source =
2029        trimmed_source_between_tokens(source, tokens, returns_index + 1, block_start_index);
2030    let syntax = source.as_deref().map(parse_registered_property_syntax_v0);
2031    (source, syntax)
2032}
2033
2034fn collect_native_css_function_results(
2035    source: &str,
2036    tokens: &[LexedToken],
2037    block_start_index: usize,
2038    block_end_index: usize,
2039) -> Vec<OmenaScssEvalNativeCssFunctionResultV0> {
2040    let mut results = Vec::new();
2041    let mut index = block_start_index + 1;
2042    while index < block_end_index {
2043        let Some(property_index) =
2044            next_non_trivia_token_index_until(tokens, index, block_end_index)
2045        else {
2046            break;
2047        };
2048        let property = &tokens[property_index];
2049        if property.kind != SyntaxKind::Ident || !property.text.eq_ignore_ascii_case("result") {
2050            index = property_index.saturating_add(1);
2051            continue;
2052        }
2053        let Some(colon_index) =
2054            next_non_trivia_token_index_until(tokens, property_index + 1, block_end_index)
2055        else {
2056            index = property_index.saturating_add(1);
2057            continue;
2058        };
2059        if tokens[colon_index].kind != SyntaxKind::Colon {
2060            index = property_index.saturating_add(1);
2061            continue;
2062        }
2063        let value_end =
2064            first_top_level_token_index(tokens, colon_index + 1, block_end_index, |token| {
2065                matches!(
2066                    token.kind,
2067                    SyntaxKind::Semicolon | SyntaxKind::SassOptionalSemicolon
2068                )
2069            })
2070            .unwrap_or(block_end_index);
2071        if let Some(value) =
2072            trimmed_source_between_tokens(source, tokens, colon_index + 1, value_end)
2073        {
2074            let value_span_start =
2075                first_non_trivia_token_index_until(tokens, colon_index + 1, value_end)
2076                    .and_then(|index| tokens.get(index))
2077                    .map(token_start)
2078                    .unwrap_or_else(|| token_end(&tokens[colon_index]));
2079            let value_span_end =
2080                previous_non_trivia_token_index_until(tokens, colon_index + 1, value_end)
2081                    .and_then(|index| tokens.get(index))
2082                    .map(token_end)
2083                    .unwrap_or(value_span_start);
2084            results.push(OmenaScssEvalNativeCssFunctionResultV0 {
2085                value,
2086                source_span_start: value_span_start,
2087                source_span_end: value_span_end,
2088            });
2089        }
2090        index = value_end.saturating_add(1);
2091    }
2092    results
2093}
2094
2095fn split_top_level_ranges(
2096    tokens: &[LexedToken],
2097    start_index: usize,
2098    end_index: usize,
2099    separator: SyntaxKind,
2100) -> Vec<(usize, usize)> {
2101    let mut ranges = Vec::new();
2102    let mut range_start = start_index;
2103    let mut paren_depth = 0usize;
2104    let mut brace_depth = 0usize;
2105    let mut bracket_depth = 0usize;
2106
2107    for index in start_index..end_index {
2108        let Some(token) = tokens.get(index) else {
2109            break;
2110        };
2111        match token.kind {
2112            SyntaxKind::LeftParen => paren_depth += 1,
2113            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
2114            SyntaxKind::LeftBrace => brace_depth += 1,
2115            SyntaxKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
2116            SyntaxKind::LeftBracket => bracket_depth += 1,
2117            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
2118            kind if kind == separator
2119                && paren_depth == 0
2120                && brace_depth == 0
2121                && bracket_depth == 0 =>
2122            {
2123                ranges.push((range_start, index));
2124                range_start = index + 1;
2125            }
2126            _ => {}
2127        }
2128    }
2129    if range_start < end_index {
2130        ranges.push((range_start, end_index));
2131    }
2132    ranges
2133}
2134
2135fn first_top_level_token_index(
2136    tokens: &[LexedToken],
2137    start_index: usize,
2138    end_index: usize,
2139    predicate: impl Fn(&LexedToken) -> bool,
2140) -> Option<usize> {
2141    let mut paren_depth = 0usize;
2142    let mut brace_depth = 0usize;
2143    let mut bracket_depth = 0usize;
2144
2145    for index in start_index..end_index {
2146        let token = tokens.get(index)?;
2147        match token.kind {
2148            SyntaxKind::LeftParen => paren_depth += 1,
2149            SyntaxKind::RightParen => paren_depth = paren_depth.saturating_sub(1),
2150            SyntaxKind::LeftBrace => brace_depth += 1,
2151            SyntaxKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
2152            SyntaxKind::LeftBracket => bracket_depth += 1,
2153            SyntaxKind::RightBracket => bracket_depth = bracket_depth.saturating_sub(1),
2154            _ => {}
2155        }
2156        if paren_depth == 0 && brace_depth == 0 && bracket_depth == 0 && predicate(token) {
2157            return Some(index);
2158        }
2159    }
2160    None
2161}
2162
2163fn matching_token_index(
2164    tokens: &[LexedToken],
2165    open_index: usize,
2166    open_kind: SyntaxKind,
2167    close_kind: SyntaxKind,
2168) -> Option<usize> {
2169    let mut depth = 0usize;
2170    for index in open_index..tokens.len() {
2171        match tokens.get(index)?.kind {
2172            kind if kind == open_kind => depth += 1,
2173            kind if kind == close_kind => {
2174                depth = depth.checked_sub(1)?;
2175                if depth == 0 {
2176                    return Some(index);
2177                }
2178            }
2179            _ => {}
2180        }
2181    }
2182    None
2183}
2184
2185fn next_matching_token_index(
2186    tokens: &[LexedToken],
2187    start_index: usize,
2188    predicate: impl Fn(&LexedToken) -> bool,
2189) -> Option<usize> {
2190    tokens
2191        .iter()
2192        .enumerate()
2193        .skip(start_index)
2194        .find_map(|(index, token)| predicate(token).then_some(index))
2195}
2196
2197fn next_non_trivia_token_index(tokens: &[LexedToken], start_index: usize) -> Option<usize> {
2198    tokens
2199        .iter()
2200        .enumerate()
2201        .skip(start_index)
2202        .find_map(|(index, token)| (!token.kind.is_trivia()).then_some(index))
2203}
2204
2205fn next_non_trivia_token_index_until(
2206    tokens: &[LexedToken],
2207    start_index: usize,
2208    end_index: usize,
2209) -> Option<usize> {
2210    tokens
2211        .iter()
2212        .enumerate()
2213        .take(end_index)
2214        .skip(start_index)
2215        .find_map(|(index, token)| (!token.kind.is_trivia()).then_some(index))
2216}
2217
2218fn first_non_trivia_token_index_until(
2219    tokens: &[LexedToken],
2220    start_index: usize,
2221    end_index: usize,
2222) -> Option<usize> {
2223    next_non_trivia_token_index_until(tokens, start_index, end_index)
2224}
2225
2226fn previous_non_trivia_token_index_until(
2227    tokens: &[LexedToken],
2228    start_index: usize,
2229    end_index: usize,
2230) -> Option<usize> {
2231    tokens
2232        .iter()
2233        .enumerate()
2234        .take(end_index)
2235        .skip(start_index)
2236        .rev()
2237        .find_map(|(index, token)| (!token.kind.is_trivia()).then_some(index))
2238}
2239
2240fn previous_non_trivia_token_index(tokens: &[LexedToken], before_index: usize) -> Option<usize> {
2241    tokens
2242        .iter()
2243        .enumerate()
2244        .take(before_index)
2245        .rev()
2246        .find_map(|(index, token)| (!token.kind.is_trivia()).then_some(index))
2247}
2248
2249fn trimmed_source_between_tokens(
2250    source: &str,
2251    tokens: &[LexedToken],
2252    start_index: usize,
2253    end_index: usize,
2254) -> Option<String> {
2255    let start = first_non_trivia_token_index_until(tokens, start_index, end_index)
2256        .and_then(|index| tokens.get(index))
2257        .map(token_start)?;
2258    let end = previous_non_trivia_token_index_until(tokens, start_index, end_index)
2259        .and_then(|index| tokens.get(index))
2260        .map(token_end)?;
2261    source
2262        .get(start..end)
2263        .map(str::trim)
2264        .and_then(|value| (!value.is_empty()).then(|| value.to_string()))
2265}
2266
2267fn token_start(token: &LexedToken) -> usize {
2268    u32::from(token.range.start()) as usize
2269}
2270
2271fn token_end(token: &LexedToken) -> usize {
2272    u32::from(token.range.end()) as usize
2273}
2274
2275fn parameter_syntax_is_supported(syntax: Option<&RegisteredPropertySyntaxV0>) -> bool {
2276    matches!(
2277        syntax,
2278        Some(RegisteredPropertySyntaxV0::Universal | RegisteredPropertySyntaxV0::Supported { .. })
2279    )
2280}
2281
2282fn extract_named_function_inner<'a>(condition: &'a str, name: &str) -> Option<&'a str> {
2283    let trimmed = condition.trim();
2284    let prefix = trimmed.get(..name.len())?;
2285    if !prefix.eq_ignore_ascii_case(name) {
2286        return None;
2287    }
2288    let rest = trimmed[name.len()..].trim_start();
2289    if !rest.starts_with('(') {
2290        return None;
2291    }
2292    let close_index = matching_closing_paren_byte_index(rest)?;
2293    rest[close_index + 1..]
2294        .trim()
2295        .is_empty()
2296        .then_some(&rest[1..close_index])
2297}
2298
2299fn extract_exact_var_reference(value: &str) -> Option<&str> {
2300    let inner = extract_named_function_inner(value, "var")?;
2301    let name = inner.trim();
2302    (name.starts_with("--") && !name.contains(',')).then_some(name)
2303}
2304
2305fn native_css_function_name_is_declaration(tokens: &[LexedToken], name_index: usize) -> bool {
2306    previous_non_trivia_token_index(tokens, name_index)
2307        .and_then(|index| tokens.get(index))
2308        .is_some_and(|token| {
2309            token.kind == SyntaxKind::AtKeyword && token.text.eq_ignore_ascii_case("@function")
2310        })
2311}
2312
2313fn matching_closing_paren_byte_index(value: &str) -> Option<usize> {
2314    let mut depth = 0usize;
2315    for (index, ch) in value.char_indices() {
2316        match ch {
2317            '(' => depth += 1,
2318            ')' => {
2319                depth = depth.checked_sub(1)?;
2320                if depth == 0 {
2321                    return Some(index);
2322                }
2323            }
2324            _ => {}
2325        }
2326    }
2327    None
2328}
2329
2330fn normalize_supports_condition_for_if(condition: &str) -> String {
2331    let trimmed = condition.trim();
2332    if trimmed.starts_with('(') {
2333        trimmed.to_string()
2334    } else {
2335        format!("({trimmed})")
2336    }
2337}
2338
2339fn static_supports_verdict_label(verdict: StaticSupportsEvalVerdictV0) -> &'static str {
2340    match verdict {
2341        StaticSupportsEvalVerdictV0::AlwaysTrue => "alwaysTrue",
2342        StaticSupportsEvalVerdictV0::AlwaysFalse => "alwaysFalse",
2343        StaticSupportsEvalVerdictV0::Unknown => "unknown",
2344    }
2345}
2346
2347fn native_css_if_value_is_fully_static(value: &str) -> bool {
2348    let trimmed = value.trim();
2349    if trimmed.is_empty() {
2350        return false;
2351    }
2352    let lower = trimmed.to_ascii_lowercase();
2353    ![
2354        "var(",
2355        "attr(",
2356        "env(",
2357        "style(",
2358        "media(",
2359        "supports(",
2360        "if(",
2361        "--",
2362    ]
2363    .iter()
2364    .any(|marker| lower.contains(marker))
2365}
2366
2367#[cfg(test)]
2368mod tests {
2369    use omena_abstract_value::RegisteredPropertySyntaxV0;
2370    use omena_parser::StyleDialect;
2371    use omena_transform_cst::lower_transform_ir_from_source;
2372
2373    use super::{
2374        native_css_branch_truthiness_by_start, summarize_native_css_function_call_evaluations,
2375        summarize_native_css_function_surface, summarize_native_css_if_function_decisions,
2376        summarize_native_css_static_edit_plan,
2377        summarize_native_css_static_edit_plan_from_transform_ir,
2378    };
2379
2380    #[test]
2381    fn native_css_function_surface_reuses_registered_property_syntax() {
2382        let source = "@function --gap(--size <length>: 1rem, --tone <color>) returns <length> { result: var(--size); }";
2383        let report = summarize_native_css_function_surface(source, StyleDialect::Css);
2384        assert!(report.is_some());
2385        let Some(report) = report else {
2386            return;
2387        };
2388
2389        assert_eq!(report.mode, "recognitionOnly");
2390        assert_eq!(report.function_count, 1);
2391        assert_eq!(report.parameter_count, 2);
2392        assert_eq!(report.typed_parameter_count, 2);
2393        assert_eq!(report.supported_parameter_syntax_count, 2);
2394        assert_eq!(report.result_count, 1);
2395        assert_eq!(report.functions[0].name, "--gap");
2396        assert_eq!(
2397            report.functions[0].return_syntax,
2398            Some(RegisteredPropertySyntaxV0::Supported {
2399                alternatives: vec![
2400                    omena_abstract_value::RegisteredPropertySyntaxAlternativeV0::Sequence {
2401                        components: vec![
2402                            omena_abstract_value::RegisteredPropertySyntaxComponentV0 {
2403                                base: omena_abstract_value::RegisteredPropertySyntaxBaseV0::Length,
2404                                multiplier:
2405                                    omena_abstract_value::RegisteredPropertySyntaxMultiplierV0::One,
2406                            },
2407                        ],
2408                    },
2409                ]
2410            })
2411        );
2412        let mut parameter_name = String::new();
2413        let write_result = report.functions[0].parameters[0]
2414            .name
2415            .write_into(&mut parameter_name);
2416        assert!(write_result.is_ok(), "writing into a String must succeed");
2417        assert_eq!(parameter_name, "--size");
2418        assert_eq!(
2419            report.functions[0].parameters[0].syntax_source.as_deref(),
2420            Some("<length>")
2421        );
2422        assert_eq!(
2423            report.functions[0].parameters[0].default_value.as_deref(),
2424            Some("1rem")
2425        );
2426        assert_eq!(report.functions[0].results[0].value, "var(--size)");
2427    }
2428
2429    #[test]
2430    fn native_css_function_surface_stays_css_dialect_only() {
2431        let source = "@function gap($size) { @return $size; }";
2432
2433        assert!(summarize_native_css_function_surface(source, StyleDialect::Scss).is_none());
2434    }
2435
2436    #[test]
2437    fn native_css_function_call_evaluation_folds_static_result_binding() {
2438        let source = "@function --gap(--size <length>: 1rem) returns <length> { result: var(--size); } .card { gap: --gap(2rem); }";
2439        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2440        assert!(report.is_some());
2441        let Some(report) = report else {
2442            return;
2443        };
2444
2445        assert_eq!(report.mode, "oracleOnlyPruneButKeep");
2446        assert_eq!(report.call_count, 1);
2447        assert_eq!(report.foldable_call_count, 1);
2448        assert_eq!(report.preserved_call_count, 0);
2449        assert_eq!(report.structural_error_count, 0);
2450        assert_eq!(report.calls[0].name, "--gap");
2451        assert_eq!(report.calls[0].argument_count, 1);
2452        assert_eq!(report.calls[0].matched_function_count, 1);
2453        assert_eq!(report.calls[0].decision, "foldToStaticValue");
2454        assert_eq!(report.calls[0].evaluated_value.as_deref(), Some("2rem"));
2455        assert_eq!(report.calls[0].arguments[0].value, "2rem");
2456        assert!(report.calls[0].arguments[0].static_value);
2457    }
2458
2459    #[test]
2460    fn native_css_function_parameter_binding_decodes_before_case_sensitive_join() {
2461        let source = r#"@function --echo(--P\61 ram <length>, --param <color>) returns <length> { result: var(--Param); } .card { width: --echo(2rem, red); }"#;
2462        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2463        assert!(report.is_some());
2464        let Some(report) = report else {
2465            return;
2466        };
2467
2468        assert_eq!(report.call_count, 1);
2469        assert_eq!(report.foldable_call_count, 1);
2470        assert_eq!(report.preserved_call_count, 0);
2471        assert_eq!(report.calls[0].decision, "foldToStaticValue");
2472        assert_eq!(report.calls[0].evaluated_value.as_deref(), Some("2rem"));
2473    }
2474
2475    #[test]
2476    fn native_css_function_parameter_binding_joins_escape_equivalent_identity() {
2477        let source = r#"@function --echo(--P\61 ram <length>) returns <length> { result: var(--Param); } .card { width: --echo(2rem); }"#;
2478        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2479        assert!(report.is_some());
2480        let Some(report) = report else {
2481            return;
2482        };
2483
2484        assert_eq!(report.call_count, 1);
2485        assert_eq!(report.foldable_call_count, 1);
2486        assert_eq!(report.preserved_call_count, 0);
2487        assert_eq!(report.calls[0].decision, "foldToStaticValue");
2488        assert_eq!(report.calls[0].evaluated_value.as_deref(), Some("2rem"));
2489    }
2490
2491    #[test]
2492    fn native_css_function_call_evaluation_folds_negative_dimensions() {
2493        let source = "@function --offset(--value <length>) returns <length> { result: var(--value); } .card { margin: --offset(-10px); }";
2494        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2495        assert!(report.is_some());
2496        let Some(report) = report else {
2497            return;
2498        };
2499
2500        assert_eq!(report.call_count, 1);
2501        assert_eq!(report.foldable_call_count, 1);
2502        assert_eq!(report.preserved_call_count, 0);
2503        assert_eq!(report.structural_error_count, 0);
2504        assert_eq!(report.calls[0].decision, "foldToStaticValue");
2505        assert_eq!(report.calls[0].evaluated_value.as_deref(), Some("-10px"));
2506    }
2507
2508    #[test]
2509    fn native_css_function_call_evaluation_folds_static_if_result_through_edge_ir() {
2510        let source = "@function --gap() returns <length> { result: if(supports(display: grid): 2rem; else: 1rem); } .card { gap: --gap(); }";
2511        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2512        assert!(report.is_some());
2513        let Some(report) = report else {
2514            return;
2515        };
2516
2517        assert_eq!(report.call_count, 1);
2518        assert_eq!(report.foldable_call_count, 1);
2519        assert_eq!(report.preserved_call_count, 0);
2520        assert_eq!(report.calls[0].decision, "foldToStaticValue");
2521        assert_eq!(report.calls[0].evaluated_value.as_deref(), Some("2rem"));
2522    }
2523
2524    #[test]
2525    fn native_css_function_call_evaluation_preserves_runtime_argument() {
2526        let source = "@function --gap(--size <length>: 1rem) returns <length> { result: var(--size); } .card { gap: --gap(var(--space)); }";
2527        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2528        assert!(report.is_some());
2529        let Some(report) = report else {
2530            return;
2531        };
2532
2533        assert_eq!(report.call_count, 1);
2534        assert_eq!(report.foldable_call_count, 0);
2535        assert_eq!(report.preserved_call_count, 1);
2536        assert_eq!(report.runtime_dependent_call_count, 1);
2537        assert_eq!(report.calls[0].decision, "preserveVerbatim");
2538        assert_eq!(
2539            report.calls[0].reason,
2540            "argument or default value depends on runtime or cascade state"
2541        );
2542        assert!(!report.calls[0].arguments[0].static_value);
2543    }
2544
2545    #[test]
2546    fn native_css_function_call_evaluation_rejects_parameter_syntax_mismatch() {
2547        let source = "@function --gap(--size <length>) returns <length> { result: var(--size); } .card { gap: --gap(red); }";
2548        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2549        assert!(report.is_some());
2550        let Some(report) = report else {
2551            return;
2552        };
2553
2554        assert_eq!(report.call_count, 1);
2555        assert_eq!(report.foldable_call_count, 0);
2556        assert_eq!(report.structural_error_count, 1);
2557        assert_eq!(report.calls[0].decision, "structuralError");
2558        assert_eq!(
2559            report.calls[0].reason,
2560            "argument does not match parameter syntax"
2561        );
2562        assert_eq!(report.calls[0].evaluated_value, None);
2563    }
2564
2565    #[test]
2566    fn native_css_function_call_evaluation_rejects_return_syntax_mismatch() {
2567        let source = "@function --tone(--size <length>) returns <color> { result: var(--size); } .card { color: --tone(2rem); }";
2568        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2569        assert!(report.is_some());
2570        let Some(report) = report else {
2571            return;
2572        };
2573
2574        assert_eq!(report.call_count, 1);
2575        assert_eq!(report.foldable_call_count, 0);
2576        assert_eq!(report.structural_error_count, 1);
2577        assert_eq!(report.calls[0].decision, "structuralError");
2578        assert_eq!(
2579            report.calls[0].reason,
2580            "result value does not match return syntax"
2581        );
2582        assert_eq!(report.calls[0].evaluated_value, None);
2583    }
2584
2585    #[test]
2586    fn native_css_function_call_evaluation_preserves_unknown_return_syntax_match() {
2587        let source = "@function --tone(--value) returns <future-value> { result: var(--value); } .card { color: --tone(customvalue); }";
2588        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2589        assert!(report.is_some());
2590        let Some(report) = report else {
2591            return;
2592        };
2593
2594        assert_eq!(report.call_count, 1);
2595        assert_eq!(report.foldable_call_count, 0);
2596        assert_eq!(report.preserved_call_count, 1);
2597        assert_eq!(report.structural_error_count, 0);
2598        assert_eq!(report.calls[0].decision, "preserveVerbatim");
2599        assert_eq!(
2600            report.calls[0].reason,
2601            "result return syntax match is unknown"
2602        );
2603        assert_eq!(report.calls[0].evaluated_value, None);
2604    }
2605
2606    #[test]
2607    fn native_css_function_call_evaluation_surfaces_direct_result_cycle() {
2608        let source =
2609            "@function --loop() returns <length> { result: --loop(); } .card { width: --loop(); }";
2610        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2611        assert!(report.is_some());
2612        let Some(report) = report else {
2613            return;
2614        };
2615
2616        assert_eq!(report.foldable_call_count, 0);
2617        assert!(report.structural_error_count >= 1);
2618        assert!(report.calls.iter().any(|call| {
2619            call.name == "--loop"
2620                && call.decision == "structuralError"
2621                && call.reason == "function result call graph contains a guaranteed cycle"
2622        }));
2623    }
2624
2625    #[test]
2626    fn native_css_function_call_evaluation_surfaces_mutual_result_cycle() {
2627        let source = "@function --a() returns <length> { result: --b(); } @function --b() returns <length> { result: --a(); } .card { width: --a(); height: --b(); }";
2628        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2629        assert!(report.is_some());
2630        let Some(report) = report else {
2631            return;
2632        };
2633
2634        assert_eq!(report.foldable_call_count, 0);
2635        assert!(report.structural_error_count >= 2);
2636        assert!(report.calls.iter().any(|call| {
2637            call.name == "--a"
2638                && call.decision == "structuralError"
2639                && call.reason == "function result call graph contains a guaranteed cycle"
2640        }));
2641        assert!(report.calls.iter().any(|call| {
2642            call.name == "--b"
2643                && call.decision == "structuralError"
2644                && call.reason == "function result call graph contains a guaranteed cycle"
2645        }));
2646    }
2647
2648    #[test]
2649    fn native_css_function_call_evaluation_preserves_ambiguous_result_cycle() {
2650        let source = "@function --a() returns <length> { result: --b(); } @function --b() returns <length> { result: --a(); } @function --b() returns <length> { result: 1px; } .card { width: --a(); }";
2651        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2652        assert!(report.is_some());
2653        let Some(report) = report else {
2654            return;
2655        };
2656
2657        assert_eq!(report.foldable_call_count, 0);
2658        assert_eq!(report.structural_error_count, 0);
2659        assert!(report.calls.iter().any(|call| {
2660            call.name == "--a"
2661                && call.decision == "preserveVerbatim"
2662                && call.reason == "result value depends on runtime or cascade state"
2663        }));
2664    }
2665
2666    #[test]
2667    fn native_css_function_call_evaluation_surfaces_missing_result() {
2668        let source = "@function --gap(--size <length>) returns <length> { color: red; } .card { gap: --gap(2rem); }";
2669        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2670        assert!(report.is_some());
2671        let Some(report) = report else {
2672            return;
2673        };
2674
2675        assert_eq!(report.call_count, 1);
2676        assert_eq!(report.foldable_call_count, 0);
2677        assert_eq!(report.structural_error_count, 1);
2678        assert_eq!(report.missing_result_count, 1);
2679        assert_eq!(report.calls[0].decision, "structuralError");
2680        assert_eq!(report.calls[0].reason, "function has no result declaration");
2681    }
2682
2683    #[test]
2684    fn native_css_function_call_evaluation_surfaces_missing_required_argument() {
2685        let source = "@function --gap(--size <length>) returns <length> { result: var(--size); } .card { gap: --gap(); }";
2686        let report = summarize_native_css_function_call_evaluations(source, StyleDialect::Css);
2687        assert!(report.is_some());
2688        let Some(report) = report else {
2689            return;
2690        };
2691
2692        assert_eq!(report.call_count, 1);
2693        assert_eq!(report.foldable_call_count, 0);
2694        assert_eq!(report.structural_error_count, 1);
2695        assert_eq!(report.calls[0].decision, "structuralError");
2696        assert_eq!(report.calls[0].reason, "required argument is missing");
2697    }
2698
2699    #[test]
2700    fn native_css_function_call_evaluation_stays_css_dialect_only() {
2701        let source = "@function --gap(--size <length>) returns <length> { result: var(--size); } .card { gap: --gap(2rem); }";
2702
2703        assert!(
2704            summarize_native_css_function_call_evaluations(source, StyleDialect::Scss).is_none()
2705        );
2706    }
2707
2708    #[test]
2709    fn native_css_static_edit_plan_folds_static_if_and_function_call_values() {
2710        let source = "@function --gap(--size <length>: 1rem) returns <length> { result: var(--size); } .card { gap: --gap(2rem); display: if(supports(display: grid): grid; else: block); }";
2711        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2712        assert!(report.is_some());
2713        let Some(report) = report else {
2714            return;
2715        };
2716
2717        assert_eq!(report.mode, "staticSubsetPruneButKeep");
2718        assert_eq!(report.edit_count, 2);
2719        assert_eq!(report.if_function_edit_count, 1);
2720        assert_eq!(report.function_call_edit_count, 1);
2721        assert!(report.output_changed);
2722        assert!(report.edited_css.contains("gap: 2rem"));
2723        assert!(report.edited_css.contains("display: grid"));
2724        assert!(!report.edited_css.contains("--gap(2rem)"));
2725        assert!(!report.edited_css.contains("if(supports"));
2726        assert_eq!(report.edits[0].edit_kind, "functionCallValueFold");
2727        assert_eq!(report.edits[1].edit_kind, "ifFunctionValueFold");
2728    }
2729
2730    #[test]
2731    fn native_css_static_edit_plan_from_transform_ir_matches_source_plan() {
2732        let source = "@function --gap(--size <length>: 1rem) returns <length> { result: var(--size); } .card { gap: --gap(2rem); } @when supports(display: grid) { .grid { display: if(supports(display: grid): grid; else: block); } } @else { .fallback { display: block; } } @if supports(display: grid) { .native-if { display: grid; } } @else { .native-if { display: block; } }";
2733        let ir = lower_transform_ir_from_source(source, StyleDialect::Css, "native-css-static");
2734        let source_plan = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2735        let ir_plan =
2736            summarize_native_css_static_edit_plan_from_transform_ir(&ir, StyleDialect::Css);
2737
2738        assert!(source_plan.is_some());
2739        assert!(ir_plan.is_some());
2740        let Some(source_plan) = source_plan else {
2741            return;
2742        };
2743        let Some(ir_plan) = ir_plan else {
2744            return;
2745        };
2746
2747        assert_eq!(ir_plan.edits, source_plan.edits);
2748        assert_eq!(ir_plan.edit_count, source_plan.edit_count);
2749        assert_eq!(
2750            ir_plan.when_rule_edit_count,
2751            source_plan.when_rule_edit_count
2752        );
2753        assert_eq!(ir_plan.if_rule_edit_count, source_plan.if_rule_edit_count);
2754        assert_eq!(
2755            ir_plan.if_function_edit_count,
2756            source_plan.if_function_edit_count
2757        );
2758        assert_eq!(
2759            ir_plan.function_call_edit_count,
2760            source_plan.function_call_edit_count
2761        );
2762        assert_eq!(ir_plan.output_changed, source_plan.output_changed);
2763        assert_eq!(ir_plan.edited_css, source_plan.edited_css);
2764    }
2765
2766    #[test]
2767    fn native_css_static_edit_plan_from_transform_ir_folds_if_rule_else_branch() {
2768        let source = "@if supports(display: -ms-grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2769        let ir = lower_transform_ir_from_source(source, StyleDialect::Css, "native-css-static");
2770        let report =
2771            summarize_native_css_static_edit_plan_from_transform_ir(&ir, StyleDialect::Css);
2772        assert!(report.is_some());
2773        let Some(report) = report else {
2774            return;
2775        };
2776
2777        assert_eq!(report.edit_count, 1);
2778        assert_eq!(report.if_rule_edit_count, 1);
2779        assert!(report.output_changed);
2780        assert!(report.edited_css.contains(".fallback { display: block; }"));
2781        assert!(!report.edited_css.contains("@if"));
2782        assert!(!report.edited_css.contains(".grid"));
2783    }
2784
2785    #[test]
2786    fn native_css_static_edit_plan_preserves_function_body_calls() {
2787        let source = "@function --inner() returns <length> { result: 1px; } @function --outer() returns <length> { result: --inner(); } .card { width: --inner(); }";
2788        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2789        assert!(report.is_some());
2790        let Some(report) = report else {
2791            return;
2792        };
2793
2794        assert_eq!(report.edit_count, 1);
2795        assert_eq!(report.function_call_edit_count, 1);
2796        assert!(report.output_changed);
2797        assert!(report.edited_css.contains("result: --inner();"));
2798        assert!(report.edited_css.contains("width: 1px"));
2799    }
2800
2801    #[test]
2802    fn native_css_static_edit_plan_folds_function_result_if_without_rewriting_function_body() {
2803        let source = "@function --gap() returns <length> { result: if(supports(display: grid): 2rem; else: 1rem); } .card { gap: --gap(); }";
2804        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2805        assert!(report.is_some());
2806        let Some(report) = report else {
2807            return;
2808        };
2809
2810        assert_eq!(report.edit_count, 1);
2811        assert_eq!(report.if_function_edit_count, 0);
2812        assert_eq!(report.function_call_edit_count, 1);
2813        assert!(report.output_changed);
2814        assert!(
2815            report
2816                .edited_css
2817                .contains("result: if(supports(display: grid): 2rem; else: 1rem);")
2818        );
2819        assert!(report.edited_css.contains("gap: 2rem"));
2820        assert!(!report.edited_css.contains("--gap();"));
2821    }
2822
2823    #[test]
2824    fn native_css_static_edit_plan_folds_static_when_rule_true_branch() {
2825        let source = "@when supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2826        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2827        assert!(report.is_some());
2828        let Some(report) = report else {
2829            return;
2830        };
2831
2832        assert_eq!(report.edit_count, 1);
2833        assert_eq!(report.when_rule_edit_count, 1);
2834        assert_eq!(report.if_function_edit_count, 0);
2835        assert_eq!(report.function_call_edit_count, 0);
2836        assert!(report.output_changed);
2837        assert!(report.edited_css.contains(".grid { display: grid; }"));
2838        assert!(!report.edited_css.contains("@when"));
2839        assert!(!report.edited_css.contains(".fallback"));
2840        assert_eq!(report.edits[0].edit_kind, "whenRuleBranchFold");
2841    }
2842
2843    #[test]
2844    fn native_css_static_edit_plan_folds_static_if_rule_true_branch() {
2845        let source = "@if supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2846        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2847        assert!(report.is_some());
2848        let Some(report) = report else {
2849            return;
2850        };
2851
2852        assert_eq!(report.edit_count, 1);
2853        assert_eq!(report.when_rule_edit_count, 0);
2854        assert_eq!(report.if_rule_edit_count, 1);
2855        assert_eq!(report.if_function_edit_count, 0);
2856        assert_eq!(report.function_call_edit_count, 0);
2857        assert!(report.output_changed);
2858        assert!(report.edited_css.contains(".grid { display: grid; }"));
2859        assert!(!report.edited_css.contains("@if"));
2860        assert!(!report.edited_css.contains(".fallback"));
2861        assert_eq!(report.edits[0].edit_kind, "ifRuleBranchFold");
2862    }
2863
2864    #[test]
2865    fn native_css_static_edit_plan_consumes_edge_ir_when_truthiness() {
2866        let source = "@when supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2867        let truthiness_by_start = native_css_branch_truthiness_by_start(source, "@when");
2868
2869        assert_eq!(truthiness_by_start.get(&0), Some(&true));
2870
2871        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2872        assert!(report.is_some());
2873        let Some(report) = report else {
2874            return;
2875        };
2876
2877        assert_eq!(report.when_rule_edit_count, 1);
2878        assert!(report.edited_css.contains(".grid { display: grid; }"));
2879        assert!(!report.edited_css.contains(".fallback"));
2880    }
2881
2882    #[test]
2883    fn native_css_static_edit_plan_consumes_edge_ir_if_rule_truthiness() {
2884        let source = "@if supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2885        let truthiness_by_start = native_css_branch_truthiness_by_start(source, "@if");
2886
2887        assert_eq!(truthiness_by_start.get(&0), Some(&true));
2888
2889        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2890        assert!(report.is_some());
2891        let Some(report) = report else {
2892            return;
2893        };
2894
2895        assert_eq!(report.if_rule_edit_count, 1);
2896        assert!(report.edited_css.contains(".grid { display: grid; }"));
2897        assert!(!report.edited_css.contains(".fallback"));
2898    }
2899
2900    #[test]
2901    fn native_css_static_edit_plan_consumes_edge_ir_if_function_truthiness() {
2902        let source = ".card { display: if(supports(display: grid): grid; else: block); }";
2903        let truthiness_by_start = native_css_branch_truthiness_by_start(source, "if()");
2904
2905        assert_eq!(truthiness_by_start.len(), 1);
2906        assert!(truthiness_by_start.values().all(|truthy| *truthy));
2907
2908        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2909        assert!(report.is_some());
2910        let Some(report) = report else {
2911            return;
2912        };
2913
2914        assert_eq!(report.if_function_edit_count, 1);
2915        assert!(report.edited_css.contains("display: grid"));
2916        assert!(!report.edited_css.contains("if(supports"));
2917    }
2918
2919    #[test]
2920    fn native_css_static_edit_plan_composes_nested_static_when_body_edits() {
2921        let source = "@when supports(display: grid) { .grid { display: if(supports(display: grid): grid; else: block); } } @else { .fallback { display: block; } }";
2922        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2923        assert!(report.is_some());
2924        let Some(report) = report else {
2925            return;
2926        };
2927
2928        assert_eq!(report.edit_count, 1);
2929        assert_eq!(report.when_rule_edit_count, 1);
2930        assert!(report.output_changed);
2931        assert!(report.edited_css.contains(".grid { display: grid; }"));
2932        assert!(!report.edited_css.contains("@when"));
2933        assert!(!report.edited_css.contains("if(supports"));
2934        assert!(!report.edited_css.contains(".fallback"));
2935    }
2936
2937    #[test]
2938    fn native_css_static_edit_plan_composes_nested_static_if_rule_edits() {
2939        let source = "@if supports(display: grid) { .grid { display: if(supports(display: grid): grid; else: block); } } @else { .fallback { display: block; } }";
2940        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2941        assert!(report.is_some());
2942        let Some(report) = report else {
2943            return;
2944        };
2945
2946        assert_eq!(report.edit_count, 1);
2947        assert_eq!(report.if_rule_edit_count, 1);
2948        assert!(report.output_changed);
2949        assert!(report.edited_css.contains(".grid { display: grid; }"));
2950        assert!(!report.edited_css.contains("@if"));
2951        assert!(!report.edited_css.contains("if(supports"));
2952        assert!(!report.edited_css.contains(".fallback"));
2953    }
2954
2955    #[test]
2956    fn native_css_static_edit_plan_folds_static_when_rule_else_branch() {
2957        let source = "@when supports(display: -ms-grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2958        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2959        assert!(report.is_some());
2960        let Some(report) = report else {
2961            return;
2962        };
2963
2964        assert_eq!(report.edit_count, 1);
2965        assert_eq!(report.when_rule_edit_count, 1);
2966        assert!(report.edited_css.contains(".fallback { display: block; }"));
2967        assert!(!report.edited_css.contains("@when"));
2968        assert!(!report.edited_css.contains(".grid"));
2969    }
2970
2971    #[test]
2972    fn native_css_static_edit_plan_folds_static_if_rule_else_branch() {
2973        let source = "@if supports(display: -ms-grid) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2974        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2975        assert!(report.is_some());
2976        let Some(report) = report else {
2977            return;
2978        };
2979
2980        assert_eq!(report.edit_count, 1);
2981        assert_eq!(report.if_rule_edit_count, 1);
2982        assert!(report.edited_css.contains(".fallback { display: block; }"));
2983        assert!(!report.edited_css.contains("@if"));
2984        assert!(!report.edited_css.contains(".grid"));
2985    }
2986
2987    #[test]
2988    fn native_css_static_edit_plan_preserves_runtime_when_rule() {
2989        let source = "@when media(width >= 1px) { .grid { display: grid; } } @else { .fallback { display: block; } }";
2990        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
2991        assert!(report.is_some());
2992        let Some(report) = report else {
2993            return;
2994        };
2995
2996        assert_eq!(report.edit_count, 0);
2997        assert_eq!(report.when_rule_edit_count, 0);
2998        assert!(!report.output_changed);
2999        assert_eq!(report.edited_css, source);
3000    }
3001
3002    #[test]
3003    fn native_css_static_edit_plan_preserves_runtime_if_rule() {
3004        let source = "@if media(width >= 1px) { .grid { display: grid; } } @else { .fallback { display: block; } }";
3005        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
3006        assert!(report.is_some());
3007        let Some(report) = report else {
3008            return;
3009        };
3010
3011        assert_eq!(report.edit_count, 0);
3012        assert_eq!(report.if_rule_edit_count, 0);
3013        assert!(!report.output_changed);
3014        assert_eq!(report.edited_css, source);
3015    }
3016
3017    #[test]
3018    fn native_css_static_edit_plan_preserves_runtime_native_values() {
3019        let source = "@function --gap(--size <length>: 1rem) returns <length> { result: var(--size); } .card { gap: --gap(var(--space)); margin: if(media(width >= 1px): 1rem; else: 2rem); }";
3020        let report = summarize_native_css_static_edit_plan(source, StyleDialect::Css);
3021        assert!(report.is_some());
3022        let Some(report) = report else {
3023            return;
3024        };
3025
3026        assert_eq!(report.edit_count, 0);
3027        assert_eq!(report.if_function_edit_count, 0);
3028        assert_eq!(report.function_call_edit_count, 0);
3029        assert!(!report.output_changed);
3030        assert_eq!(report.edited_css, source);
3031    }
3032
3033    #[test]
3034    fn native_css_static_edit_plan_stays_css_dialect_only() {
3035        let source = ".card { display: if(supports(display: grid): grid; else: block); }";
3036
3037        assert!(summarize_native_css_static_edit_plan(source, StyleDialect::Scss).is_none());
3038    }
3039
3040    #[test]
3041    fn native_css_if_function_decision_folds_static_supports_branch() {
3042        let source = ".card { display: if(supports(display: grid): grid; else: block); }";
3043        let report = summarize_native_css_if_function_decisions(source, StyleDialect::Css);
3044        assert!(report.is_some());
3045        let Some(report) = report else {
3046            return;
3047        };
3048
3049        assert_eq!(report.mode, "oracleOnlyPruneButKeep");
3050        assert_eq!(report.function_count, 1);
3051        assert_eq!(report.foldable_function_count, 1);
3052        assert_eq!(report.preserved_function_count, 0);
3053        assert_eq!(report.static_supports_branch_count, 1);
3054        assert_eq!(report.runtime_branch_count, 0);
3055        assert_eq!(report.functions[0].decision, "foldToStaticValue");
3056        assert_eq!(report.functions[0].selected_branch_index, Some(0));
3057        assert_eq!(report.functions[0].selected_value.as_deref(), Some("grid"));
3058        assert_eq!(report.functions[0].branches[0].condition_kind, "supports");
3059        assert_eq!(report.functions[0].branches[0].verdict, "alwaysTrue");
3060        assert!(
3061            report.functions[0].branches[0]
3062                .supports_witness
3063                .as_ref()
3064                .is_some_and(|witness| witness.product == "omena-cascade.supports-static-eval")
3065        );
3066    }
3067
3068    #[test]
3069    fn native_css_if_function_decision_preserves_runtime_media_branch() {
3070        let source = ".card { margin: if(media(width >= 1px): 1rem; else: 2rem); }";
3071        let report = summarize_native_css_if_function_decisions(source, StyleDialect::Css);
3072        assert!(report.is_some());
3073        let Some(report) = report else {
3074            return;
3075        };
3076
3077        assert_eq!(report.function_count, 1);
3078        assert_eq!(report.foldable_function_count, 0);
3079        assert_eq!(report.preserved_function_count, 1);
3080        assert_eq!(report.static_supports_branch_count, 0);
3081        assert_eq!(report.runtime_branch_count, 1);
3082        assert_eq!(report.functions[0].decision, "preserveVerbatim");
3083        assert_eq!(
3084            report.functions[0].reason,
3085            "encountered runtime condition before a static winner"
3086        );
3087        assert_eq!(report.functions[0].selected_branch_index, None);
3088        assert_eq!(report.functions[0].branches[0].condition_kind, "media");
3089        assert_eq!(report.functions[0].branches[0].verdict, "runtime");
3090    }
3091
3092    #[test]
3093    fn native_css_if_function_decision_preserves_runtime_value() {
3094        let source = ".card { color: if(supports(color: red): var(--accent); else: blue); }";
3095        let report = summarize_native_css_if_function_decisions(source, StyleDialect::Css);
3096        assert!(report.is_some());
3097        let Some(report) = report else {
3098            return;
3099        };
3100
3101        assert_eq!(report.function_count, 1);
3102        assert_eq!(report.functions[0].decision, "preserveVerbatim");
3103        assert_eq!(
3104            report.functions[0].reason,
3105            "selected branch value depends on runtime or cascade state"
3106        );
3107        assert_eq!(report.functions[0].selected_value, None);
3108    }
3109
3110    #[test]
3111    fn native_css_if_function_decision_stays_css_dialect_only() {
3112        let source = ".card { width: if(supports(width: 1px): 1px; else: 2px); }";
3113
3114        assert!(summarize_native_css_if_function_decisions(source, StyleDialect::Scss).is_none());
3115    }
3116}