// 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.refactoring.safeDelete;

import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExternalAnnotationsManager;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix;
import com.intellij.codeInsight.generation.GetterSetterPrototypeProvider;
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.ide.util.SuperMethodWarningUtil;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.FindSuperElementsHelper;
import com.intellij.psi.impl.light.LightDefaultConstructor;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.FunctionalExpressionSearch;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.ContractConverter;
import com.intellij.refactoring.changeSignature.ParameterInfoImpl;
import com.intellij.refactoring.changeSignature.inCallers.AbstractJavaMemberCallerChooser;
import com.intellij.refactoring.move.moveClassesOrPackages.ModuleInfoUsageDetector;
import com.intellij.refactoring.safeDelete.usageInfo.*;
import com.intellij.refactoring.util.ConflictsUtil;
import com.intellij.refactoring.util.RefactoringMessageUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.usages.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.ui.tree.TreeUtil;
import com.siyeh.ig.style.LambdaCanBeReplacedWithAnonymousInspection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.openapi.util.NlsContexts.DialogMessage;

public class JavaSafeDeleteProcessor extends SafeDeleteProcessorDelegateBase {
  private static final Logger LOG = Logger.getInstance(JavaSafeDeleteProcessor.class);

  @Override
  public boolean handlesElement(PsiElement element) {
    return element instanceof PsiClass || 
           element instanceof PsiMethod ||
           element instanceof PsiRecordComponent ||
           element instanceof PsiField ||
           element instanceof PsiParameter ||
           element instanceof PsiLocalVariable ||
           element instanceof PsiPackage ||
           element instanceof PsiJavaFile;
  }

