// 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.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.JavaProjectCodeInsightSettings;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.completion.util.CompletionStyleUtil;
import com.intellij.codeInsight.editorActions.TabOutScopesTracker;
import com.intellij.codeInsight.guess.GuessManager;
import com.intellij.codeInsight.lookup.*;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.modcompletion.ModCompletionItemProvider;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.module.JdkApiCompatibilityService;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.impl.FakePsiElement;
import com.intellij.psi.impl.light.LightVariableBuilder;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.jsp.JspxLanguage;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.util.*;
import com.intellij.psi.util.proximity.ReferenceListWeigher;
import com.intellij.ui.JBColor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PairFunction;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.siyeh.ig.psiutils.SideEffectChecker;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;

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

import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch;
import static com.intellij.psi.util.proximity.ReferenceListWeigher.ReferenceListApplicability.inapplicable;

public final class JavaCompletionUtil {
  public static final Key<Boolean> FORCE_SHOW_SIGNATURE_ATTR = Key.create("forceShowSignature");
  private static final Logger LOG = Logger.getInstance(JavaCompletionUtil.class);
  public static final Key<PairFunction<PsiExpression, CompletionParameters, PsiType>> DYNAMIC_TYPE_EVALUATOR = Key.create("DYNAMIC_TYPE_EVALUATOR");

  private static final Key<PsiType> QUALIFIER_TYPE_ATTR = Key.create("qualifierType"); // SmartPsiElementPointer to PsiType of "qualifier"
  static final NullableLazyKey<ExpectedTypeInfo[], CompletionLocation> EXPECTED_TYPES = NullableLazyKey.create(
    "expectedTypes",
    location -> {
      if (PsiJavaPatterns.psiElement().beforeLeaf(PsiJavaPatterns.psiElement().withText("."))
        .accepts(location.getCompletionParameters().getPosition())) {
        return ExpectedTypeInfo.EMPTY_ARRAY;
      }

      return JavaSmartCompletionContributor.getExpectedTypes(location.getCompletionParameters());
    });

  public static final Key<Boolean> SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS");

