Repository: quarkusio/quarkus
Issue: Qute: bug in `if` test

### Describe the bug

I have the following tag:

```html
{#if user && target.status == ContentStatus:NEW && !target.voted(user)}
    User: {user}<br/>
    Target.status: {target.status}<br/>
    Target.voted(user): {target.voted(user)}<br/>
{/if}
```

When I call it with some values, I'm getting this output:

```
User: User<2>
Target.status: ACCEPTED
Target.voted(user): false
```

This can't be right. As you see `target.status` is `ACCEPTED` (that's the correct value) but the `if` condition specifies `target.status == ContentStatus:NEW` so in theory we should never be able to get into the `if` block and display this.

I can give you instructions for reproducing but it requires you build the vixen branch and checkout the aviouf demo app. Otherwise you can perhaps point me where to look in a debugger to help you narrow it down?

### Expected behavior

_No response_

### Actual behavior

_No response_

### How to Reproduce?

_No response_

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

_No response_

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

_No response_

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

_No response_

### Additional information

_No response_

Changed files:
independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java

diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
index 2bfcaf27a64..ce693ba386d 100644
--- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
+++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/IfSectionHelper.java
@@ -14,7 +14,6 @@
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
-import java.util.stream.Collectors;
 
 /**
  * Basic {@code if} statement.
@@ -422,18 +421,77 @@ static BigDecimal getDecimal(Object value) {
 
     static List<Object> parseParams(List<Object> params, ParserDelegate parserDelegate) {
 
-        int highestPrecedence = 0;
-        // Replace operators and composite params if needed
+        replaceOperatorsAndCompositeParams(params, parserDelegate);
+        int highestPrecedence = getHighestPrecedence(params);
+
+        if (!isGroupingNeeded(params)) {
+            // No operators or all of the same precedence
+            return params;
+        }
+
+        // Take the operators with highest precedence and form groups
+        // For example "user.active && target.status == NEW && !target.voted" becomes "user.active && [target.status == NEW] && [!target.voted]"
+        // The algorithm used is not very robust and should be improved later
+        List<Object> highestGroup = null;
+        List<Object> ret = new ArrayList<>();
+        int lastGroupdIdx = 0;
+
+        for (ListIterator<Object> iterator = params.listIterator(); iterator.hasNext();) {
+            int prevIdx = iterator.previousIndex();
+            Object param = iterator.next();
+            if (param instanceof Operator) {
+                Operator op = (Operator) param;
+                if (op.precedence == highestPrecedence) {
+                    if (highestGroup == null) {
+                        highestGroup = new ArrayList<>();
+                        if (op.isBinary()) {
+                            highestGroup.add(params.get(prevIdx));
+                        }
+                    }
+                    highestGroup.add(param);
+                    // Add non-grouped elements 
+                    if (prevIdx > lastGroupdIdx) {
+                        int from = lastGroupdIdx > 0 ? lastGroupdIdx + 1 : 0;
+                        int to = op.isBinary() ? prevIdx : prevIdx + 1;
+                        params.subList(from, to).forEach(ret::add);
+                    }
+                } else if (op.precedence < highestPrecedence) {
+                    if (highestGroup != null) {
+                        ret.add(highestGroup);
+                        lastGroupdIdx = prevIdx;
+                        highestGroup = null;
+                    }
+                } else {
+                    throw new IllegalStateException();
+                }
+            } else if (highestGroup != null) {
+                highestGroup.add(param);
+            }
+        }
+        if (highestGroup != null) {
+            ret.add(highestGroup);
+        } else {
+            // Add all remaining non-grouped elements
+            if (lastGroupdIdx + 1 != params.size()) {
+                params.subList(lastGroupdIdx + 1, params.size()).forEach(ret::add);
+            }
+        }
+        return parseParams(ret, parserDelegate);
+    }
+
+    private static boolean isGroupingNeeded(List<Object> params) {
+        // No operators or all of the same precedence
+        return params.stream().filter(p -> (p instanceof Operator)).map(p -> ((Operator) p).getPrecedence()).distinct()
+                .count() > 1;
+    }
+
+    private static void replaceOperatorsAndCompositeParams(List<Object> params, ParserDelegate parserDelegate) {
         for (ListIterator<Object> iterator = params.listIterator(); iterator.hasNext();) {
             Object param = iterator.next();
             if (param instanceof String) {
                 String stringParam = param.toString();
                 Operator operator = Operator.from(stringParam);
                 if (operator != null) {
-                    // Binary operator
-                    if (operator.getPrecedence() > highestPrecedence) {
-                        highestPrecedence = operator.getPrecedence();
-                    }
                     if (operator.isBinary() && !iterator.hasNext()) {
                         throw parserDelegate.createParserError(
                                 "binary operator [" + operator + "] set but the second operand not present for {#if} section");
@@ -457,50 +515,19 @@ static List<Object> parseParams(List<Object> params, ParserDelegate parserDelega
                 }
             }
         }
+    }
 
-        if (params.stream().filter(p -> p instanceof Operator).map(p -> ((Operator) p).getPrecedence())
-                .collect(Collectors.toSet()).size() <= 1) {
-            // No binary operators or all of the same precedence
-            return params;
-        }
-
-        // Take the operators with highest precedence and form groups
-        List<Object> highestGroup = null;
-        List<Object> ret = new ArrayList<>();
-        int lastGroupdIdx = 0;
-
-        for (ListIterator<Object> iterator = params.listIterator(); iterator.hasNext();) {
-            int prevIdx = iterator.previousIndex();
-            Object param = iterator.next();
-            if (isBinaryOperatorEq(param, highestPrecedence)) {
-                if (highestGroup == null) {
-                    highestGroup = new ArrayList<>();
-                    highestGroup.add(params.get(prevIdx));
-                }
-                highestGroup.add(param);
-                // Add non-grouped elements 
-                if (prevIdx > lastGroupdIdx) {
-                    params.subList(lastGroupdIdx > 0 ? lastGroupdIdx + 1 : 0, prevIdx).forEach(ret::add);
-                }
-            } else if (isBinaryOperatorLt(param, highestPrecedence)) {
-                if (highestGroup != null) {
-                    ret.add(highestGroup);
-                    lastGroupdIdx = prevIdx;
-                    highestGroup = null;
+    private static int getHighestPrecedence(List<Object> params) {
+        int highestPrecedence = 0;
+        for (Object param : params) {
+            if (param instanceof Operator) {
+                Operator op = (Operator) param;
+                if (op.precedence > highestPrecedence) {
+                    highestPrecedence = op.precedence;
                 }
-            } else if (highestGroup != null) {
-                highestGroup.add(param);
             }
         }
-        if (highestGroup != null) {
-            ret.add(highestGroup);
-        } else {
-            // Add all remaining non-grouped elements
-            if (lastGroupdIdx + 1 != params.size()) {
-                params.subList(lastGroupdIdx + 1, params.size()).forEach(ret::add);
-            }
-        }
-        return parseParams(ret, parserDelegate);
+        return highestPrecedence;
     }
 
     static List<Object> processCompositeParam(String stringParam, ParserDelegate parserDelegate) {
@@ -514,14 +541,6 @@ static List<Object> processCompositeParam(String stringParam, ParserDelegate par
         return parseParams(split, parserDelegate);
     }
 
-    private static boolean isBinaryOperatorEq(Object val, int precedence) {
-        return val instanceof Operator && ((Operator) val).getPrecedence() == precedence;
-    }
-
-    private static boolean isBinaryOperatorLt(Object val, int precedence) {
-        return val instanceof Operator && ((Operator) val).getPrecedence() < precedence;
-    }
-
     @SuppressWarnings("unchecked")
     static Condition createCondition(Object param, SectionBlock block, Operator operator, SectionInitContext context) {
 
diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
index 5c057c1f1ea..a8a3dc5e227 100644
--- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
+++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/IfSectionTest.java
@@ -73,6 +73,12 @@ public void testNestedIf() {
     public void testCompositeParameters() {
         Engine engine = Engine.builder().addDefaults().build();
         assertEquals("OK", engine.parse("{#if (true || false) && true}OK{/if}").render());
+        assertEquals("OK", engine.parse("{#if (true || false) && true && !false}OK{/if}").render());
+        assertEquals("OK", engine.parse("{#if  true && true && !(true || false)}NOK{#else}OK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && true  && !(true && false)}OK{#else}NOK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && !true && (true || false)}NOK{#else}OK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && (true  && ( true && false))}NOK{#else}OK{/if}").render());
+        assertEquals("OK", engine.parse("{#if true && (!false || false || (true || false))}OK{#else}NOK{/if}").render());
         assertEquals("OK", engine.parse("{#if (foo.or(false) || false || true) && (true)}OK{/if}").render());
         assertEquals("NOK", engine.parse("{#if foo.or(false) || false}OK{#else}NOK{/if}").render());
         assertEquals("OK", engine.parse("{#if false || (foo.or(false) || (false || true))}OK{#else}NOK{/if}").render());
@@ -222,6 +228,38 @@ public void testSafeExpression() {
         assertEquals("OK", engine.parse("{#if hero??}OK{#else}NOK{/if}").data("hero", true).render());
     }
 
+    @Test
+    public void testFromageCondition() {
+        Engine engine = Engine.builder().addDefaults().addValueResolver(new ReflectionValueResolver())
+                .addNamespaceResolver(NamespaceResolver.builder("ContentStatus").resolve(ec -> ContentStatus.NEW).build())
+                .build();
+        assertEquals("OK",
+                engine.parse("{#if user && target.status == ContentStatus:NEW && !target.voted(user)}NOK{#else}OK{/if}")
+                        .data("user", "Stef", "target", new Target(ContentStatus.ACCEPTED)).render());
+        assertEquals("OK",
+                engine.parse("{#if user && target.status == ContentStatus:NEW && !target.voted(user)}OK{#else}NOK{/if}")
+                        .data("user", "Stef", "target", new Target(ContentStatus.NEW)).render());
+    }
+
+    public static class Target {
+
+        public ContentStatus status;
+
+        public Target(ContentStatus status) {
+            this.status = status;
+        }
+
+        public boolean voted(String user) {
+            return false;
+        }
+
+    }
+
+    public enum ContentStatus {
+        NEW,
+        ACCEPTED
+    }
+
     private void assertParserError(String template, String message, int line) {
         Engine engine = Engine.builder().addDefaultSectionHelpers().build();
         try {