// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion;

import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.*;
import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
import com.intellij.codeInsight.lookup.*;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.icons.AllIcons;
import com.intellij.java.JavaBundle;
import com.intellij.java.codeserver.core.JavaPsiEnumUtil;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.lang.LangBundle;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.jvm.types.JvmPrimitiveTypeKind;
import com.intellij.modcompletion.ModCompletionItemProvider;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.options.advanced.AdvancedSettings;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PsiJavaElementPattern;
import com.intellij.patterns.PsiNameValuePairPattern;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.filters.*;
import com.intellij.psi.filters.classes.AnnotationTypeFilter;
import com.intellij.psi.filters.classes.AssignableFromContextFilter;
import com.intellij.psi.filters.classes.NoFinalLibraryClassesFilter;
import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
import com.intellij.psi.filters.element.ModifierFilter;
import com.intellij.psi.filters.getters.ClassLiteralGetter;
import com.intellij.psi.filters.getters.ExpectedTypesGetter;
import com.intellij.psi.filters.getters.JavaMembersGetter;
import com.intellij.psi.filters.types.AssignableFromFilter;
import com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex;
import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex;
import com.intellij.psi.impl.java.stubs.index.JavaSourceModuleNameIndex;
import com.intellij.psi.impl.light.LightJavaModule;
import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.PsiLabelReference;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.ElementClassFilter;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.ui.JBColor;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.JavaDeprecationUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import org.jetbrains.annotations.*;

import java.util.*;
import java.util.function.Function;

import static com.intellij.codeInsight.completion.JavaQualifierAsArgumentContributor.JavaQualifierAsArgumentStaticMembersProcessor;
import static com.intellij.patterns.PsiJavaPatterns.*;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_COMPARABLE;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT;

