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