  @Override
  public @Nullable NonCodeUsageSearchInfo findUsages(@NotNull PsiElement element,
                                                     PsiElement @NotNull [] allElementsToDelete,
                                                     @NotNull List<? super UsageInfo> usages) {
    Condition<PsiElement> insideDeletedCondition = getUsageInsideDeletedFilter(allElementsToDelete);
    if (element instanceof PsiClass aClass) {
      findClassUsages(aClass, allElementsToDelete, usages);
      if (element instanceof PsiTypeParameter typeParameter) {
        findTypeParameterExternalUsages(typeParameter, usages);
      }
      else {
        appendCallees(aClass, usages);
      }
      ModuleInfoUsageDetector.createSafeDeleteUsageInstance(element.getProject(), allElementsToDelete)
        .detectModuleStatementsUsed(usages, MultiMap.create());
    }
    else if (element instanceof PsiMethod method) {
      insideDeletedCondition = findMethodUsages(method, allElementsToDelete, usages);
    }
    else if (element instanceof PsiField field) {
      insideDeletedCondition = findFieldUsages(field, usages, allElementsToDelete);
    }
    else if (element instanceof PsiRecordComponent component) {
      insideDeletedCondition = findRecordComponentUsages(component, allElementsToDelete, usages);
    }
    else if (element instanceof PsiParameter parameter) {
      LOG.assertTrue(parameter.getDeclarationScope() instanceof PsiMethod);
      findParameterUsages(parameter, allElementsToDelete, usages);
    }
    else if (element instanceof PsiLocalVariable variable) {
      for (PsiReference reference : ReferencesSearch.search(element).asIterable()) {
        PsiReferenceExpression referencedElement = (PsiReferenceExpression)reference.getElement();
        PsiElement statementOrExprInList = PsiTreeUtil.getParentOfType(referencedElement, PsiStatement.class);
        if (statementOrExprInList instanceof PsiExpressionListStatement statement) {
          statementOrExprInList = PsiTreeUtil.findPrevParent(statement.getExpressionList(), referencedElement);
        }

        boolean isSafeToDelete = PsiUtil.isAccessedForWriting(referencedElement);
        boolean hasSideEffects = false;
        if (PsiUtil.isOnAssignmentLeftHand(referencedElement)) {
          PsiExpression rhs = ((PsiAssignmentExpression)referencedElement.getParent()).getRExpression();
          hasSideEffects = RemoveUnusedVariableUtil.checkSideEffects(rhs, variable, new ArrayList<>());
        }
        usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(statementOrExprInList, element, isSafeToDelete && !hasSideEffects));
      }
    }
    return new NonCodeUsageSearchInfo(insideDeletedCondition, element);
  }

  @Override
  public @Nullable Collection<? extends PsiElement> getElementsToSearch(@NotNull PsiElement element,
                                                                        @Nullable Module module,
                                                                        @NotNull Collection<? extends PsiElement> allElementsToDelete) {
    Project project = element.getProject();
    if (element instanceof SyntheticElement && !(element instanceof LightDefaultConstructor)) return null;
    if (element instanceof PsiPackage aPackage && module != null) {
      PsiDirectory[] directories = aPackage.getDirectories(module.getModuleScope());
      if (directories.length == 0) return null;
      return Arrays.asList(directories);
    }
    if (element instanceof PsiJavaFile javaFile) {
      PsiClass[] classes = javaFile.getClasses();
      if (classes.length == 0) return null;
      return List.of(classes);
    }
    if (element instanceof PsiMethod method) {
      if (ApplicationManager.getApplication().isUnitTestMode()) {
        return Collections.singletonList(element);
      }
      if (method.isDefaultConstructor()) {
        return Collections.singletonList(method.getContainingClass());
      }
      PsiMethod[] methods = SuperMethodWarningUtil.checkSuperMethods(method, allElementsToDelete);
      if (methods.length == 0) return null;
      ArrayList<PsiMethod> psiMethods = new ArrayList<>(Arrays.asList(methods));
      psiMethods.add(method);
      return psiMethods;
    }
    if (element instanceof PsiParameter param && param.getDeclarationScope() instanceof PsiMethod method) {
      Set<PsiElement> parametersToDelete = new HashSet<>();
      parametersToDelete.add(element);
      int parameterIndex = method.getParameterList().getParameterIndex(param);
      List<PsiMethod> superMethods = new ArrayList<>(Arrays.asList(method.findDeepestSuperMethods()));
      if (superMethods.isEmpty()) {
        superMethods.add(method);
      }
      if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ReadAction.run(() -> {
        ContainerUtil.addAllNotNull(superMethods, FindSuperElementsHelper.getSiblingInheritedViaSubClass(method));
        for (PsiMethod superMethod : superMethods) {
          parametersToDelete.add(superMethod.getParameterList().getParameters()[parameterIndex]);
          OverridingMethodsSearch.search(superMethod).forEach(overrider -> {
            parametersToDelete.add(overrider.getParameterList().getParameters()[parameterIndex].getNavigationElement());
            return true;
          });
        }
      }), JavaRefactoringBundle.message("progress.title.collect.hierarchy", param.getName()), true, project)) {
        return null;
      }

      if (parametersToDelete.size() > 1 && !ApplicationManager.getApplication().isUnitTestMode()) {
        String message = JavaRefactoringBundle.message("0.is.a.part.of.method.hierarchy.do.you.want.to.delete.multiple.parameters",
                                                       UsageViewUtil.getLongName(method));
        int result = Messages.showYesNoCancelDialog(project, message, SafeDeleteHandler.getRefactoringName(),
                                                    Messages.getQuestionIcon());
        if (result == Messages.CANCEL) return null;
        if (result == Messages.NO) return Collections.singletonList(element);
      }
      return parametersToDelete;
    }
    if (element instanceof PsiTypeParameter typeParameter) {
      PsiTypeParameterListOwner owner = typeParameter.getOwner();
      if (owner instanceof PsiMethod method && !owner.hasModifierProperty(PsiModifier.STATIC)) {
        PsiTypeParameterList typeParameterList = owner.getTypeParameterList();
        if (typeParameterList != null) {
          int index = typeParameterList.getTypeParameterIndex(typeParameter);
          if (index >= 0) {
            ArrayList<PsiTypeParameter> overriders = new ArrayList<>();
            overriders.add(typeParameter);
            OverridingMethodsSearch.search(method).forEach(overrider -> {
              PsiTypeParameter[] typeParameters = overrider.getTypeParameters();
              if (index < typeParameters.length) {
                overriders.add(typeParameters[index]);
              }
              return true;
            });
            if (overriders.size() > 1 && !ApplicationManager.getApplication().isUnitTestMode()) {
              String message =
                JavaRefactoringBundle.message("0.is.a.part.of.method.hierarchy.do.you.want.to.delete.multiple.type.parameters",
                                              UsageViewUtil.getLongName(owner));
              int result = ApplicationManager.getApplication().isUnitTestMode()
                           ? Messages.YES
                           : Messages.showYesNoCancelDialog(project, message, SafeDeleteHandler.getRefactoringName(),
                                                            Messages.getQuestionIcon());
              if (result == Messages.CANCEL) return null;
              if (result == Messages.NO) return Collections.singletonList(element);
            }
            return overriders;
          }
        }
      }
    }
    return Collections.singletonList(element);
  }

  @Override
  public UsageView showUsages(UsageInfo @NotNull [] usages,
                              @NotNull UsageViewPresentation presentation,
                              @NotNull UsageViewManager manager,
                              PsiElement @NotNull [] elements) {
    List<PsiElement> overridingMethods = new ArrayList<>();
    List<UsageInfo> others = new ArrayList<>();
    for (UsageInfo usage : usages) {
      if (usage instanceof SafeDeleteOverridingMethodUsageInfo info) {
        overridingMethods.add(info.getOverridingMethod());
      }
      else {
        others.add(usage);
      }
    }

    UsageTarget[] targets = new UsageTarget[elements.length + overridingMethods.size()];
    for (int i = 0; i < targets.length; i++) {
      targets[i] = new PsiElement2UsageTargetAdapter(i < elements.length ? elements[i] : overridingMethods.get(i - elements.length));
    }
    return manager.showUsages(targets, UsageInfoToUsageConverter.convert(elements, others.toArray(UsageInfo.EMPTY_ARRAY)), presentation);
  }

  @Override
  public Collection<PsiElement> getAdditionalElementsToDelete(@NotNull PsiElement element,
                                                              @NotNull Collection<? extends PsiElement> allElementsToDelete,
                                                              boolean askUser) {
    if (element instanceof PsiRecordComponent component) {
      PsiMethod method = JavaPsiRecordUtil.getAccessorForRecordComponent(component);
      List<PsiElement> additional = new ArrayList<>();
      if (method != null && !(method instanceof SyntheticElement) && !allElementsToDelete.contains(method)) {
        additional.add(method);
      }
      PsiClass recordClass = component.getContainingClass();
      assert recordClass != null;
      PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(recordClass);
      if (constructor == null || constructor instanceof SyntheticElement) return additional; 
      PsiRecordHeader header = recordClass.getRecordHeader();
      assert header != null;
      int index = ArrayUtil.indexOf(header.getRecordComponents(), component);
      if (index < 0) return additional;
      PsiParameter parameter = constructor.getParameterList().getParameter(index);
      if (parameter != null) {
        additional.add(parameter);
      }
      return additional;
    }
    if (element instanceof PsiField field) {
      Project project = element.getProject();
      String propertyName = JavaCodeStyleManager.getInstance(project).variableNameToPropertyName(field.getName(), VariableKind.FIELD);

      PsiClass aClass = field.getContainingClass();
      if (aClass != null) {
        boolean isStatic = field.hasModifierProperty(PsiModifier.STATIC);
        PsiMethod[] getters = GetterSetterPrototypeProvider.findGetters(aClass, propertyName, isStatic);
        if (getters != null) {
          List<PsiMethod> validGetters = new ArrayList<>(1);
          for (PsiMethod getter : getters) {
            if (getter != null && !allElementsToDelete.contains(getter) && getter.isPhysical()) {
              validGetters.add(getter);
            }
          }
          getters = validGetters.isEmpty() ? null : validGetters.toArray(PsiMethod.EMPTY_ARRAY);
        }

        PsiMethod setter = PropertyUtilBase.findPropertySetter(aClass, propertyName, isStatic, false);
        if (setter != null && (allElementsToDelete.contains(setter) || !setter.isPhysical())) {
          setter = null;
        }
        if (askUser && (getters != null || setter != null)) {
          String title = RefactoringBundle.message("delete.title");
          String message =
            RefactoringMessageUtil.getGetterSetterMessage(field.getName(), title, getters != null ? getters[0] : null, setter);
          if (!ApplicationManager.getApplication().isUnitTestMode() &&
              Messages.showYesNoDialog(project, message, RefactoringBundle.message("safe.delete.title"),
                                       Messages.getQuestionIcon()) != Messages.YES) {
            getters = null;
            setter = null;
          }
        }
        List<PsiElement> elements = new ArrayList<>();
        if (setter != null) elements.add(setter);
        if (getters != null) Collections.addAll(elements, getters);
        return elements;
      }
    }
    return null;
  }

  @Override
  public void findConflicts(@NotNull PsiElement element,
                            PsiElement @NotNull [] allElementsToDelete,
                            UsageInfo @NotNull [] usages,
                            @NotNull MultiMap<PsiElement, @DialogMessage String> conflicts) {
    if (element instanceof PsiMethod method) {
      PsiClass containingClass = method.getContainingClass();
      if (containingClass != null && !containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
        for (PsiMethod superMethod : method.findSuperMethods()) {
          if (isInside(superMethod, allElementsToDelete)) continue;
          if (superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
            String message = JavaRefactoringBundle.message("0.implements.1", RefactoringUIUtil.getDescription(element, true),
                                                           RefactoringUIUtil.getDescription(superMethod, true));
            conflicts.putValue(superMethod, StringUtil.capitalize(message));
          }
        }
      }
    }
    else if (element instanceof PsiParameter parameter) {
      PsiElement scope = parameter.getDeclarationScope();
      if (scope instanceof PsiMethod method) {
        for (UsageInfo usage : usages) {
          if (usage.getElement() instanceof PsiMethodReferenceExpression ref && method.equals(ref.resolve())) {
            String message = JavaRefactoringBundle.message("method.reference.will.be.converted.to.lambda.expression.warning");
            conflicts.putValue(ref, StringUtil.capitalize(message));
          }
        }
        collectMethodConflicts(conflicts, method, parameter);
      }
    }
    else if (element instanceof PsiRecordComponent component) {
      PsiClass recordClass = component.getContainingClass();
      assert recordClass != null;
      PsiMethod constructor = JavaPsiRecordUtil.findCanonicalConstructor(recordClass);
      if (constructor != null) {
        PsiRecordHeader header = recordClass.getRecordHeader();
        assert header != null;
        int index = ArrayUtil.indexOf(header.getRecordComponents(), component);
        if (index >= 0) {
          PsiParameter parameter = constructor.getParameterList().getParameter(index);
          if (parameter != null) {
            findConflicts(parameter, allElementsToDelete, UsageInfo.EMPTY_ARRAY, conflicts);
          }
        }
      }
    }
  }

  @Override
  public UsageInfo @Nullable [] preprocessUsages(@NotNull Project project, UsageInfo @NotNull [] usages) {
    List<UsageInfo> result = Collections.synchronizedList(new ArrayList<>());
    List<UsageInfo> overridingMethods = new ArrayList<>();
    List<SafeDeleteParameterCallHierarchyUsageInfo> delegatingParams = new ArrayList<>();
    List<SafeDeleteMemberCalleeUsageInfo> calleesSafeToDelete = new ArrayList<>();
    for (UsageInfo usage : usages) {
      if (usage.isNonCodeUsage) {
        if (usage instanceof SafeDeleteUsageInfo info) {
          PsiElement element = info.getReferencedElement();
          if (element instanceof PsiModifierListOwner owner) {
            ExternalAnnotationsManager annotationsManager = ExternalAnnotationsManager.getInstance(element.getProject());
            List<PsiFile> annotationsFiles = annotationsManager.findExternalAnnotationsFiles(owner);
            if (annotationsFiles != null && annotationsFiles.contains(usage.getFile())) {
              result.add(new SafeDeleteExternalAnnotationsUsageInfo(element, usage.getElement()));
              continue;
            }
          }
        }
        result.add(usage);
      }
      else if (usage instanceof SafeDeleteMemberCalleeUsageInfo info) {
        calleesSafeToDelete.add(info);
      }
      else if (usage instanceof SafeDeleteOverridingMethodUsageInfo) {
        overridingMethods.add(usage);
      }
      else if (usage instanceof SafeDeleteParameterCallHierarchyUsageInfo info) {
        delegatingParams.add(info);
      }
      else if (usage instanceof SafeDeleteAnnotation info) {
        result.add(new SafeDeleteAnnotation((PsiAnnotation)usage.getElement(), info.getReferencedElement(), true));
      }
      else {
        result.add(usage);
      }
    }

    if (!overridingMethods.isEmpty()) {
      if (ApplicationManager.getApplication().isUnitTestMode()) {
        result.addAll(overridingMethods);
      }
      else {
        OverridingMethodsDialog dialog = new OverridingMethodsDialog(project, overridingMethods);
        if (!dialog.showAndGet()) {
          return null;
        }
        ArrayList<UsageInfo> selected = dialog.getSelected();
        Set<UsageInfo> unselected = new HashSet<>(overridingMethods);
        for (UsageInfo usageInfo : selected) {
          unselected.remove(usageInfo);
        }

        if (!unselected.isEmpty()) {
          List<PsiElement> unselectedMethods =
            ContainerUtil.map(unselected, info -> ((SafeDeleteOverridingMethodUsageInfo)info).getOverridingMethod());

          result.removeIf(info -> info instanceof SafeDeleteOverrideAnnotation anno &&
                                  !allSuperMethodsSelectedToDelete(unselectedMethods, anno.getMethod()));
        }

        result.addAll(selected);
      }
    }

    if (!delegatingParams.isEmpty()) {
      SafeDeleteParameterCallHierarchyUsageInfo parameterHierarchyUsageInfo = delegatingParams.get(0);
      if (ApplicationManager.getApplication().isUnitTestMode()) {
        result.addAll(delegatingParams);
      }
      else {
        PsiMethod method = parameterHierarchyUsageInfo.getCalledMethod();
        PsiParameter parameter = parameterHierarchyUsageInfo.getReferencedElement();
        int parameterIndex = method.getParameterList().getParameterIndex(parameter);
        AbstractJavaMemberCallerChooser<?> chooser = new SafeDeleteJavaCallerChooser(method, project, result) {
          @Override
          protected @NotNull List<SafeDeleteParameterCallHierarchyUsageInfo> getTopLevelItems() {
            return delegatingParams;
          }

          @Override
          protected int getParameterIdx() {
            return parameterIndex;
          }

          @Override
          protected PsiParameter getParameterInCaller(PsiMethod called, int paramIdx, PsiMethod caller) {
            return delegatingParams.stream()
              .filter(usage -> caller.equals(usage.getCallerMethod()))
              .map(usage -> usage.getParameterInCaller())
              .findFirst()
              .orElse(super.getParameterInCaller(called, paramIdx, caller));
          }
        };
        TreeUtil.expand(chooser.getTree(), 2);
        if (!chooser.showAndGet()) {
          return null;
        }
      }
    }

    if (!calleesSafeToDelete.isEmpty()) {
      if (ApplicationManager.getApplication().isUnitTestMode()) {
        result.addAll(calleesSafeToDelete);
      }
      else {
        PsiMember member = calleesSafeToDelete.get(0).getCallerMember();
        List<UsageInfo> list = new ArrayList<>();
        SafeDeleteJavaCalleeChooser chooser = new SafeDeleteJavaCalleeChooser(member, project, list) {
          @Override
          protected @NotNull List<SafeDeleteMemberCalleeUsageInfo> getTopLevelItems() {
            return calleesSafeToDelete;
          }
        };
        TreeUtil.expand(chooser.getTree(), 2);
        if (!chooser.showAndGet()) {
          return null;
        }
        result.addAll(list);
        List<PsiElement> methodsToDelete = new ArrayList<>();
        for (UsageInfo info : list) {
          methodsToDelete.add(info.getElement());
        }
        methodsToDelete.add(member);
        Condition<PsiElement> insideDeletedCondition = getUsageInsideDeletedFilter(methodsToDelete.toArray(PsiElement.EMPTY_ARRAY));
        for (UsageInfo info : list) {
          PsiElement psi = info.getElement();
          JavaRefactoringSettings refactoringSettings = JavaRefactoringSettings.getInstance();
          boolean searchNonJava = psi instanceof PsiMethod
                                  ? refactoringSettings.RENAME_SEARCH_FOR_TEXT_FOR_METHOD
                                  : refactoringSettings.RENAME_SEARCH_FOR_TEXT_FOR_FIELD;
          boolean searchInCommentsAndStrings = psi instanceof PsiMethod
                                               ? refactoringSettings.RENAME_SEARCH_IN_COMMENTS_FOR_METHOD
                                               : refactoringSettings.RENAME_SEARCH_IN_COMMENTS_FOR_FIELD;
          SafeDeleteProcessor.addNonCodeUsages(psi, GlobalSearchScope.projectScope(project), result, insideDeletedCondition,
                                               searchNonJava, searchInCommentsAndStrings);
        }
      }
    }

    return result.toArray(UsageInfo.EMPTY_ARRAY);
  }

  private static boolean allSuperMethodsSelectedToDelete(List<PsiElement> unselectedMethods, PsiMethod method) {
    ArrayList<PsiMethod> superMethods = new ArrayList<>(Arrays.asList(method.findSuperMethods()));
    superMethods.retainAll(unselectedMethods);
    return superMethods.isEmpty();
  }

  @Override
  public void prepareForDeletion(@NotNull PsiElement element) {
    if (element instanceof PsiParameter parameter
        && element.getParent() instanceof PsiParameterList parameterList
        && parameterList.getParent() instanceof PsiMethod method) {
      PsiAnnotation contract = JavaMethodContractUtil.findContractAnnotation(method);
      if (contract != null) {
        ParameterInfoImpl[] info = ParameterInfoImpl.fromMethodExceptParameter(method, parameter);
        try {
          String[] names = ContainerUtil.map(parameterList.getParameters(), PsiParameter::getName, ArrayUtilRt.EMPTY_STRING_ARRAY);
          PsiAnnotation newContract = ContractConverter.convertContract(method, names, info);
          if (newContract != null && newContract != contract) {
            contract.replace(newContract);
          }
        }
        catch (ContractConverter.ContractConversionException ignored) {
        }
      }
    }
  }

  @Override
  public boolean isToSearchInComments(PsiElement element) {
    if (element instanceof PsiClass) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS;
    }
    if (element instanceof PsiMethod) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD;
    }
    if (element instanceof PsiVariable) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE;
    }
    if (element instanceof PsiPackage) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_PACKAGE;
    }
    return false;
  }

  @Override
  public void setToSearchInComments(PsiElement element, boolean enabled) {
    if (element instanceof PsiClass) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS = enabled;
    }
    else if (element instanceof PsiMethod) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_METHOD = enabled;
    }
    else if (element instanceof PsiVariable) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_VARIABLE = enabled;
    }
    else if (element instanceof PsiPackage) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_PACKAGE = enabled;
    }
  }

  @Override
  public boolean isToSearchForTextOccurrences(PsiElement element) {
    if (element instanceof PsiClass) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS;
    }
    if (element instanceof PsiMethod) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD;
    }
    if (element instanceof PsiVariable) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE;
    }
    if (element instanceof PsiPackage) {
      return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_PACKAGE;
    }
    return false;
  }

  @Override
  public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) {
    if (element instanceof PsiClass) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS = enabled;
    }
    else if (element instanceof PsiMethod) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_METHOD = enabled;
    }
    else if (element instanceof PsiVariable) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_VARIABLE = enabled;
    }
    else if (element instanceof PsiPackage) {
      JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_PACKAGE = enabled;
    }
  }

  private static Condition<PsiElement> getUsageInsideDeletedFilter(PsiElement[] allElementsToDelete) {
    return usage -> !(usage instanceof PsiFile) && isInside(usage, allElementsToDelete);
  }

  private static void findClassUsages(PsiClass psiClass, PsiElement[] allElementsToDelete, List<? super UsageInfo> usages) {
    String qualifiedName = psiClass.getQualifiedName();
    boolean annotationType = psiClass.isAnnotationType() && qualifiedName != null;

    PsiElement[] topElementsToDelete = Arrays.stream(allElementsToDelete).map(element -> {
      if (element instanceof PsiClass) {
        PsiElement parent = element.getParent();
        if (parent instanceof PsiClassOwner owner) {
          PsiClass[] classes = owner.getClasses();
          if (classes.length == 1 && classes[0] == element) {
            return element.getContainingFile();
          }
        }
      }
      return element;
    }).toArray(PsiElement[]::new);
    ReferencesSearch.search(psiClass).forEach(reference -> {
      PsiElement element = reference.getElement();

      if (!isInside(element, topElementsToDelete)) {
        PsiElement parent = element.getParent();
        JavaSafeDeleteDelegate safeDeleteDelegate = JavaSafeDeleteDelegate.EP.forLanguage(element.getLanguage());
        UsageInfo usageInfo = safeDeleteDelegate != null ? safeDeleteDelegate.createExtendsListUsageInfo(psiClass, reference) : null;
        if (usageInfo != null) {
          usages.add(usageInfo);
          return true;
        }
        LOG.assertTrue(element.getTextRange() != null);
        PsiFile containingFile = psiClass.getContainingFile();
        boolean sameFileWithSingleClass = false;
        if (containingFile instanceof PsiClassOwner owner) {
          PsiClass[] classes = owner.getClasses();
          sameFileWithSingleClass = classes.length == 1 &&
                                    classes[0] == psiClass &&
                                    element.getContainingFile() == containingFile;
        }

        boolean safeDelete = sameFileWithSingleClass || isInNonStaticImport(element);
        if (annotationType && parent instanceof PsiAnnotation annotation) {
          usages.add(new SafeDeleteAnnotation(annotation, psiClass, safeDelete));
        }
        else {
          usages.add(new SafeDeleteReferenceJavaDeleteUsageInfo(element, psiClass, safeDelete));
        }
      }
      return true;
    });
  }

  private static boolean isInNonStaticImport(PsiElement element) {
    return ImportSearcher.getImport(element, true) != null;
  }

  private static void findTypeParameterExternalUsages(PsiTypeParameter typeParameter, List<? super UsageInfo> usages) {
    PsiTypeParameterListOwner owner = typeParameter.getOwner();
    if (owner != null) {
      PsiTypeParameterList parameterList = owner.getTypeParameterList();
      if (parameterList != null) {
        int paramsCount = parameterList.getTypeParameters().length;
        int index = parameterList.getTypeParameterIndex(typeParameter);

        ReferencesSearch.search(owner).forEach(reference -> {
          JavaSafeDeleteDelegate safeDeleteDelegate = JavaSafeDeleteDelegate.EP.forLanguage(reference.getElement().getLanguage());
          if (safeDeleteDelegate != null) {
            safeDeleteDelegate.createJavaTypeParameterUsageInfo(reference, usages, typeParameter, paramsCount, index);
          }
          return true;
        });
      }
    }
  }

  private static @Nullable Condition<PsiElement> findMethodUsages(PsiMethod psiMethod,
                                                                  PsiElement[] allElementsToDelete,
                                                                  @NotNull List<? super UsageInfo> usages) {
    Collection<PsiReference> references = ReferencesSearch.search(psiMethod).findAll();

    if (psiMethod.isConstructor()) {
      return findConstructorUsages(psiMethod, references, usages, allElementsToDelete);
    }