public final class JavaCompletionContributor extends CompletionContributor implements DumbAware {
  static final ElementPattern<PsiElement> UNEXPECTED_REFERENCE_AFTER_DOT = or(
      // dot at the statement beginning
      psiElement().afterLeaf(".").insideStarting(psiExpressionStatement()),
      //example: class A{ .something }
      psiElement().afterLeaf(".")
        .insideStarting(psiElement(JavaElementType.TYPE))
        .afterLeafSkipping(psiElement().andOr(
                             psiElement().whitespace(),
                             psiElement().withText("")),
                           psiElement().withParent(PsiErrorElement.class))
        .withParent(PsiJavaCodeReferenceElement.class)
        .withSuperParent(2, PsiTypeElement.class)
        .withSuperParent(3, PsiClass.class),
      //example: void test(String p.<caret>)
      psiElement().afterLeaf(".")
        .afterLeafSkipping(psiElement().andOr(
                             psiElement().whitespace(),
                             psiElement().withText("")),
                           psiElement().withParent(PsiErrorElement.class))
        .afterLeafSkipping(psiElement().withParent(PsiErrorElement.class),
                           psiElement().withElementType(JavaTokenType.IDENTIFIER)
                             .withParent(PsiParameter.class)
                             .withSuperParent(2, PsiParameterList.class)),
      // like `call(Cls::methodRef.<caret>`
      psiElement().afterLeaf(psiElement(JavaTokenType.DOT).afterSibling(psiElement(PsiMethodCallExpression.class).withLastChild(
        psiElement(PsiExpressionList.class).withLastChild(psiElement(PsiErrorElement.class))))),
      // dot after primitive type `int.<caret>` or dot after dot `Object..<caret>`
      psiElement().afterLeaf(psiElement(JavaTokenType.DOT).withParent(
        psiElement(PsiErrorElement.class).afterSibling(psiElement(PsiErrorElement.class)))));
  private static final PsiNameValuePairPattern NAME_VALUE_PAIR =
    psiNameValuePair().withSuperParent(2, psiElement(PsiAnnotation.class));
  private static final ElementPattern<PsiElement> ANNOTATION_ATTRIBUTE_NAME =
    or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR),
       psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)),
       psiElement().afterLeaf(",").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
  private static final PsiJavaElementPattern.Capture<PsiElement> IN_TYPE_PARAMETER =
    psiElement().afterLeaf(JavaKeywords.EXTENDS, JavaKeywords.SUPER, "&").withParent(
      psiElement(PsiReferenceList.class).withParent(PsiTypeParameter.class));

  public static final ElementPattern<PsiElement> IN_SWITCH_LABEL =
    psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchBlock.class)));
  private static final ElementPattern<PsiElement> IN_ENUM_SWITCH_LABEL =
    psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2,
      psiElement(PsiSwitchBlock.class).with(new PatternCondition<>("enumExpressionType") {
        @Override
        public boolean accepts(@NotNull PsiSwitchBlock psiSwitchBlock, ProcessingContext context) {
          PsiExpression expression = psiSwitchBlock.getExpression();
          if (expression == null) return false;
          PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
          return aClass != null && aClass.isEnum();
        }
      }))));
  static final PsiJavaElementPattern.Capture<PsiElement> IN_CASE_LABEL_ELEMENT_LIST =
    psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class));

  private static final ElementPattern<PsiElement> AFTER_NUMBER_LITERAL =
    psiElement().afterLeaf(psiElement().withElementType(
      elementType().oneOf(JavaTokenType.DOUBLE_LITERAL, JavaTokenType.LONG_LITERAL, JavaTokenType.FLOAT_LITERAL, JavaTokenType.INTEGER_LITERAL)));
  private static final ElementPattern<PsiElement> IMPORT_REFERENCE =
    psiElement().withParent(psiElement(PsiJavaCodeReferenceElement.class).withParent(PsiImportStatementBase.class));
  private static final ElementPattern<PsiElement> CATCH_OR_FINALLY = psiElement().afterLeaf(
    psiElement().withText("}").withParent(
      psiElement(PsiCodeBlock.class).afterLeaf(JavaKeywords.TRY)));
  private static final ElementPattern<PsiElement> INSIDE_CONSTRUCTOR = psiElement().inside(psiMethod().constructor(true));
  private static final ElementPattern<PsiElement> AFTER_ENUM_CONSTANT =
    psiElement().inside(PsiTypeElement.class).afterLeaf(
      psiElement().inside(true, psiElement(PsiEnumConstant.class), psiElement(PsiClass.class, PsiExpressionList.class)));
  static final ElementPattern<PsiElement> IN_EXTENDS_OR_IMPLEMENTS = psiElement().afterLeaf(
    psiElement()
      .withText(string().oneOf(JavaKeywords.EXTENDS, JavaKeywords.IMPLEMENTS, ",", "&"))
      .withParent(PsiReferenceList.class));
  static final ElementPattern<PsiElement> IN_PERMITS_LIST = psiElement().afterLeaf(
    psiElement()
      .withText(string().oneOf(JavaKeywords.PERMITS, ","))
      .withParent(psiElement(PsiReferenceList.class).withFirstChild(psiElement(PsiKeyword.class).withText(JavaKeywords.PERMITS))));
  private static final ElementPattern<PsiElement> IN_VARIABLE_TYPE = psiElement()
    .withParents(PsiJavaCodeReferenceElement.class, PsiTypeElement.class, PsiDeclarationStatement.class)
    .afterLeaf(psiElement().inside(psiAnnotation()));
  private static final ElementPattern<PsiElement> TOP_LEVEL_VAR_IN_MODULE = psiElement().withSuperParent(3, PsiJavaFile.class)
    .inVirtualFile(virtualFile().withName("module-info.java"));

  /**
   * @param position completion invocation position
   * @return filter for acceptable references; if null then no references are accepted at a given position
   */
  public static @Nullable ElementFilter getReferenceFilter(PsiElement position) {
    PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false,
                                                           PsiCodeBlock.class, PsiMethod.class,
                                                           PsiExpressionList.class, PsiVariable.class, PsiAnnotation.class);
    if (containingClass != null) {
      if (IN_PERMITS_LIST.accepts(position)) {
        return createPermitsListFilter();
      }

      if (IN_EXTENDS_OR_IMPLEMENTS.accepts(position)) {
        AndFilter filter = new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter()),
                                         new ExcludeDeclaredFilter(new ClassFilter(PsiClass.class)));
        PsiElement cls = position.getParent().getParent().getParent();
        if (cls instanceof PsiClass && !(cls instanceof PsiTypeParameter)) {
          filter = new AndFilter(filter, new NoFinalLibraryClassesFilter());
        }
        return filter;
      }
      if (IN_TYPE_PARAMETER.accepts(position)) {
        return new ExcludeDeclaredFilter(new ClassFilter(PsiTypeParameter.class));
      }
    }

    if (getAnnotationNameIfInside(position) != null) {
      return new OrFilter(ElementClassFilter.PACKAGE, new AnnotationTypeFilter());
    }

    if (JavaKeywordCompletion.isDeclarationStart(position) ||
        JavaKeywordCompletion.isInsideParameterList(position) ||
        isInsideAnnotationName(position) ||
        PsiTreeUtil.getParentOfType(position, PsiReferenceParameterList.class, false, PsiAnnotation.class) != null ||
        IN_VARIABLE_TYPE.accepts(position)) {
      return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE);
    }

    if (psiElement().afterLeaf(JavaKeywords.INSTANCEOF).accepts(position) ||
        position.getParent() instanceof PsiJavaCodeReferenceElement ref &&
        ref.getParent() instanceof PsiTypeElement typeElement &&
        (typeElement.getParent() instanceof PsiPatternVariable ||
         typeElement.getParent() instanceof PsiDeconstructionList)) {
      return new ElementFilter() {
        @Override
        public boolean isAcceptable(Object element, @Nullable PsiElement context) {
          return element instanceof PsiClass && !(element instanceof PsiTypeParameter);
        }

        @Override
        public boolean isClassAcceptable(Class<?> hintClass) {
          return PsiClass.class.isAssignableFrom(hintClass) && !PsiTypeParameter.class.isAssignableFrom(hintClass);
        }
      };
    }

    if (JavaKeywordCompletion.VARIABLE_AFTER_FINAL.accepts(position)) {
      return ElementClassFilter.CLASS;
    }

    if (CATCH_OR_FINALLY.accepts(position) ||
        JavaKeywordCompletion.START_SWITCH.accepts(position) ||
        JavaKeywordCompletion.isInstanceofPlace(position) ||
        JavaKeywordCompletion.isAfterPrimitiveOrArrayType(position)) {
      return null;
    }

    if (JavaKeywordCompletion.START_FOR.withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement.class, PsiForStatement.class).accepts(position)) {
      return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.VARIABLE);
    }

    if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
      return ElementClassFilter.CLASS;
    }

    if (psiElement().inside(PsiAnnotationParameterList.class).accepts(position)) {
      return createAnnotationFilter();
    }

    PsiVariable var = PsiTreeUtil.getParentOfType(position, PsiVariable.class, false, PsiClass.class);
    if (var != null && PsiTreeUtil.isAncestor(var.getInitializer(), position, false)) {
      return new ExcludeFilter(var);
    }

    if (IN_CASE_LABEL_ELEMENT_LIST.accepts(position)) {
      return getCaseLabelElementListFilter(position);
    }

    PsiForeachStatement loop = PsiTreeUtil.getParentOfType(position, PsiForeachStatement.class);
    if (loop != null && PsiTreeUtil.isAncestor(loop.getIteratedValue(), position, false)) {
      return new ExcludeFilter(loop.getIterationParameter());
    }

    if (PsiTreeUtil.getParentOfType(position, PsiPackageAccessibilityStatement.class) != null) {
      return applyScopeFilter(ElementClassFilter.PACKAGE, position);
    }

    if (PsiTreeUtil.getParentOfType(position, PsiUsesStatement.class, PsiProvidesStatement.class) != null) {
      ElementFilter filter = new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE);
      if (PsiTreeUtil.getParentOfType(position, PsiReferenceList.class) != null) {
        filter = applyScopeFilter(filter, position);
      }
      return filter;
    }

    if (position.getParent() instanceof PsiReferenceExpression) {
      PsiClass enumClass = JavaPsiEnumUtil.getEnumClassForExpressionInInitializer((PsiReferenceExpression)position.getParent());
      if (enumClass != null) {
        return new EnumStaticFieldsFilter(enumClass);
      }
    }

    return TrueFilter.INSTANCE;
  }

  @Contract(pure = true)
  private static @NotNull ElementFilter getCaseLabelElementListFilter(@NotNull PsiElement position) {
    PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class);
    if (switchBlock == null) return TrueFilter.INSTANCE;

    PsiExpression selector = switchBlock.getExpression();
    if (selector == null || selector.getType() == null) return TrueFilter.INSTANCE;

    PsiType selectorType = selector.getType();

    if (IN_ENUM_SWITCH_LABEL.accepts(position)) {
      ClassFilter enumClassFilter = new ClassFilter(PsiField.class) {
        @Override
        public boolean isAcceptable(Object element, PsiElement context) {
          return element instanceof PsiEnumConstant;
        }
      };
      if (!PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, position)) {
        return enumClassFilter;
      }

      ClassFilter inheritorsEnumFilter = new ClassFilter(PsiClass.class) {
        @Override
        public boolean isAcceptable(Object element, PsiElement context) {
          return element instanceof PsiClass psiClass &&
                 (JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName()) || psiClass.isEnum() || psiClass.isInterface()) &&
                 //it will be covered by enum class, and it looks like noise
                 !JAVA_LANG_COMPARABLE.equals(psiClass.getQualifiedName()) &&
                 TypeConversionUtil.areTypesConvertible(TypeUtils.getType(psiClass), selectorType);
        }
      };

      return new OrFilter(enumClassFilter, inheritorsEnumFilter);
    }

    ClassFilter constantVariablesFilter = new ClassFilter(PsiVariable.class) {
      @Override
      public boolean isAcceptable(Object element, PsiElement context) {
        PsiVariable variable;
        if (element instanceof PsiField field) {
          if (!field.hasModifierProperty(PsiModifier.FINAL) || !field.hasModifierProperty(PsiModifier.STATIC) ||
              !JavaResolveUtil.isAccessible(field, field.getContainingClass(), field.getModifierList(), context, null, null)) {
            return false;
          }

          PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(field.getType());
          if (aClass != null && aClass.isEnum()) {
            return false;
          }
          variable = field;
        }
        else if (element instanceof PsiLocalVariable local) {
          if (!local.hasModifierProperty(PsiModifier.FINAL)) {
            return false;
          }
          variable = local;
        }
        else {
          return false;
        }
        return TypeConversionUtil.isAssignable(selectorType, variable.getType());
      }
    };

    if (!PsiUtil.isAvailable(JavaFeature.PATTERNS_IN_SWITCH, position) ||
        isPrimitive(selectorType) || TypeUtils.isJavaLangString(selectorType)) {
      ClassFilter classFilter = new ClassFilter(PsiClass.class) {
        @Override
        public boolean isAcceptable(Object element, PsiElement context) {
          // Accept only classes with inner classes or with suitable fields
          if (!(element instanceof PsiClass psiClass)) return false;
          for (PsiClass aClass : psiClass.getInnerClasses()) {
            if (JavaResolveUtil.isAccessible(aClass, psiClass, aClass.getModifierList(), context, null, null)) {
              return true;
            }
          }
          return ContainerUtil.exists(psiClass.getAllFields(), field -> constantVariablesFilter.isAcceptable(field, context));
        }
      };
      return new OrFilter(classFilter, constantVariablesFilter);
    }

    ClassFilter enumInheritorsFilter = new ClassFilter(PsiField.class) {
      @Override
      public boolean isAcceptable(Object element, PsiElement context) {
        return element instanceof PsiEnumConstant enumConstant && TypeConversionUtil.areTypesConvertible(enumConstant.getType(), selectorType);
      }
    };

    if (TypeUtils.isJavaLangObject(selectorType)) {
      return new OrFilter(new ClassFilter(PsiClass.class), constantVariablesFilter, enumInheritorsFilter);
    }

    ClassFilter inheritorsFilter = new ClassFilter(PsiClass.class) {
      @Override
      public boolean isAcceptable(Object element, PsiElement context) {
        return element instanceof PsiClass psiClass &&
               TypeConversionUtil.areTypesConvertible(TypeUtils.getType(psiClass), selectorType);
      }
    };

    if (TypeUtils.isJavaLangString(selectorType)) {
      return new OrFilter(constantVariablesFilter, inheritorsFilter);
    }

    return selectorType instanceof PsiClassType
           ? new OrFilter(inheritorsFilter, enumInheritorsFilter)
           : TrueFilter.INSTANCE;
  }

  @Contract(pure = true)
  private static boolean isPrimitive(@NotNull PsiType type) {
    if (type instanceof PsiPrimitiveType) return true;
    if (!(type instanceof PsiClassType)) return false;

    PsiClass aClass = ((PsiClassType)type).resolve();
    Collection<String> boxedPrimitiveTypes = JvmPrimitiveTypeKind.getBoxedFqns();
    return aClass != null && boxedPrimitiveTypes.contains(aClass.getQualifiedName());
  }

  static @NotNull AndFilter createPermitsListFilter() {
    return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter() {
      @Override
      protected boolean checkInheritance(@NotNull PsiClass curClass, @NotNull PsiClass candidate) {
        String qualifiedName = curClass.getQualifiedName();
        return qualifiedName != null && (qualifiedName.equals(candidate.getQualifiedName()) || curClass.isInheritor(candidate, true));
      }
    }));
  }

  private static boolean isInsideAnnotationName(PsiElement position) {
    PsiAnnotation anno = PsiTreeUtil.getParentOfType(position, PsiAnnotation.class, true, PsiMember.class);
    return anno != null && PsiTreeUtil.isAncestor(anno.getNameReferenceElement(), position, true);
  }

  private static ElementFilter createAnnotationFilter() {
    return new OrFilter(
      ElementClassFilter.CLASS,
      ElementClassFilter.PACKAGE,
      new AndFilter(new ClassFilter(PsiField.class), new ModifierFilter(PsiModifier.STATIC, PsiModifier.FINAL)));
  }

  public static ElementFilter applyScopeFilter(ElementFilter filter, PsiElement position) {
    Module module = ModuleUtilCore.findModuleForPsiElement(position);
    return module != null ? new AndFilter(filter, new SearchScopeFilter(module.getModuleScope())) : filter;
  }

  @Override
  public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet _result) {
    PsiElement position = parameters.getPosition();
    if (!isInJavaContext(position)) {
      return;
    }

    if (AFTER_NUMBER_LITERAL.accepts(position) ||
        UNEXPECTED_REFERENCE_AFTER_DOT.accepts(position) ||
        AFTER_ENUM_CONSTANT.accepts(position)) {
      _result.stopHere();
      return;
    }

    boolean smart = parameters.getCompletionType() == CompletionType.SMART;

    CompletionResultSet result = JavaCompletionSorting.addJavaSorting(parameters, _result);
    JavaCompletionSession session = new JavaCompletionSession(result);

    PrefixMatcher matcher = result.getPrefixMatcher();
    PsiElement parent = position.getParent();


    boolean mayCompleteReference = true;

    if (position instanceof PsiIdentifier) {
      addIdentifierVariants(parameters, position, result, session, matcher);
      if (JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN.accepts(position)) {
        session.flushBatchItems();
        result.stopHere();
        return;
      }

      Set<ExpectedTypeInfo> expectedInfos = ContainerUtil.newHashSet(JavaSmartCompletionContributor.getExpectedTypes(parameters));
      boolean shouldAddExpressionVariants = shouldAddExpressionVariants(parameters);

      boolean hasTypeMatchingSuggestions =
        shouldAddExpressionVariants && addExpectedTypeMembers(parameters, false, expectedInfos,
                                                              item -> session.registerBatchItems(Collections.singleton(item)));

      if (!ModCompletionItemProvider.modCommandCompletionEnabled()) {
        if (!smart) {
          PsiAnnotation anno = findAnnotationWhoseAttributeIsCompleted(position);
          if (anno != null) {
            PsiClass annoClass = anno.resolveAnnotationType();
            mayCompleteReference = mayCompleteValueExpression(position, annoClass);
            if (annoClass != null) {
              completeAnnotationAttributeName(result, position, anno, annoClass);
              JavaKeywordCompletion.addPrimitiveTypes(result, position, session);
            }
          }
        }

        PsiReference ref = position.getContainingFile().findReferenceAt(parameters.getOffset());
        if (ref instanceof PsiLabelReference labelRef) {
          session.registerBatchItems(processLabelReference(labelRef));
          result.stopHere();
        }
      }

      List<LookupElement> refSuggestions = Collections.emptyList();
      if (parent instanceof PsiJavaCodeReferenceElement parentRef && mayCompleteReference) {
        if (IN_PERMITS_LIST.accepts(parent) && parameters.getInvocationCount() <= 1 && !parentRef.isQualified()) {
          refSuggestions = completePermitsListReference(parameters, parentRef, matcher);
        }
        else {
          if (parameters.getInvocationCount() >= 2 && Registry.is("java.completion.methods.use.tags")) {
            MethodTags.TagMatcher tagMatcher = new MethodTags.TagMatcher(matcher);
            List<LookupElement> proposedElements = completeReference(parameters, parentRef, session, expectedInfos, tagMatcher::prefixMatches);
            refSuggestions = ContainerUtil.map(proposedElements, element -> {
              LookupElement withTags = MethodTags.wrapLookupWithTags(element, matcher::prefixMatches, matcher.getPrefix(), parameters.getCompletionType());
              return withTags != null ? withTags : element;
            });
          }
          else {
            refSuggestions = completeReference(parameters, parentRef, session, expectedInfos, matcher::prefixMatches);
            if (refSuggestions
              .stream()
              .map(lookupElement -> MethodTags.collectTags(lookupElement, matcher::prefixMatches))
              .anyMatch(t -> t != null && !t.isEmpty())) {
              //it is possible to propose some tags, let's try to do this
              _result.restartCompletionWhenNothingMatches();
            }
          }
        }
        List<LookupElement> filtered = filterReferenceSuggestions(parameters, expectedInfos, refSuggestions);
        hasTypeMatchingSuggestions |= ContainerUtil.exists(filtered, item ->
          ReferenceExpressionCompletionContributor.matchesExpectedType(item, expectedInfos));
        session.registerBatchItems(filtered);
        result.stopHere();
      }

      session.flushBatchItems();

      if (smart) {
        hasTypeMatchingSuggestions |= smartCompleteExpression(parameters, result, expectedInfos);
        smartCompleteNonExpression(parameters, result);
      } else {
        if (!JavaKeywordCompletion.AFTER_DOT.accepts(position)) {
          for (ExpectedTypeInfo info : expectedInfos) {
            ClassLiteralGetter.addCompletions(new JavaSmartCompletionParameters(parameters, info), result, matcher);
          }
        }
      }

      if ((!hasTypeMatchingSuggestions || parameters.getInvocationCount() >= 2) &&
          parent instanceof PsiJavaCodeReferenceElement &&
          !expectedInfos.isEmpty() &&
          JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position)) {
        List<LookupElement> base = ContainerUtil.concat(
          refSuggestions,
          completeReference(parameters, (PsiJavaCodeReferenceElement)parent, session, expectedInfos, s -> !matcher.prefixMatches(s)));
        SlowerTypeConversions.addChainedSuggestions(parameters, result, expectedInfos, base);
      }

      if (smart && parameters.getInvocationCount() > 1 && shouldAddExpressionVariants) {
        addExpectedTypeMembers(parameters, true, expectedInfos, result);
      }
    }

    if (!smart && psiElement().inside(PsiLiteralExpression.class).accepts(position)) {
      Set<String> usedWords = new HashSet<>();
      result.runRemainingContributors(parameters, cr -> {
        usedWords.add(cr.getLookupElement().getLookupString());
        result.passResult(cr);
      });

      PsiReference reference = position.getContainingFile().findReferenceAt(parameters.getOffset());
      if (reference == null || reference.isSoft()) {
        WordCompletionContributor.addWordCompletionVariants(result, parameters, usedWords);
      }
    }

    if (!smart && position instanceof PsiIdentifier) {
      JavaGenerateMemberCompletionContributor.fillCompletionVariants(parameters, result);
    }

    if (!smart && mayCompleteReference) {
      addAllClasses(parameters, result, session);
    }

    if (position instanceof PsiIdentifier) {
      FunctionalExpressionCompletionProvider.addFunctionalVariants(parameters, true, result.getPrefixMatcher(), result);
    }

    if (position instanceof PsiIdentifier &&
        !smart &&
        parent instanceof PsiReferenceExpression &&
        !((PsiReferenceExpression)parent).isQualified() &&
        parameters.isExtendedCompletion() &&
        StringUtil.isNotEmpty(matcher.getPrefix())) {
      new JavaStaticMemberProcessor(parameters).processStaticMethodsGlobally(matcher, result);
    }

    if (!smart && parent instanceof PsiJavaModuleReferenceElement) {
      addModuleReferences(parent, position, parameters.getOriginalFile(), result);
    }

    if (JavaPatternCompletionUtil.insideDeconstructionList(parameters.getPosition())) {
      JavaPatternCompletionUtil.suggestFullDeconstructionList(parameters, result);
    }
  }

  @VisibleForTesting
  public static boolean mayCompleteValueExpression(@NotNull PsiElement position, @Nullable PsiClass annoClass) {
    return psiElement().afterLeaf("(").accepts(position) && annoClass != null && annoClass.findMethodsByName("value", false).length > 0;
  }

  private static List<LookupElement> filterReferenceSuggestions(CompletionParameters parameters,
                                                                Set<ExpectedTypeInfo> expectedInfos,
                                                                List<LookupElement> refSuggestions) {
    if (parameters.getCompletionType() == CompletionType.SMART) {
      return ReferenceExpressionCompletionContributor.smartCompleteReference(refSuggestions, expectedInfos);
    }
    return refSuggestions;
  }

  private static void smartCompleteNonExpression(CompletionParameters parameters, CompletionResultSet result) {
    PsiElement position = parameters.getPosition();
    PsiElement parent = position.getParent();
    if (!SmartCastProvider.inCastContext(parameters) && parent instanceof PsiJavaCodeReferenceElement) {
      JavaSmartCompletionContributor.addClassReferenceSuggestions(parameters, result, position, (PsiJavaCodeReferenceElement)parent);
    }
    if (InstanceofTypeProvider.AFTER_INSTANCEOF.accepts(position)) {
      InstanceofTypeProvider.addCompletions(parameters, result);
    }
    if (ExpectedAnnotationsProvider.ANNOTATION_ATTRIBUTE_VALUE.accepts(position)) {
      ExpectedAnnotationsProvider.addCompletions(position, result);
    }
    if (CatchTypeProvider.CATCH_CLAUSE_TYPE.accepts(position)) {
      CatchTypeProvider.addCompletions(parameters, result);
    }
  }

  private static boolean smartCompleteExpression(CompletionParameters parameters,
                                                 CompletionResultSet result,
                                                 Set<? extends ExpectedTypeInfo> infos) {
    PsiElement position = parameters.getPosition();
    if (SmartCastProvider.inCastContext(parameters) ||
        !JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position) ||
        !(position.getParent() instanceof PsiJavaCodeReferenceElement)) {
      return false;
    }

    boolean[] hadItems = new boolean[1];
    for (ExpectedTypeInfo info : new ObjectOpenCustomHashSet<>(infos, JavaSmartCompletionContributor.EXPECTED_TYPE_INFO_STRATEGY)) {
      BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(parameters, info), lookupElement -> {
        PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
        if (psiType != null && info.getType().isAssignableFrom(psiType)) {
          hadItems[0] = true;
          result.addElement(JavaSmartCompletionContributor.decorate(lookupElement, infos));
        }
      }, result.getPrefixMatcher());
    }
    return hadItems[0];
  }

  public static @Nullable PsiAnnotation findAnnotationWhoseAttributeIsCompleted(@NotNull PsiElement position) {
    return ANNOTATION_ATTRIBUTE_NAME.accepts(position) && !JavaKeywordCompletion.isAfterPrimitiveOrArrayType(position)
           ? Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiAnnotation.class))
           : null;
  }

  private static void addIdentifierVariants(@NotNull CompletionParameters parameters,
                                            PsiElement position,
                                            CompletionResultSet result,
                                            JavaCompletionSession session, PrefixMatcher matcher) {
