// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.java.codeInsight.completion;

import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.StaticallyImportable;
import com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider;
import com.intellij.codeInsight.hints.Option;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.java.codeInsight.AbstractParameterInfoTestCase;
import com.intellij.java.codeInsight.JavaExternalDocumentationTest;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.psi.JavaCodeFragmentFactory;
import com.intellij.psi.PsiExpressionCodeFragment;
import com.intellij.psi.PsiMethod;
import com.intellij.testFramework.AutoPopupParameterInfoTestUtil;
import com.intellij.testFramework.EditorTestUtil;
import com.intellij.testFramework.NeedsIndex;
import com.intellij.testFramework.fixtures.EditorMouseFixture;

import java.util.LinkedHashMap;

@NeedsIndex.SmartMode(reason = "Hints shouldn't work in dumb mode")
public class CompletionHintsTest extends AbstractParameterInfoTestCase {
  private boolean myStoredParamHintsValue;
  private boolean myStoredTabOutValue;

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    CodeInsightSettings settings = CodeInsightSettings.getInstance();
    myStoredParamHintsValue = settings.SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION;
    myStoredTabOutValue = settings.TAB_EXITS_BRACKETS_AND_QUOTES;
    settings.SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION = true;
    settings.TAB_EXITS_BRACKETS_AND_QUOTES = false;
  }

  @Override
  protected void tearDown() throws Exception {
    try {
      CodeInsightSettings settings = CodeInsightSettings.getInstance();
      settings.SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION = myStoredParamHintsValue;
      settings.TAB_EXITS_BRACKETS_AND_QUOTES = myStoredTabOutValue;
    }
    catch (Throwable e) {
      addSuppressedException(e);
    }
    finally {
      super.tearDown();
    }
  }

  public void testBasicScenarioWithHintsDisabledForMethod() {
    disableVirtualComma();

    // check hints appearance on completion
    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");

    // check that hints don't disappear after daemon highlighting passes
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");

    // test Tab/Shift+Tab navigation
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>, <HINT text=\"value:\"/><caret>) } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");

    // test hints remain shown while entering parameter values
    type("\"a");
    next();
    type("\"b");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>\"a\", <HINT text=\"value:\"/>\"b<caret>\") } }");

    // test hints disappearance when caret moves out of parameter list
    right();
    right();
    right();
    right();

    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(\"a\", \"b\") }<caret> }");
  }

  public void testBasicScenarioWithHintsEnabledForMethod() {
    disableVirtualComma();

    // check hints appearance on completion
    configureJava("class C { void m() { Character.for<caret> } }");
    complete("forDigit");
    checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");

    // check that hints don't disappear after daemon highlighting passes
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");

    // test Tab/Shift+Tab navigation
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<Hint text=\"digit:\"/>, <HINT text=\"radix:\"/><caret>) } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");

    // test hints remain shown while entering parameter values
    type("1");
    next();
    type("2");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<Hint text=\"digit:\"/>1, <HINT text=\"radix:\"/>2<caret>) } }");

    // test hints don't disappear when caret moves out of parameter list
    right();
    right();
    right();

    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<hint text=\"digit:\"/>1, <hint text=\"radix:\"/>2) }<caret> }");
  }

  public void testWithHintsEnabledForNonLiterals() {
    disableVirtualComma();

    Option option = JavaInlayParameterHintsProvider.Utils.getInstance().getShowForParamsWithSameType();
    boolean savedValue = option.get();
    try {
      option.set(true);

      configureJava("class C { void m() { Character.for<caret> } }");
      complete("forDigit");
      checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");

      waitForAllAsyncStuff();
      checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");
    }
    finally {
      option.set(savedValue);
    }
  }

  public void testSwitchingOverloads() {
    disableVirtualComma();

    configureJava("class C { void m() { Character.to<caret> } }");
    complete("toChars(int codePoint)");
    checkResultWithInlays("class C { void m() { Character.toChars(<HINT text=\"codePoint:\"/><caret>) } }");
    AutoPopupParameterInfoTestUtil.waitForAutoPopup(myFixture.getProject());
    showParameterInfo();
    methodOverloadDown();
    checkResultWithInlays(
      "class C { void m() { Character.toChars(<HINT text=\"codePoint:\"/><caret>, <Hint text=\"dst:\"/>, <Hint text=\"dstIndex:\"/>) } }");
  }

  public void testSwitchingOverloadsWithParameterFilled() {
    disableVirtualComma();

    configureJava("class C { void m() { Character.to<caret> } }");
    complete("toChars(int codePoint)");
    type("123");
    checkResultWithInlays("class C { void m() { Character.toChars(<HINT text=\"codePoint:\"/>123<caret>) } }");
    AutoPopupParameterInfoTestUtil.waitForAutoPopup(myFixture.getProject());
    showParameterInfo();
    methodOverloadDown();
    checkResultWithInlays("class C { void m() { Character.toChars(<Hint text=\"codePoint:\"/>123, <HINT text=\"dst:\"/><caret>, <Hint text=\"dstIndex:\"/>) } }");
  }

  public void testSwitchingOverloadsForMethodWithDisabledHints() {
    disableVirtualComma();

    configureJava("""
                    class C {
                      int some(int from, int to) { return 0; }
                      int some(int from, int to, int other) { return 0; }
                      void m() { s<caret> }
                    }""");
    complete("some(int from, int to, int other)");
    checkResultWithInlays("""
                            class C {
                              int some(int from, int to) { return 0; }
                              int some(int from, int to, int other) { return 0; }
                              void m() { some(<HINT text="from:"/><caret>, <Hint text="to:"/>, <Hint text="other:"/>) }
                            }""");
    AutoPopupParameterInfoTestUtil.waitForAutoPopup(myFixture.getProject());
    showParameterInfo();
    checkHintContents("""
                        <html><b>int from</b>, int to</html>
                        -
                        [<html><b>int from</b>, int to, int other</html>]""");
    methodOverloadDown();
    waitForAllAsyncStuff();
    checkResultWithInlays("""
                            class C {
                              int some(int from, int to) { return 0; }
                              int some(int from, int to, int other) { return 0; }
                              void m() { some(<HINT text="from:"/><caret>, <Hint text="to:"/>) }
                            }""");
    checkHintContents("""
                        [<html><b>int from</b>, int to</html>]
                        -
                        <html><b>int from</b>, int to, int other</html>""");
  }

  public void testNoHintsForMethodReference() {
    disableVirtualComma();

    configureJava("""
                    class C {
                      interface I { void i(int p); }
                      void referenced(int a) {}
                      void m(I lambda) {}
                      void m2() { m(this::<caret>) }
                    }""");
    complete("referenced");
    checkResultWithInlays("""
                            class C {
                              interface I { void i(int p); }
                              void referenced(int a) {}
                              void m(I lambda) {}
                              void m2() { m(this::referenced<caret>) }
                            }""");
  }

  public void testNestedCompletion() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");
    type("System.getPro");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"def:\"/>), <hint text=\"value:\"/>) } }");
  }

  public void testTabWithNestedCompletion() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    waitForAllAsyncStuff();
    type("System.getPro");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"def:\"/>), <hint text=\"value:\"/>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<Hint text=\"key:\"/>, <HINT text=\"def:\"/><caret>), <hint text=\"value:\"/>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/>System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>)<caret>, <Hint text=\"value:\"/>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>), <HINT text=\"value:\"/><caret>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>), <hint text=\"value:\"/>)<caret> } }");
  }

  public void testNoHintsForMethodWithOneParameterFromBlackList() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key)");
    checkResultWithInlays("class C { void m() { System.getProperty(<caret>) } }");
  }

  public void testHintsDisappearWhenNumberOfParametersIsChangedDirectly() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    checkResultWithInlays("class C { void m() { System.getProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"def:\"/>) } }");
    delete();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<caret> ) } }");
  }

  public void testHintsDisappearWhenNumberOfParametersIsChangedDirectlyWithNoOverloads() {
    disableVirtualComma();

    configureJava("class C { void m() { Character.for<caret> } }");
    complete("forDigit");
    checkResultWithInlays("class C { void m() { Character.forDigit(<HINT text=\"digit:\"/><caret>, <Hint text=\"radix:\"/>) } }");
    delete();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<caret> ) } }");
  }

  public void testCaretIsToTheRightOfHintAfterSmartInnerCompletion() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    type("new String().trim");
    completeSmart();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>new String().trim(), <HINT text=\"value:\"/><caret>) } }");
  }

  public void testNoHintsDuplicationWhenTypingToTheLeftOfHint() {
    disableVirtualComma();

    configureJava("class C { void m() { Character.for<caret> } }");
    complete("forDigit");
    waitForAllAsyncStuff();
    type("1");
    right();
    type("2");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<Hint text=\"digit:\"/>1,<HINT text=\"radix:\"/>2<caret> ) } }");
    myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_LINE_END);
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { Character.forDigit(<hint text=\"digit:\"/>1,<hint text=\"radix:\"/>2 ) } }<caret>");
  }

  public void testPrevParameterFromOutside() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    next();
    next();
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<Hint text=\"key:\"/>, <HINT text=\"def:\"/><caret>) } }");
  }

  public void testPrevParameterFromOutsideWhenParametersAreNotEmpty() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    type("\"a");
    next();
    type("\"b");
    next();
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<Hint text=\"key:\"/>\"a\", <HINT text=\"def:\"/>\"b\"<caret>) } }");
  }

  public void testVararg() {
    disableVirtualComma();

    configureJava("class C { void m() { String.f<caret> } }");
    complete("format(String format, Object... args)");
    checkResultWithInlays("class C { void m() { String.format(<HINT text=\"format:\"/><caret><Hint text=\",args:\"/>) } }");
    type("\"a");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { String.format(<Hint text=\"format:\"/>\"a\", <HINT text=\"args:\"/><caret>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { String.format(<hint text=\"format:\"/>\"a\"<hint text=\",args:\"/>)<caret> } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { String.format(<Hint text=\"format:\"/>\"a\", <HINT text=\"args:\"/><caret>) } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { String.format(<HINT text=\"format:\"/>\"a\"<caret><Hint text=\",args:\"/>) } }");
  }

  public void testVarargWithNoMandatoryArguments() {
    disableVirtualComma();

    configureJava("class C { int vararg(int... args){ return 0; } void m() { varar<caret> } }");
    complete();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(<HINT text=\"args:\"/><caret>) } }");
    checkHintContents("<html><b>int...</b></html>");
    type("1");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(<Hint text=\"args:\"/>1, <caret>) } }");
    checkHintContents("<html><b>int...</b></html>");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(<hint text=\"args:\"/>1)<caret> } }");
    checkHintContents(null);
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(<Hint text=\"args:\"/>1, <caret>) } }");
    checkHintContents("<html><b>int...</b></html>");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int... args){ return 0; } void m() { vararg(<HINT text=\"args:\"/>1<caret>) } }");
    checkHintContents("<html><b>int...</b></html>");
  }

  public void testVarargWithTwoMandatoryArguments() {
    disableVirtualComma();

    configureJava("class C { int vararg(int a, int b, int... args){ return 0; } void m() { varar<caret> } }");
    complete();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<HINT text=\"a:\"/><caret>, <Hint text=\"b:\"/><Hint text=\",args:\"/>) } }");
    type("1");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<Hint text=\"a:\"/>1, <HINT text=\"b:\"/><caret><Hint text=\",args:\"/>) } }");
    type("2");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<Hint text=\"a:\"/>1, <Hint text=\"b:\"/>2, <HINT text=\"args:\"/><caret>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<hint text=\"a:\"/>1, <hint text=\"b:\"/>2<hint text=\",args:\"/>)<caret> } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<Hint text=\"a:\"/>1, <Hint text=\"b:\"/>2, <HINT text=\"args:\"/><caret>) } }");
    prev();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { int vararg(int a, int b, int... args){ return 0; } void m() { vararg(<Hint text=\"a:\"/>1, <HINT text=\"b:\"/>2<caret><Hint text=\",args:\"/>) } }");
  }

  public void testVarargHintsDontSwitchPlaces() {
    disableVirtualComma();

    configureJava("class C { void m() { java.util.Collections.add<caret> } }");
    complete();
    checkResultWithInlays("class C { void m() { java.util.Collections.addAll(<HINT text=\"c:\"/><caret><Hint text=\",elements:\"/>) } }");
    left();
    type('s');
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { java.util.Collections.addAll(<HINT text=\"c:\"/>s<caret><Hint text=\",elements:\"/>) } }");
    backspace();
    checkResultWithInlays("class C { void m() { java.util.Collections.addAll(<HINT text=\"c:\"/><caret><Hint text=\",elements:\"/>) } }");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { java.util.Collections.addAll(<HINT text=\"c:\"/><caret><Hint text=\",elements:\"/>) } }");
  }

  public void testHintsDontDisappearWhenNavigatingAwayFromUncompletedInvocation() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");
    home();
    type(' ');
    waitForAllAsyncStuff();
    checkResultWithInlays(" <caret>class C { void m() { System.setProperty(<hint text=\"key:\"/>, <hint text=\"value:\"/>) } }");
  }

  public void testHintsDontDisappearOnUnfinishedInputForMethodWithOneParameter() {
    disableVirtualComma();

    configureJava("class C { void m() { System.clear<caret> } }");
    complete();
    checkResultWithInlays("class C { void m() { System.clearProperty(<HINT text=\"key:\"/><caret>) } }");
    home();
    waitForAllAsyncStuff();
    checkResultWithInlays("<caret>class C { void m() { System.clearProperty(<hint text=\"key:\"/>) } }");
  }

  public void testSeveralParametersCompletion() {
    disableVirtualComma();

    configureJava("""
                    class P {
                        void method(int a, int b) {}
                    }
                    class C extends P {
                        void method(int a, int b) {
                            super.meth<caret>
                        }
                    }""");
    complete();
    checkResultWithInlays("""
                            class P {
                                void method(int a, int b) {}
                            }
                            class C extends P {
                                void method(int a, int b) {
                                    super.method(<HINT text="a:"/><caret>, <Hint text="b:"/>);
                                }
                            }""");
    complete("a, b");
    waitForAllAsyncStuff();
    checkResultWithInlays("""
                            class P {
                                void method(int a, int b) {}
                            }
                            class C extends P {
                                void method(int a, int b) {
                                    super.method(a, b);<caret>
                                }
                            }""");
  }

  public void testLargeNumberOfParameters() {
    disableVirtualComma();

    configureJava("""
                    class C {
                        void mmm(int a, int b, int c, int d, int e, int f) {}
                        void m2() { mm<caret> }
                    }""");
    complete();
    checkResultWithInlays("""
                            class C {
                                void mmm(int a, int b, int c, int d, int e, int f) {}
                                void m2() { mmm(<HINT text="a:"/><caret>, <Hint text="b:"/>, <Hint text="c:"/>, <Hint text="d:"/>, <Hint text="e:"/>, <Hint text="f:"/>); }
                            }""");
  }

  public void testNestedContextIsNotDisposedOnTabbingOutToOuterContext() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    waitForAllAsyncStuff();
    type("System.getPro");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"def:\"/>), <hint text=\"value:\"/>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<Hint text=\"key:\"/>, <HINT text=\"def:\"/><caret>), <hint text=\"value:\"/>) } }");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/>System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>)<caret>, <Hint text=\"value:\"/>) } }");
    left();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<hint text=\"key:\"/>System.getProperty(<Hint text=\"key:\"/>, <HINT text=\"def:\"/><caret>), <hint text=\"value:\"/>) } }");
  }

  public void testHintPopupContentsForMethodWithOverloads() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"def:\"/>) } }");
    checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<Hint text=\"key:\"/>, <HINT text=\"def:\"/><caret>) } }");
    checkHintContents("<html><b>String</b>&nbsp;&nbsp;<i>a default value.  </i></html>");
    showParameterInfo();
    waitForAllAsyncStuff();
    checkHintContents("""
                        <html><font color=a8a8a8>@NonNls @NotNull String key</font></html>
                        -
                        [<html>@NotNull String key, <b>String def</b></html>]""");
  }

  public void testHintPopupContentsForMethodWithoutOverloads() {
    disableVirtualComma();

    configureJava("class C { void m() { System.setPro<caret> } }");
    complete("setProperty");
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<HINT text=\"key:\"/><caret>, <Hint text=\"value:\"/>) } }");
    checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.setProperty(<Hint text=\"key:\"/>, <HINT text=\"value:\"/><caret>) } }");
    checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the value of the system property.  </i></html>");
    showParameterInfo();
    waitForAllAsyncStuff();
    checkHintContents("<html>@NonNls @NotNull String key, <b>@NonNls @NotNull String value</b></html>");
  }

  public void testUpInEditor() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
    showParameterInfo();
    waitForAllAsyncStuff();
    checkHintContents("""
                        <html><b>@NonNls @NotNull String key</b></html>
                        -
                        [<html><b>@NotNull String key</b>, String def</html>]""");
    up();
    waitForAllAsyncStuff();
    checkResultWithInlays("<caret>class C { void m() { System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>) } }");
    checkHintContents(null);
  }

  public void testDownInEditor() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
    showParameterInfo();
    waitForAllAsyncStuff();
    checkHintContents("""
                        <html><b>@NonNls @NotNull String key</b></html>
                        -
                        [<html><b>@NotNull String key</b>, String def</html>]""");
    myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN);
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { System.getProperty(<hint text=\"key:\"/>, <hint text=\"def:\"/>) } }<caret>");
    checkHintContents(null);
  }

  public void testPopupAfterCaretMovesOutsideOfParenthesis() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    checkHintContents("<html><b>@NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
    left();
    left();
    left();
    waitForAllAsyncStuff();
    checkHintContents(null);
  }

  public void testNoLineUnderPopupText() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key)");
    waitForAllAsyncStuff();
    checkHintContents("<html><b>@NonNls @NotNull String</b>&nbsp;&nbsp;<i>the name of the system property.  </i></html>");
  }

  public void testSwitchIsPossibleForManuallyEnteredUnmatchedMethodCall() {
    disableVirtualComma();

    configureJava("""
                    class C {
                      void a(int p, int q) {}
                      void a(int p, int q, int r) {}
                      void m() { a(<caret>) }
                    }""");
    showParameterInfo();
    checkHintContents("""
                        <html><b>int p</b>, int q</html>
                        -
                        <html><b>int p</b>, int q, int r</html>""");
    methodOverloadDown();
    checkResultWithInlays("""
                            class C {
                              void a(int p, int q) {}
                              void a(int p, int q, int r) {}
                              void m() { a(<HINT text="p:"/><caret>, <Hint text="q:"/>) }
                            }""");
    next();
    waitForAllAsyncStuff();
    checkResultWithInlays("""
                            class C {
                              void a(int p, int q) {}
                              void a(int p, int q, int r) {}
                              void m() { a(<Hint text="p:"/>, <HINT text="q:"/><caret>) }
                            }""");
  }

  public void testShortHintIsShownAfterFullHint() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    waitForAllAsyncStuff();
    showParameterInfo();
    checkHintContents("""
                        <html><b>@NonNls @NotNull String key</b></html>
                        -
                        [<html><b>@NotNull String key</b>, String def</html>]""");
    escape();
    checkHintContents(null);
    right();
    waitForAllAsyncStuff();
    checkHintContents("<html><b>String</b>&nbsp;&nbsp;<i>a default value.  </i></html>");
  }

  public void testAutopopupIsShownWithCompletionHintsDisabled() {
    disableVirtualComma();

    CodeInsightSettings.getInstance().SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION = false;
    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    checkResultWithInlays("class C { void m() { System.getProperty(<caret>) } }");
    waitForAllAsyncStuff();
    checkHintContents("""
                        <html><b>@NonNls @NotNull String key</b></html>
                        -
                        [<html><b>@NotNull String key</b>, String def</html>]""");
  }

  public void testFullPopupIsHiddenOnTyping() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    AutoPopupParameterInfoTestUtil.waitForAutoPopup(myFixture.getProject());
    showParameterInfo();
    type(' ');
    checkHintContents(null);
  }

  public void testFullPopupIsHiddenOnTypingAfterOverloadSwitch() {
    disableVirtualComma();

    configureJava("class C { void m() { System.getProperty(\"a\"<caret>) } }");
    showParameterInfo();
    methodOverloadDown();
    type("\"b");
    checkHintContents(null);
  }

  public void testNextParameterWorksWhenTabCompletionDoesntChangeAnything() {
    disableVirtualComma();

    configureJava("class C { void m() { String local = \"a\"; String local2 = \"b\"; System.getPro<caret> } }");
    complete("getProperty(String key, String def)");
    type("local");
    complete();
    assertEquals("local", myFixture.getLookupElements()[0].getLookupString());
    myFixture.performEditorAction(IdeActions.ACTION_CHOOSE_LOOKUP_ITEM_REPLACE);
    waitForAllAsyncStuff();
    checkResultWithInlays("class C { void m() { String local = \"a\"; String local2 = \"b\"; System.getProperty(<Hint text=\"key:\"/>local, <HINT text=\"def:\"/><caret>) } }");
  }

  public void testGenericType() {
    disableVirtualComma();

    configureJava("class C { void efgh(Class<?> c) {} void m() { efg<caret> } }");
    complete();
    waitForAllAsyncStuff();
    checkHintContents("<html><b>Class&lt;?&gt;</b></html>");
  }

  public void testCompletionBetweenVarargHints() {
    disableVirtualComma();

    configureJava("class C { int myVal = 1; void vararg(int a, int... b) {} void m() { varar<caret> } }");
    complete();
    checkResultWithInlays("class C { int myVal = 1; void vararg(int a, int... b) {} void m() { vararg(<HINT text=\"a:\"/><caret><Hint text=\",b:\"/>); } }");
    type("myVa");
    complete();