  public static @Nullable Set<PsiType> getExpectedTypes(@NotNull BaseCompletionParameters parameters) {
    PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true);
    if (expr != null) {
      Set<PsiType> set = new HashSet<>();
      for (ExpectedTypeInfo expectedInfo : JavaSmartCompletionContributor.getExpectedTypes(parameters)) {
        set.add(expectedInfo.getType());
      }
      return set;
    }
    return null;
  }

  private static final Key<List<SmartPsiElementPointer<PsiMethod>>> ALL_METHODS_ATTRIBUTE = Key.create("allMethods");

  public static PsiType getQualifierType(@NotNull LookupElement item) {
    return item.getUserData(QUALIFIER_TYPE_ATTR);
  }

  public static void completeVariableNameForRefactoring(Project project, Set<LookupElement> set, String prefix, PsiType varType, VariableKind varKind) {
    CamelHumpMatcher camelHumpMatcher = new CamelHumpMatcher(prefix);
    JavaMemberNameCompletionContributor.completeVariableNameForRefactoring(project, set, camelHumpMatcher, varType, varKind, true, false);
  }

  public static void putAllMethods(@NotNull LookupElement item, @NotNull List<? extends PsiMethod> methods) {
    item.putUserData(ALL_METHODS_ATTRIBUTE, ContainerUtil.map(methods, method -> SmartPointerManager.getInstance(method.getProject()).createSmartPsiElementPointer(method)));
  }

  public static @Unmodifiable List<PsiMethod> getAllMethods(@NotNull LookupElement item) {
    List<SmartPsiElementPointer<PsiMethod>> pointers = item.getUserData(ALL_METHODS_ATTRIBUTE);
    if (pointers == null) return null;

    return ContainerUtil.mapNotNull(pointers, SmartPsiElementPointer::getElement);
  }

  public static String @NotNull [] completeVariableNameForRefactoring(@NotNull JavaCodeStyleManager codeStyleManager,
                                                                      @Nullable PsiType varType,
                                                                      @NotNull VariableKind varKind,
                                                                      @NotNull SuggestedNameInfo suggestedNameInfo) {
    return JavaMemberNameCompletionContributor
      .completeVariableNameForRefactoring(codeStyleManager, new CamelHumpMatcher(""), varType, varKind, suggestedNameInfo, true, false);
  }

  public static boolean isInExcludedPackage(@NotNull PsiMember member, boolean allowInstanceInnerClasses) {
    String name = PsiUtil.getMemberQualifiedName(member);
    if (name == null) return false;

    if (!member.hasModifierProperty(PsiModifier.STATIC)) {
      if (member instanceof PsiMethod || member instanceof PsiField) {
        return false;
      }
      if (allowInstanceInnerClasses && member instanceof PsiClass && member.getContainingClass() != null) {
        return false;
      }
    }

    return JavaProjectCodeInsightSettings.getSettings(member.getProject()).isExcluded(name);
  }

  public static @NotNull <T extends PsiType> T originalize(@NotNull T type) {
    if (!type.isValid()) {
      return type;
    }

    T result = new PsiTypeMapper() {
      private final Set<PsiClassType> myVisited = new ReferenceOpenHashSet<>();

      @Override
      public PsiType visitClassType(@NotNull PsiClassType classType) {
        if (!myVisited.add(classType)) return classType;

        PsiClassType.ClassResolveResult classResolveResult = classType.resolveGenerics();
        PsiClass psiClass = classResolveResult.getElement();
        PsiSubstitutor substitutor = classResolveResult.getSubstitutor();
        if (psiClass == null) return classType;

        return new PsiImmediateClassType(CompletionUtil.getOriginalOrSelf(psiClass), originalizeSubstitutor(substitutor));
      }

      private @NotNull PsiSubstitutor originalizeSubstitutor(@NotNull PsiSubstitutor substitutor) {
        PsiSubstitutor originalSubstitutor = PsiSubstitutor.EMPTY;
        for (Map.Entry<PsiTypeParameter, PsiType> entry : substitutor.getSubstitutionMap().entrySet()) {
          PsiType value = entry.getValue();
          originalSubstitutor = originalSubstitutor.put(CompletionUtil.getOriginalOrSelf(entry.getKey()),
                                                        value == null ? null : mapType(value));
        }
        return originalSubstitutor;
      }

      @Override
      public PsiType visitType(@NotNull PsiType type) {
        return type;
      }
    }.mapType(type);
    if (result == null) {
      throw new AssertionError("Null result for type " + type + " of class " + type.getClass());
    }
    return result;
  }

  public static @Nullable List<? extends PsiElement> getAllPsiElements(@NotNull LookupElement item) {
    List<PsiMethod> allMethods = getAllMethods(item);
    if (allMethods != null) return allMethods;
    if (item.getObject() instanceof PsiElement) return Collections.singletonList((PsiElement)item.getObject());
    return null;
  }

  static @Nullable PsiType getLookupElementType(@NotNull LookupElement element) {
    TypedLookupItem typed = element.as(TypedLookupItem.CLASS_CONDITION_KEY);
    return typed != null ? typed.getType() : null;
  }

  static @Nullable PsiType getQualifiedMemberReferenceType(@Nullable PsiType qualifierType, @NotNull PsiMember member) {
    Ref<PsiSubstitutor> subst = Ref.create(PsiSubstitutor.EMPTY);
    class MyProcessor implements PsiScopeProcessor, NameHint, ElementClassHint {
      @Override
      public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
        if (element == member) {
          subst.set(state.get(PsiSubstitutor.KEY));
        }
        return true;
      }

      @Override
      public String getName(@NotNull ResolveState state) {
        return member.getName();
      }

      @Override
      public boolean shouldProcess(@NotNull DeclarationKind kind) {
        return member instanceof PsiEnumConstant ? kind == DeclarationKind.ENUM_CONST :
               member instanceof PsiField ? kind == DeclarationKind.FIELD :
               kind == DeclarationKind.METHOD;
      }

      @Override
      public <T> T getHint(@NotNull Key<T> hintKey) {
        //noinspection unchecked
        return hintKey == NameHint.KEY || hintKey == ElementClassHint.KEY ? (T)this : null;
      }
    }

    PsiScopesUtil.processTypeDeclarations(qualifierType, member, new MyProcessor());

    PsiType rawType = member instanceof PsiField ? ((PsiField)member).getType() :
                      member instanceof PsiMethod ? ((PsiMethod)member).getReturnType() :
                      JavaPsiFacade.getElementFactory(member.getProject()).createType((PsiClass)member);
    return subst.get().substitute(rawType);
  }

  static @NotNull Set<LookupElement> processJavaReference(@NotNull PsiElement element,
                                                          @NotNull PsiJavaCodeReferenceElement javaReference,
                                                          @NotNull ElementFilter elementFilter,
                                                          @NotNull JavaCompletionProcessor.Options options,
                                                          @NotNull Condition<? super String> nameCondition,
                                                          @NotNull CompletionParameters parameters) {
    Set<LookupElement> set = new LinkedHashSet<>();

    JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, options, nameCondition);
    PsiType plainQualifier = processor.getQualifierType();

    List<PsiType> runtimeQualifiers = getQualifierCastTypes(javaReference, parameters);
    if (!runtimeQualifiers.isEmpty()) {
      PsiType[] conjuncts = JBIterable.of(plainQualifier).append(runtimeQualifiers).toArray(PsiType.EMPTY_ARRAY);
      PsiType composite = PsiIntersectionType.createIntersection(false, conjuncts);
      PsiElement ctx = createContextWithXxxVariable(element, composite);
      javaReference = createReference("xxx.xxx", ctx);
      processor.setQualifierType(composite);
    }

    javaReference.processVariants(processor);

    List<PsiTypeLookupItem> castItems = ContainerUtil.map(runtimeQualifiers, q -> PsiTypeLookupItem.createLookupItem(q, element));

    boolean pkgContext = inSomePackage(element);

    PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(plainQualifier);
    boolean honorExcludes = qualifierClass == null || !isInExcludedPackage(qualifierClass, false);

    Set<PsiType> expectedTypes = ObjectUtils.coalesce(getExpectedTypes(parameters), Collections.emptySet());

    Set<PsiMember> mentioned = new HashSet<>();
    JavaLookupElementHighlighter highlighter = getHighlighterForPlace(element, parameters.getOriginalFile().getVirtualFile());
    for (CompletionElement completionElement : processor.getResults()) {
      for (LookupElement item : createLookupElements(completionElement, javaReference)) {
        item.putUserData(QUALIFIER_TYPE_ATTR, plainQualifier);
        Object o = item.getObject();
        if (o instanceof PsiClass) {
          PsiClass specifiedQualifierClass = javaReference.isQualified() ? qualifierClass : ((PsiClass)o).getContainingClass();
          if (!isSourceLevelAccessible(element, (PsiClass)o, pkgContext, specifiedQualifierClass)) {
            continue;
          }
        }
        if (o instanceof PsiMember) {
          if (honorExcludes && isInExcludedPackage((PsiMember)o, true)) {
            continue;
          }
          mentioned.add(CompletionUtil.getOriginalOrSelf((PsiMember)o));
        }
        PsiTypeLookupItem qualifierCast = findQualifierCast(item, castItems, plainQualifier, processor, expectedTypes);
        if (qualifierCast != null) item = castQualifier(item, qualifierCast);
        set.add(highlighter.highlightIfNeeded(qualifierCast != null ? qualifierCast.getType() : plainQualifier, item, o));
      }
    }

    PsiElement refQualifier = javaReference.getQualifier();
    if (!ModCompletionItemProvider.modCommandCompletionEnabled() && 
        refQualifier == null && PsiTreeUtil.getParentOfType(element, PsiPackageStatement.class, PsiImportStatementBase.class) == null) {
      StaticMemberProcessor memberProcessor = new JavaStaticMemberProcessor(parameters);
      memberProcessor.processMembersOfRegisteredClasses(nameCondition, (member, psiClass) -> {
        if (!mentioned.contains(member) && processor.satisfies(member, ResolveState.initial())) {
          ContainerUtil.addIfNotNull(set, memberProcessor.createLookupElement(member, psiClass, true));
        }
      });
    }
    else if (refQualifier instanceof PsiSuperExpression && ((PsiSuperExpression)refQualifier).getQualifier() == null) {
      set.addAll(SuperCalls.suggestQualifyingSuperCalls(element, javaReference, elementFilter, options, nameCondition));
    }

    return set;
  }

  static @NotNull PsiReferenceExpression createReference(@NotNull String text, @NotNull PsiElement context) {
    return (PsiReferenceExpression)JavaPsiFacade.getElementFactory(context.getProject()).createExpressionFromText(text, context);
  }

  private static @NotNull List<PsiType> getQualifierCastTypes(PsiJavaReference javaReference, @NotNull CompletionParameters parameters) {
    if (javaReference instanceof PsiReferenceExpression refExpr) {
      PsiExpression qualifier = refExpr.getQualifierExpression();
      if (qualifier != null) {
        Project project = qualifier.getProject();
        PairFunction<PsiExpression, CompletionParameters, PsiType> evaluator = refExpr.getContainingFile().getCopyableUserData(DYNAMIC_TYPE_EVALUATOR);
        if (evaluator != null) {
          PsiType type = evaluator.fun(qualifier, parameters);
          if (type != null) {
            return Collections.singletonList(type);
          }
        }

        return GuessManager.getInstance(project).getControlFlowExpressionTypeConjuncts(qualifier, parameters.getInvocationCount() > 1);
      }
    }
    return Collections.emptyList();
  }

  public static boolean shouldCast(@Nullable PsiType plainQualifier,
                                   @NotNull JavaCompletionProcessor processor,
                                   @NotNull Set<? extends PsiType> expectedTypes,
                                   PsiType castType,
                                   @NotNull PsiMember member) {
    if (plainQualifier != null) {
      if (member instanceof PsiMethod method &&
          plainQualifier instanceof PsiClassType classQualifier &&
          castType instanceof PsiClassType classCastType) {
        PsiClassType.ClassResolveResult plainResult = classQualifier.resolveGenerics();
        PsiClass plainClass = plainResult.getElement();
        HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
        PsiMethod plainMethod = plainClass == null ? null :
                                StreamEx.ofTree(signature, s -> StreamEx.of(s.getSuperSignatures()))
                                .map(sig -> MethodSignatureUtil.findMethodBySignature(plainClass, sig, true))
                                .filter(Objects::nonNull)
                                .findFirst().orElse(null);
        if (plainMethod != null) {
          PsiClassType.ClassResolveResult castResult = classCastType.resolveGenerics();
          PsiClass castClass = castResult.getElement();

          if (castClass == null || !castClass.isInheritor(plainClass, true)) {
            return false;
          }

          if (!processor.isAccessible(plainMethod)) {
            return true;
          }

          PsiSubstitutor castSub = TypeConversionUtil.getSuperClassSubstitutor(plainClass, classCastType);
          PsiType typeAfterCast = toRaw(castSub.substitute(method.getReturnType()));
          PsiType typeDeclared = toRaw(plainResult.getSubstitutor().substitute(plainMethod.getReturnType()));
          return typeAfterCast != null && typeDeclared != null &&
                 !typeAfterCast.equals(typeDeclared) &&
                 ContainerUtil.exists(expectedTypes, et -> et.isAssignableFrom(typeAfterCast) && !et.isAssignableFrom(typeDeclared));
        }
      }

      return containsMember(castType, member, true) && !containsMember(plainQualifier, member, true);
    }
    return false;
  }

  private static @NotNull LookupElement castQualifier(@NotNull LookupElement item, @NotNull PsiTypeLookupItem castTypeItem) {
    return new LookupElementDecorator<>(item) {
      @Override
      public void handleInsert(@NotNull InsertionContext context) {
        Document document = context.getEditor().getDocument();
        context.commitDocument();
        PsiFile file = context.getFile();
        PsiJavaCodeReferenceElement ref =
          PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiJavaCodeReferenceElement.class, false);
        if (ref != null) {
          PsiElement qualifier = ref.getQualifier();
          if (qualifier != null) {
            CommonCodeStyleSettings settings = CompletionStyleUtil.getCodeStyleSettings(context);

            String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : "";
            document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")");

            String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : "";
            String prefix = "(" + parenSpace + "(" + spaceWithin;
            String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : "";
            int exprStart = qualifier.getTextRange().getStartOffset();
            document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter);

            CompletionUtil.emulateInsertion(context, exprStart + prefix.length(), castTypeItem);
            PsiDocumentManager.getInstance(file.getProject()).doPostponedOperationsAndUnblockDocument(document);
            context.getEditor().getCaretModel().moveToOffset(context.getTailOffset());
          }
        }

        super.handleInsert(context);
      }

      @Override
      public void renderElement(@NotNull LookupElementPresentation presentation) {
        super.renderElement(presentation);

        presentation.appendTailText(" on " + castTypeItem.getType().getPresentableText(), true);
      }
    };
  }

  private static PsiTypeLookupItem findQualifierCast(@NotNull LookupElement item,
                                                     @NotNull List<PsiTypeLookupItem> castTypeItems,
                                                     @Nullable PsiType plainQualifier,
                                                     JavaCompletionProcessor processor,
                                                     Set<? extends PsiType> expectedTypes) {
    PsiMember member = ObjectUtils.tryCast(item.getObject(), PsiMember.class);
    if (member == null) return null;
    return ContainerUtil.find(castTypeItems, c -> {
      PsiType castType = c.getType();
      return shouldCast(plainQualifier, processor, expectedTypes, castType, member);
    });
  }

  private static @Nullable PsiType toRaw(@Nullable PsiType type) {
    return type instanceof PsiClassType ? ((PsiClassType)type).rawType() : type;
  }

  static class JavaLookupElementHighlighter {
    private final @NotNull PsiElement myPlace;
    private final @Nullable VirtualFile myOriginalFile;
    private final @NotNull LanguageLevel myLanguageLevel;
    private final @NotNull Set<PsiField> myConstantsUsedInSwitch;

    JavaLookupElementHighlighter(@NotNull PsiElement place, @Nullable VirtualFile originalFile) {
      myPlace = place;
      myOriginalFile = originalFile;
      myLanguageLevel = PsiUtil.getLanguageLevel(myPlace);
      myConstantsUsedInSwitch = findConstantsUsedInSwitch(myPlace);
    }

    @NotNull
    LookupElement highlightIfNeeded(@Nullable PsiType qualifierType, @NotNull LookupElement item, @NotNull Object object) {
      LookupElement element = generateLookupElementDecorator(qualifierType, object, presentationDecorator ->
        LookupElementDecorator.withRenderer(item, new LookupElementRenderer<>() {
          @Override
          public void renderElement(@NotNull LookupElementDecorator<LookupElement> element, @NotNull LookupElementPresentation presentation) {
            element.getDelegate().renderElement(presentation);
            presentationDecorator.accept(presentation);
          }
        }));
      return element == null ? item : element;
    }

    private @Nullable LookupElement generateLookupElementDecorator(@Nullable PsiType qualifierType, @NotNull Object object,
                                                                   @NotNull Function<Consumer<LookupElementPresentation>, LookupElementDecorator<LookupElement>> generator) {
      if (object instanceof PsiMember) {
        if (JdkApiCompatibilityService.getInstance().firstCompatibleLanguageLevel((PsiMember)object, myLanguageLevel) != null) {
          LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextForeground(JBColor.RED));
          return PrioritizedLookupElement.withExplicitProximity(element, -1);
        }
        if (object instanceof PsiEnumConstant psiEnumConstant &&
            myConstantsUsedInSwitch.contains(CompletionUtil.getOriginalOrSelf(psiEnumConstant))) {
          LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextForeground(JBColor.RED));
          return PrioritizedLookupElement.withExplicitProximity(element, -1);
        }
        if (object instanceof PsiClass psiClass) {
          if (ReferenceListWeigher.INSTANCE.getApplicability(psiClass, myPlace) == inapplicable) {
            LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextForeground(JBColor.RED));
            return PrioritizedLookupElement.withExplicitProximity(element, -1);
          }
          if (PsiUtil.isAvailable(JavaFeature.MODULES, myPlace)) {
            final PsiJavaModule currentModule =
              ReadAction.compute(() -> JavaPsiModuleUtil.findDescriptorByFile(myOriginalFile, myPlace.getProject()));
            if (currentModule != null) {
              final PsiJavaModule targetModule = ReadAction.compute(() -> JavaPsiModuleUtil.findDescriptorByElement(psiClass));
              if (targetModule != null && targetModule != currentModule &&
                  !JavaPsiModuleUtil.reads(currentModule, targetModule)) {
                LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextForeground(JBColor.RED));
                return PrioritizedLookupElement.withExplicitProximity(element, -1);
              }
            }
          }
        }
      }
      if (containsMember(qualifierType, object, false) && !qualifierType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
        LookupElementDecorator<LookupElement> element = generator.apply(presentation -> presentation.setItemTextBold(true));
        return object instanceof PsiField ? element : PrioritizedLookupElement.withExplicitProximity(element, 1);
      }
      return null;
    }
  }

  static @NotNull JavaLookupElementHighlighter getHighlighterForPlace(@NotNull PsiElement place, @Nullable VirtualFile originalFile) {
    return new JavaLookupElementHighlighter(place, originalFile);
  }

  public static @NotNull LookupElement highlightIfNeeded(@Nullable PsiType qualifierType,
                                                         @Nullable VirtualFile originalFile,
                                                         @NotNull LookupElement item,
                                                         @NotNull Object object,
                                                         @NotNull PsiElement place) {
    return getHighlighterForPlace(place, originalFile).highlightIfNeeded(qualifierType, item, object);
  }


  @Contract("null, _, _ -> false")
  private static boolean containsMember(@Nullable PsiType qualifierType, @NotNull Object object, boolean checkBases) {
    if (!(object instanceof PsiMember)) return false;

    if (qualifierType instanceof PsiArrayType) { //length and clone()
      PsiFile file = ((PsiMember)object).getContainingFile();
      //yes, they're a bit dummy
      return file == null || file.getVirtualFile() == null;
    }
    else if (qualifierType instanceof PsiClassType) {
      PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve();
      if (qualifierClass == null) return false;
      if (object instanceof PsiMethod && qualifierClass.findMethodBySignature((PsiMethod)object, checkBases) != null) {
        return true;
      }
      PsiClass memberClass = ((PsiMember)object).getContainingClass();
      return checkBases ? InheritanceUtil.isInheritorOrSelf(qualifierClass, memberClass, true) : qualifierClass.equals(memberClass);
    }
    return false;
  }

  static @Unmodifiable @NotNull Iterable<? extends LookupElement> createLookupElements(@NotNull CompletionElement completionElement, @NotNull PsiJavaReference reference) {
    Object completion = completionElement.getElement();
    assert !(completion instanceof LookupElement);

    if (reference instanceof PsiJavaCodeReferenceElement) {
      if (completion instanceof PsiMethod &&
          ((PsiJavaCodeReferenceElement)reference).getParent() instanceof PsiImportStaticStatement) {
        return Collections.singletonList(JavaLookupElementBuilder.forMethod((PsiMethod)completion, PsiSubstitutor.EMPTY));
      }

      if (completion instanceof PsiClass && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
        List<JavaPsiClassReferenceElement> classItems = JavaClassNameCompletionContributor.createClassLookupItems(
          CompletionUtil.getOriginalOrSelf((PsiClass)completion),
          JavaClassNameCompletionContributor.AFTER_NEW.accepts(reference),
          JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER,
          Conditions.alwaysTrue());
        return JBIterable.from(classItems).flatMap(i -> JavaConstructorCallElement.wrap(i, reference.getElement()));
      }
    }

    PsiSubstitutor substitutor = completionElement.getSubstitutor();
    if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
    if (completion instanceof PsiClass && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
      JavaPsiClassReferenceElement classItem =
        JavaClassNameCompletionContributor.createClassLookupItem((PsiClass)completion, true).setSubstitutor(substitutor);
      return JavaConstructorCallElement.wrap(classItem, reference.getElement());
    }
    if (completion instanceof PsiMethod) {
      if (reference instanceof PsiMethodReferenceExpression && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
        return Collections.singleton((LookupElement)new JavaMethodReferenceElement(
          (PsiMethod)completion, (PsiMethodReferenceExpression)reference, completionElement.getMethodRefType()));
      }

      JavaMethodCallElement item = new JavaMethodCallElement((PsiMethod)completion).setQualifierSubstitutor(substitutor);
      item.setForcedQualifier(completionElement.getQualifierText());
      return Collections.singletonList(item);
    }
    if (completion instanceof PsiVariable && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
      if (completion instanceof PsiEnumConstant enumConstant &&
          PsiTreeUtil.isAncestor(enumConstant.getArgumentList(), reference.getElement(), true)) {
        return Collections.emptyList();
      }
      return Collections.singletonList(new VariableLookupItem((PsiVariable)completion).setSubstitutor(substitutor).qualifyIfNeeded(reference, null));
    }
    if (completion instanceof PsiPackage && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
      return Collections.singletonList(new PackageLookupItem((PsiPackage)completion, reference.getElement()));
    }

    return List.of();
  }

  public static boolean hasAccessibleConstructor(@NotNull PsiType type, @NotNull PsiElement place) {
    if (type instanceof PsiArrayType) return true;

    PsiClass psiClass = PsiUtil.resolveClassInType(type);
    if (psiClass == null || psiClass.isEnum() || psiClass.isAnnotationType()) return false;

    PsiMethod[] methods = psiClass.getConstructors();
    return methods.length == 0 || ContainerUtil.exists(methods, constructor -> isConstructorCompletable(constructor, place));
  }

  private static boolean isConstructorCompletable(@NotNull PsiMethod constructor, @NotNull PsiElement place) {
    if (!(constructor instanceof PsiCompiledElement)) return true; // it's possible to use a quick fix to make accessible after completion
    if (constructor.hasModifierProperty(PsiModifier.PRIVATE)) return false;
    if (constructor.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) return PsiUtil.isAccessible(constructor, place, null);
    return true;
  }

  public static LinkedHashSet<String> getAllLookupStrings(@NotNull PsiMember member) {
    LinkedHashSet<String> allLookupStrings = new LinkedHashSet<>();
    String name = member.getName();
    allLookupStrings.add(name);
    PsiClass containingClass = member.getContainingClass();
    while (containingClass != null) {
      String className = containingClass.getName();
      if (className == null) {
        break;
      }
      name = className + "." + name;
      allLookupStrings.add(name);
      PsiElement parent = containingClass.getParent();
      if (!(parent instanceof PsiClass)) {
        break;
      }
      containingClass = (PsiClass)parent;
    }
    return allLookupStrings;
  }

  public static boolean mayHaveSideEffects(@Nullable PsiElement element) {
    return element instanceof PsiExpression && SideEffectChecker.mayHaveSideEffects((PsiExpression)element);
  }

  public static void insertClassReference(@NotNull PsiClass psiClass, @NotNull PsiFile file, int offset) {
    insertClassReference(psiClass, file, offset, offset);
  }

  public static int insertClassReference(PsiClass psiClass, PsiFile file, int startOffset, int endOffset) {
    Project project = file.getProject();
    PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
    Document document = file.getFileDocument();
    documentManager.commitDocument(document);

    PsiManager manager = file.getManager();

    PsiReference reference = file.findReferenceAt(startOffset);
