IGNITE-19949 Fix dynamic completer for same options (#3644)

Now you can define --option for multiple commands, and they won't be conflicting.
diff --git a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
index 12f2dc1..3622a61 100644
--- a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
+++ b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistry.java
@@ -27,6 +27,7 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Registry that holds all available dynamic completers.
@@ -115,11 +116,7 @@
             String lastNotEmptyWord = findLastNotEmptyWord(words);
             String preLastNotEmptyWord = findLastNotEmptyWordBeforeWordFromEnd(words, lastNotEmptyWord);
 
-            if (cursorWord.equals(lastNotEmptyWord)) {
-                if (exclusiveDisableOptions.contains(lastNotEmptyWord) || exclusiveDisableOptions.contains(preLastNotEmptyWord)) {
-                    return false;
-                }
-            } else if (exclusiveDisableOptions.contains(lastNotEmptyWord)) {
+            if (shouldBeDisabled(preLastNotEmptyWord, lastNotEmptyWord, cursorWord)) {
                 return false;
             }
 
@@ -154,5 +151,36 @@
                 return factory.getDynamicCompleter(words);
             }
         }
+
+        private boolean shouldBeDisabled(String preLastNotEmptyWord, String lastNotEmptyWord, String cursorWord) {
+            boolean enableOverridesDisable = intersect(conf.enableOptions(), exclusiveDisableOptions);
+            if (enableOverridesDisable) {
+                return false;
+            }
+
+            if (cursorWord.equals(lastNotEmptyWord)) {
+                if (exclusiveDisableOptions.contains(lastNotEmptyWord) || exclusiveDisableOptions.contains(preLastNotEmptyWord)) {
+                    return true;
+                }
+            } else if (exclusiveDisableOptions.contains(lastNotEmptyWord)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        private boolean intersect(@Nullable Set<String> a, @Nullable Set<String> b) {
+            if (a == null || b == null) {
+                return false;
+            }
+
+            for (String s : a) {
+                if (b.contains(s)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
     }
 }
diff --git a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
index 26ab52e..3acced6 100644
--- a/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterRegistryTest.java
@@ -81,10 +81,13 @@
         registry.register(CompleterConf.forCommand("command2"), words -> completer3);
 
         // When find completers for "command1"
-        List<DynamicCompleter> completers = registry.findCompleters(words("command1", "subcommand1"));
+        List<DynamicCompleter> completers1 = registry.findCompleters(words("command1", "subcommand1"));
 
         // Then
-        assertThat(completers, containsInAnyOrder(completer1, completer2));
+        assertThat(completers1, containsInAnyOrder(completer1, completer2));
+        // And
+        List<DynamicCompleter> completers2 = registry.findCompleters(words("command2"));
+        assertThat(completers2, contains(completer3));
     }
 
     @Test
@@ -342,4 +345,57 @@
         // Then completer is not returned
         assertThat(completersWithPositional, is(empty()));
     }
+
+    @Test
+    void sameOptionDifferentCompleters() {
+        // Given
+        registry.register(
+                CompleterConf.builder()
+                        .command("command", "subcommand1")
+                        .enableOptions("--to")
+                        .exclusiveEnableOptions().build(),
+                words -> completer1
+        );
+
+        registry.register(
+                CompleterConf.builder()
+                        .command("command", "subcommand2")
+                        .enableOptions("--to")
+                        .exclusiveEnableOptions().build(),
+                words -> completer2);
+
+        // Then
+        assertThat(registry.findCompleters(words("command", "subcommand1", "--to")), containsInAnyOrder(completer1));
+        // And
+        assertThat(registry.findCompleters(words("command", "subcommand2", "--to")), containsInAnyOrder(completer2));
+    }
+
+    @Test
+    void positionParameterIsNotCollapsedWithOptions() {
+        registry.register(
+                CompleterConf.builder()
+                        .enableOptions(Options.NODE_NAME)
+                        .exclusiveEnableOptions().build(),
+                words -> completer1
+        );
+
+        registry.register(
+                CompleterConf.builder()
+                        .command("cluster", "config", "show")
+                        .command("cluster", "config", "update")
+                        .singlePositionalParameter().build(),
+                words -> completer2
+        );
+
+        // Then
+        assertThat(
+                registry.findCompleters(words("cluster", "config", "update", Options.NODE_NAME.fullName(), "")),
+                hasSize(1)
+        );
+        // And
+        assertThat(
+                registry.findCompleters(words("cluster", "config", "update", "")),
+                hasSize(1)
+        );
+    }
 }