// 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.changeSignature;

import com.intellij.codeInsight.*;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableUtil;
import com.intellij.codeInsight.generation.surroundWith.SurroundWithUtil;
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.rename.ResolveSnapshotProvider;
import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo;
import com.intellij.refactoring.util.*;
import com.intellij.refactoring.util.usageInfo.DefaultConstructorImplicitUsageInfo;
import com.intellij.refactoring.util.usageInfo.NoConstructorClassUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.siyeh.IntentionPowerPackBundle;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

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

/**
 * @author Maxim.Medvedev
 */
public final class JavaChangeSignatureUsageProcessor implements ChangeSignatureUsageProcessor {
  private static final Logger LOG = Logger.getInstance(JavaChangeSignatureUsageProcessor.class);

  private static boolean isJavaUsage(UsageInfo info) {
    final PsiElement element = info.getElement();
    if (element == null) return false;
    return element.getLanguage() == JavaLanguage.INSTANCE;
  }

  @Override
  public UsageInfo[] findUsages(ChangeInfo info) {
    return info instanceof JavaChangeInfo javaInfo
           ? new JavaChangeSignatureUsageSearcher(javaInfo).findUsages()
           : UsageInfo.EMPTY_ARRAY;
  }

  @Override
  public MultiMap<PsiElement, @DialogMessage String> findConflicts(ChangeInfo info, Ref<UsageInfo[]> refUsages) {
    return info instanceof JavaChangeInfo javaInfo
           ? new ConflictSearcher(javaInfo).findConflicts(refUsages)
           : new MultiMap<>();
  }

  @Override
  public boolean processUsage(ChangeInfo changeInfo, UsageInfo usage, boolean beforeMethodChange, UsageInfo[] usages) {
    if (!isJavaUsage(usage)) return false;
    JavaChangeInfo javaChangeInfo = JavaChangeInfoConverters.getJavaChangeInfo(changeInfo, usage);
    if (javaChangeInfo == null) {
      return false;
    }

    if (beforeMethodChange) {
      if (usage instanceof CallerUsageInfo callerUsageInfo) {
        processCallerMethod(javaChangeInfo, callerUsageInfo.getMethod(), null, callerUsageInfo.isToInsertParameter(),
                            callerUsageInfo.isToInsertException());
        return true;
      }
      else if (usage instanceof OverriderUsageInfo info) {
        final PsiMethod method = info.getOverridingMethod();
        final PsiMethod baseMethod = info.getBaseMethod();
        if (info.isOriginalOverrider()) {
          processPrimaryMethod(javaChangeInfo, method, baseMethod, false);
        }
        else {
          processCallerMethod(javaChangeInfo, method, baseMethod, info.isToInsertArgs(), info.isToCatchExceptions());
        }
        return true;
      }
      else if (usage instanceof DeconstructionUsageInfo info) {
        final PsiDeconstructionPattern deconstruction = info.getDeconstruction();
        final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(usage.getProject());
        var processor = new DeconstructionProcessor(javaChangeInfo, elementFactory, deconstruction.getDeconstructionList());
        processor.run();
        return true;
      }
      else if (usage instanceof MethodReferenceUsageInfo info && MethodReferenceUsageInfo.needToExpand(javaChangeInfo)) {
        final PsiElement element = usage.getElement();
        if (element instanceof PsiMethodReferenceExpression ref) {
          final PsiExpression expression = LambdaRefactoringUtil.convertToMethodCallInLambdaBody(ref);
          if (expression instanceof PsiCallExpression call) {
            info.setCallExpression(call);
            return true;
          }
        }
      }
      else if (usage instanceof FunctionalInterfaceChangedUsageInfo functionalUsage) {
        final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(usage.getProject());
        final PsiElement element = usage.getElement();
        final PsiMethod interfaceMethod = functionalUsage.getMethod();
        if (element instanceof PsiLambdaExpression lambda) {
          MethodParamsProcessor processor =
            new MethodParamsProcessor(javaChangeInfo, interfaceMethod, elementFactory, PsiSubstitutor.EMPTY, lambda.getParameterList(),
                                      lambda.getBody());
          processor.run();
        }
        else if (element instanceof PsiMethodReferenceExpression methodRef) {
          final PsiLambdaExpression lambdaExpression = LambdaRefactoringUtil.convertMethodReferenceToLambda(methodRef, false, true);
          if (lambdaExpression != null) {
            MethodParamsProcessor processor =
              new MethodParamsProcessor(javaChangeInfo, interfaceMethod, elementFactory, PsiSubstitutor.EMPTY, 
                                        lambdaExpression.getParameterList(), lambdaExpression.getBody());
            processor.run();
          }
        }
        return true;
      }

    }
    else {
      PsiElement element = usage.getElement();
      LOG.assertTrue(element != null);

      if (usage instanceof DefaultConstructorImplicitUsageInfo defConstructorUsage) {
        PsiMethod constructor = defConstructorUsage.getConstructor();
        if (!constructor.isPhysical()) {
          final boolean toPropagate = changeInfo instanceof JavaChangeInfoImpl info && info.propagateParametersMethods.remove(constructor);
          final PsiClass containingClass = defConstructorUsage.getContainingClass();
          constructor = (PsiMethod)containingClass.add(constructor);
          PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(containingClass.getModifierList()), true);
          if (toPropagate) {
            ((JavaChangeInfoImpl)changeInfo).propagateParametersMethods.add(constructor);
          }
        }
        addSuperCall(javaChangeInfo, constructor, defConstructorUsage.getBaseConstructor(), usages);
        return true;
      }
      else if (usage instanceof NoConstructorClassUsageInfo info) {
        addDefaultConstructor(javaChangeInfo, info.getPsiClass(), usages);
        return true;
      }
      else if (usage instanceof MethodReferenceUsageInfo info && MethodReferenceUsageInfo.needToExpand(javaChangeInfo)) {
        final MethodCallUsageInfo methodCallInfo = info.createMethodCallInfo();
        if (methodCallInfo != null) {
          processMethodUsage(methodCallInfo.getElement(), javaChangeInfo, methodCallInfo.isToChangeArguments(),
                             methodCallInfo.isToCatchExceptions(), methodCallInfo.isVarArgCall(), 
                             methodCallInfo.getReferencedMethod(), methodCallInfo.getSubstitutor(), usages);
          return true;
        }
      }
      else if (usage instanceof MethodCallUsageInfo methodCallInfo) {
        processMethodUsage(methodCallInfo.getElement(), javaChangeInfo, methodCallInfo.isToChangeArguments(),
                           methodCallInfo.isToCatchExceptions(), methodCallInfo.isVarArgCall(), 
                           methodCallInfo.getReferencedMethod(), methodCallInfo.getSubstitutor(), usages);
        return true;
      }
      else if (usage instanceof ChangeSignatureParameterUsageInfo info) {
        String newName = info.newParameterName;
        String oldName = info.oldParameterName;
        if (element instanceof PsiReferenceExpression ref) {
          processParameterUsage(ref, oldName, newName);
        }
        return true;
      }
      else if (usage instanceof RecordGetterDeclarationUsageInfo info) {
        processRecordGetter((PsiMethod)element, info.myNewName, info.myNewType);
      }
      else if (usage instanceof CallReferenceUsageInfo info) {
        PsiCallReference reference = info.getReference();
        assert reference != null;
        reference.handleChangeSignature(changeInfo);
        return true;
      }
      else if (element instanceof PsiEnumConstant enumConstant) {
        fixActualArgumentsList(enumConstant.getArgumentList(), javaChangeInfo, true, PsiSubstitutor.EMPTY, false);
        return true;
      }
      else if (!(usage instanceof OverriderUsageInfo) && !(usage instanceof UnresolvableCollisionUsageInfo)) {
        PsiReference reference = usage instanceof MoveRenameUsageInfo ? usage.getReference() : element.getReference();
        if (element instanceof PsiImportStaticReferenceElement refElement) {
          refElement.handleElementRename(javaChangeInfo.getNewName());
        } else if (reference != null) {
          reference.bindToElement(javaChangeInfo.getMethod());
        }
      }
    }
    return false;
  }

  private static void processRecordGetter(PsiMethod method, String newName, String type) {
    Project project = method.getProject();
    PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
    if (newName != null && !newName.equals(method.getName())) {
      final PsiIdentifier nameId = method.getNameIdentifier();
      assert nameId != null : method;
      nameId.replace(factory.createIdentifier(newName));
    }
    PsiTypeElement element = method.getReturnTypeElement();
    if (element != null && !element.getType().equalsToText(type)) {
      JavaCodeStyleManager.getInstance(project).shortenClassReferences(element.replace(factory.createTypeElementFromText(type, method)));
    }
  }

  private static void processParameterUsage(PsiReferenceExpression ref, String oldName, String newName) {
    PsiElement last = ref.getReferenceNameElement();
    if (last instanceof PsiIdentifier && last.getText().equals(oldName)) {
      PsiElementFactory factory = JavaPsiFacade.getElementFactory(ref.getProject());
      PsiIdentifier newNameIdentifier = factory.createIdentifier(newName);
      last.replace(newNameIdentifier);
    }
  }

  private static void addDefaultConstructor(JavaChangeInfo changeInfo, PsiClass aClass, UsageInfo[] usages) {
    if (!(aClass instanceof PsiAnonymousClass)) {
      PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());
      PsiMethod defaultConstructor = factory.createMethodFromText(aClass.getName() + "(){}", aClass);
      defaultConstructor = (PsiMethod)CodeStyleManager.getInstance(aClass.getProject()).reformat(defaultConstructor);
      defaultConstructor = (PsiMethod)aClass.add(defaultConstructor);
      PsiUtil.setModifierProperty(defaultConstructor, VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true);
      addSuperCall(changeInfo, defaultConstructor, null, usages);
    }
    else {
      final PsiElement parent = aClass.getParent();
      if (parent instanceof PsiNewExpression expression) {
        final PsiExpressionList argumentList = expression.getArgumentList();
        final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
        final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
        fixActualArgumentsList(argumentList, changeInfo, true, substitutor, false);
      }
    }
  }

  private static void addSuperCall(JavaChangeInfo changeInfo, PsiMethod constructor, PsiMethod callee, UsageInfo[] usages) {
    final PsiElementFactory factory = JavaPsiFacade.getElementFactory(constructor.getProject());
    PsiExpressionStatement superCall = (PsiExpressionStatement)factory.createStatementFromText("super();", constructor);
    PsiCodeBlock body = constructor.getBody();
    assert body != null;
    PsiStatement[] statements = body.getStatements();
    if (statements.length > 0) {
      superCall = (PsiExpressionStatement)body.addBefore(superCall, statements[0]);
    }
    else {
      superCall = (PsiExpressionStatement)body.add(superCall);
    }
    PsiMethodCallExpression callExpression = (PsiMethodCallExpression)superCall.getExpression();
    final PsiClass aClass = constructor.getContainingClass();
    final PsiClass baseClass = changeInfo.getMethod().getContainingClass();
    final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY);
    processMethodUsage(callExpression.getMethodExpression(), changeInfo, 
                       true, false, changeInfo.wasVararg(), callee, substitutor, usages);
  }

  private static void processMethodUsage(PsiElement element,
                                         JavaChangeInfo changeInfo,
                                         boolean toChangeArguments,
                                         boolean toCatchExceptions,
                                         boolean varArgCall, 
                                         PsiMethod callee, 
                                         PsiSubstitutor substitutor,
                                         UsageInfo[] usages) {
    if (changeInfo.isNameChanged()) {
      if (element instanceof PsiJavaCodeReferenceElement ref) {
        PsiElement last = ref.getReferenceNameElement();
        if (last instanceof PsiIdentifier && last.getText().equals(changeInfo.getOldName())) {
          last.replace(changeInfo.getNewNameIdentifier());
        }
      }
    }

    final PsiMethod caller = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
    if (toChangeArguments) {
      final PsiExpressionList list = RefactoringUtil.getArgumentListByMethodReference(element);
      LOG.assertTrue(list != null);
      boolean toInsertDefaultValue = needDefaultValue(changeInfo, caller);
      if (toInsertDefaultValue && element instanceof PsiReferenceExpression ref) {
        final PsiExpression qualifierExpression = ref.getQualifierExpression();
        if (qualifierExpression instanceof PsiSuperExpression && caller != null && callerSignatureIsAboutToChangeToo(caller, usages)) {
          toInsertDefaultValue = false;
        }
      }

      fixActualArgumentsList(list, changeInfo, toInsertDefaultValue, substitutor, varArgCall);
    }

    if (toCatchExceptions) {
      PsiStatement statement;
      if (!(element instanceof PsiReferenceExpression &&
            (statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class)) != null &&
            JavaHighlightUtil.isSuperOrThisCall(statement, true, false))) {
        if (needToCatchExceptions(changeInfo, caller)) {
          PsiClassType[] newExceptions =
            callee != null ? getCalleeChangedExceptionInfo(callee) : getPrimaryChangedExceptionInfo(changeInfo);
          fixExceptions(element, newExceptions);
        }
      }
    }
  }

  private static boolean callerSignatureIsAboutToChangeToo(@NotNull PsiMethod caller, UsageInfo[] usages) {
    for (UsageInfo usage : usages) {
      if (usage instanceof MethodCallUsageInfo info && MethodSignatureUtil.isSuperMethod(info.getReferencedMethod(), caller)) {
        return true;
      }
    }
    return false;
  }

  private static PsiClassType[] getCalleeChangedExceptionInfo(PsiMethod callee) {
    return callee.getThrowsList().getReferencedTypes(); //Callee method's throws list is already modified!
  }

  private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) {
    //methods' throws lists are already modified, may use ExceptionUtil.collectUnhandledExceptions
    newExceptions = filterCheckedExceptions(newExceptions);

    PsiElement context = PsiTreeUtil.getParentOfType(ref, PsiTryStatement.class, PsiMethod.class, PsiLambdaExpression.class);
    if (context instanceof PsiTryStatement tryStatement) {
      PsiCodeBlock tryBlock = tryStatement.getTryBlock();

      //Remove unused catches
      Collection<PsiClassType> classes = ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock);
      PsiParameter[] catchParameters = tryStatement.getCatchBlockParameters();
      for (PsiParameter parameter : catchParameters) {
        final PsiType caughtType = parameter.getType();

        if (!(caughtType instanceof PsiClassType type)) continue;
        if (ExceptionUtil.isUncheckedExceptionOrSuperclass(type)) continue;

        if (!isCatchParameterRedundant(type, classes)) continue;
        parameter.getParent().delete(); //delete catch section
      }

      PsiClassType[] exceptionsToAdd = filterUnhandledExceptions(newExceptions, tryBlock);
      addExceptions(exceptionsToAdd, tryStatement);

      adjustPossibleEmptyTryStatement(tryStatement);
    }
    else {
      newExceptions = filterUnhandledExceptions(newExceptions, ref);
      if (newExceptions.length > 0) {
        //Add new try statement
        PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(ref.getProject());
        PsiTryStatement tryStatement = (PsiTryStatement)elementFactory.createStatementFromText("try {} catch (Exception e) {}", null);
        PsiStatement anchor;
        if (context instanceof PsiLambdaExpression lambda) {
          PsiCodeBlock codeBlock = CommonJavaRefactoringUtil.expandExpressionLambdaToCodeBlock(lambda);
          LOG.assertTrue(codeBlock != null);
          anchor = codeBlock.getStatements()[0];
        }
        else {
          anchor = PsiTreeUtil.getParentOfType(ref, PsiStatement.class);
        }
        LOG.assertTrue(anchor != null);
        PsiElement container = anchor.getParent();
        PsiElement[] elements = SurroundWithUtil.moveDeclarationsOut(container, new PsiElement[]{anchor}, true);
        tryStatement = (PsiTryStatement)container.addAfter(tryStatement, elements[elements.length - 1]);
        PsiCodeBlock tryBlock = tryStatement.getTryBlock();
        LOG.assertTrue(tryBlock != null);
        tryBlock.addRange(elements[0], elements[elements.length - 1]);

        addExceptions(newExceptions, tryStatement);
        container.deleteChildRange(elements[0], elements[elements.length - 1]);
        tryStatement.getCatchSections()[0].delete(); //Delete dummy catch section
      }
    }
  }

  public static boolean hasNewCheckedExceptions(JavaChangeInfo changeInfo) {
    return filterCheckedExceptions(getPrimaryChangedExceptionInfo(changeInfo)).length > 0;
  }

  private static PsiClassType[] filterCheckedExceptions(PsiClassType[] exceptions) {
    List<PsiClassType> result = new ArrayList<>();
    for (PsiClassType exceptionType : exceptions) {
      if (!ExceptionUtil.isUncheckedException(exceptionType)) result.add(exceptionType);
    }
    return result.toArray(PsiClassType.EMPTY_ARRAY);
  }

  private static void adjustPossibleEmptyTryStatement(PsiTryStatement tryStatement) {
    PsiCodeBlock tryBlock = tryStatement.getTryBlock();
    if (tryBlock != null) {
      if (tryStatement.getCatchSections().length == 0 &&
          tryStatement.getFinallyBlock() == null &&
          tryStatement.getResourceList() == null) {
        PsiElement firstBodyElement = tryBlock.getFirstBodyElement();
        if (firstBodyElement != null) {
          tryStatement.getParent().addRangeAfter(firstBodyElement, tryBlock.getLastBodyElement(), tryStatement);
        }
        tryStatement.delete();
      }
    }
  }

  private static void addExceptions(PsiClassType[] exceptionsToAdd, PsiTryStatement tryStatement) {
    for (PsiClassType type : exceptionsToAdd) {
      final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(tryStatement.getProject());
      String name = styleManager.suggestVariableName(VariableKind.PARAMETER, null, null, type).names[0];
      name = styleManager.suggestUniqueVariableName(name, tryStatement, false);

      PsiCatchSection catchSection =
        JavaPsiFacade.getElementFactory(tryStatement.getProject()).createCatchSection(type, name, tryStatement);
      tryStatement.add(catchSection);
    }
  }

  private static PsiClassType[] filterUnhandledExceptions(PsiClassType[] exceptions, PsiElement place) {
    List<PsiClassType> result = new ArrayList<>();
    for (PsiClassType exception : exceptions) {
      if (!ExceptionUtil.isHandled(exception, place)) result.add(exception);
    }
    return result.toArray(PsiClassType.EMPTY_ARRAY);
  }

  private static boolean isCatchParameterRedundant(PsiClassType catchParamType, Collection<? extends PsiClassType> thrownTypes) {
    for (PsiType exceptionType : thrownTypes) {
      if (exceptionType.isConvertibleFrom(catchParamType)) return false;
    }
    return true;
  }

  //This method works equally well for primary usages as well as for propagated callers' usages
  private static void fixActualArgumentsList(PsiExpressionList list,
                                             JavaChangeInfo changeInfo,
                                             boolean toInsertDefaultValue,
                                             PsiSubstitutor substitutor,
                                             boolean varArgCall) {
    final PsiElementFactory factory = JavaPsiFacade.getElementFactory(list.getProject());
    if (changeInfo.isParameterSetOrOrderChanged()) {
      if (changeInfo instanceof JavaChangeInfoImpl javaChangeInfo && javaChangeInfo.isPropagationEnabled) {
        final ParameterInfoImpl[] createdParamInfo = javaChangeInfo.getCreatedParmsInfoWithoutVarargs();
        for (ParameterInfoImpl info : createdParamInfo) {
          PsiExpression newArg;
          if (toInsertDefaultValue) {
            newArg = createDefaultValue(changeInfo, factory, info, list, substitutor);
          }
          else {
            newArg = factory.createExpressionFromText(info.getName(), list);
          }
          if (newArg != null) JavaCodeStyleManager.getInstance(list.getProject()).shortenClassReferences(list.add(newArg));
        }
      }
      else {
        final PsiExpression[] args = list.getExpressions();
        final int nonVarargCount = varArgCall ? getNonVarargCount(changeInfo, args) : args.length;
        final int varargCount = args.length - nonVarargCount;
        if (varargCount<0) return;
        PsiExpression[] newVarargInitializers = null;

        final int newArgsLength;
        final int newNonVarargCount;
        final JavaParameterInfo[] newParameters = changeInfo.getNewParameters();
        if (changeInfo.isArrayToVarargs()) {
          newNonVarargCount = newParameters.length - 1;
          final JavaParameterInfo lastNewParam = newParameters[newParameters.length - 1];
          final PsiExpression arrayToConvert = args[lastNewParam.getOldIndex()];
          if (arrayToConvert instanceof PsiNewExpression expression) {
            final PsiArrayInitializerExpression arrayInitializer = expression.getArrayInitializer();
            if (arrayInitializer != null) {
              newVarargInitializers = arrayInitializer.getInitializers();
            }
          }
          newArgsLength = newVarargInitializers == null ? newParameters.length : newNonVarargCount + newVarargInitializers.length;
        }
        else if (changeInfo.isRetainsVarargs() && varArgCall) {
          newNonVarargCount = newParameters.length - 1;
          newArgsLength = newNonVarargCount + varargCount;
        }
        else if (changeInfo.isObtainsVarags()) {
          newNonVarargCount = newParameters.length - 1;
          newArgsLength = newNonVarargCount;
        }
        else {
          newNonVarargCount = newParameters.length;
          newArgsLength = newParameters.length;
        }

        String[] oldVarargs = null;
        if (varArgCall && changeInfo.wasVararg() && !changeInfo.isRetainsVarargs()) {
          oldVarargs = new String[varargCount];
          for (int i = nonVarargCount; i < args.length; i++) {
            oldVarargs[i - nonVarargCount] = args[i].getText();
          }
        }

        final PsiExpression[] newArgs = new PsiExpression[newArgsLength];
        for (int i = 0; i < newNonVarargCount; i++) {
          if (newParameters[i].getOldIndex() == nonVarargCount && oldVarargs != null) {
            PsiType type = newParameters[i].createType(changeInfo.getMethod(), list.getManager());
            if (type instanceof PsiArrayType) {
              type = substitutor.substitute(type);
              type = TypeConversionUtil.erasure(type);
              String typeText = type.getCanonicalText();
              if (type instanceof PsiEllipsisType) {
                typeText = typeText.replace("...", "[]");
              }
              String text = "new " + typeText + "{" + StringUtil.join(oldVarargs, ",") + "}";
              newArgs[i] = factory.createExpressionFromText(text, changeInfo.getMethod());
              continue;
            }
          }
          newArgs[i] = createActualArgument(changeInfo, list, newParameters[i], toInsertDefaultValue, args, substitutor);
        }
        if (changeInfo.isArrayToVarargs()) {
          if (newVarargInitializers == null) {
            newArgs[newNonVarargCount] =
              createActualArgument(changeInfo, list, newParameters[newNonVarargCount], toInsertDefaultValue, args, substitutor);
          }
          else {
            System.arraycopy(newVarargInitializers, 0, newArgs, newNonVarargCount, newVarargInitializers.length);
          }
        }
        else {
          final int newVarargCount = newArgsLength - newNonVarargCount;
          LOG.assertTrue(newVarargCount == 0 || newVarargCount == varargCount);
          for (int i = newNonVarargCount; i < newArgsLength; i++) {
            final int oldIndex = newParameters[newNonVarargCount].getOldIndex();
            if (oldIndex >= 0 && oldIndex != nonVarargCount) {
              newArgs[i] = createActualArgument(changeInfo, list, newParameters[newNonVarargCount], toInsertDefaultValue, args, substitutor);
            } else {
              System.arraycopy(args, nonVarargCount, newArgs, newNonVarargCount, newVarargCount);
              break;
            }
          }
        }
        ChangeSignatureUtil.synchronizeList(list, Arrays.asList(newArgs), ExpressionList.INSTANCE, changeInfo.toRemoveParm());
      }
    }
  }

  private static int getNonVarargCount(JavaChangeInfo changeInfo, PsiExpression[] args) {
    if (!changeInfo.wasVararg()) return args.length;
    return changeInfo.getOldParameterTypes().length - 1;
  }

  private static @Nullable PsiExpression createActualArgument(JavaChangeInfo changeInfo,
                                                              PsiExpressionList list,
                                                              JavaParameterInfo info,
                                                              boolean toInsertDefaultValue,
                                                              PsiExpression[] args,
                                                              PsiSubstitutor substitutor) {
    final PsiElementFactory factory = JavaPsiFacade.getElementFactory(list.getProject());
    final int index = info.getOldIndex();
    if (index >= 0 && index < args.length) {
      return args[index];
    }
    else {
      if (toInsertDefaultValue) {
        return createDefaultValue(changeInfo, factory, info, list, substitutor);
      }
      else {
        return factory.createExpressionFromText(info.getName(), list);
      }
    }
  }

  private static @Nullable PsiExpression createDefaultValue(JavaChangeInfo changeInfo,
                                                            PsiElementFactory factory,
                                                            JavaParameterInfo info,
                                                            PsiExpressionList list, PsiSubstitutor substitutor) {
    if (info.isUseAnySingleVariable()) {
      final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(list.getProject()).getResolveHelper();
      final PsiType type = info.getTypeWrapper().getType(changeInfo.getMethod(), list.getManager());
      final VariablesProcessor processor = new VariablesProcessor(false) {
        @Override
        protected boolean check(PsiVariable var, ResolveState state) {
          if (var instanceof PsiField field && !resolveHelper.isAccessible(field, list, null)) return false;
          if (var instanceof PsiLocalVariable && list.getTextRange().getStartOffset() <= var.getTextRange().getStartOffset()) return false;
          if (PsiTreeUtil.isAncestor(var, list, false)) return false;
          final PsiType varType = state.get(PsiSubstitutor.KEY).substitute(var.getType());
          return type.isAssignableFrom(varType);
        }

        @Override
        public boolean execute(@NotNull PsiElement pe, @NotNull ResolveState state) {
          super.execute(pe, state);
          return size() < 2;
        }
      };
      PsiScopesUtil.treeWalkUp(processor, list, null);
      if (processor.size() == 1) {
        final PsiVariable result = processor.getResult(0);
        return factory.createExpressionFromText(result.getName(), list);
      }
      if (processor.size() == 0) {
        final PsiClass parentClass = PsiTreeUtil.getParentOfType(list, PsiClass.class);
        if (parentClass != null) {
          PsiClass containingClass = parentClass;
          final Set<PsiClass> containingClasses = new HashSet<>();
          while (containingClass != null) {
            if (type.isAssignableFrom(factory.createType(containingClass, PsiSubstitutor.EMPTY))) {
              containingClasses.add(containingClass);
            }
            containingClass = PsiTreeUtil.getParentOfType(containingClass, PsiClass.class);
          }
          if (containingClasses.size() == 1) {
            PsiClass aClass = containingClasses.contains(parentClass) ? null : containingClasses.iterator().next();
            return RefactoringChangeUtil.createThisExpression(parentClass.getManager(), aClass);
          }
        }
      }
    }
    final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(list, PsiCallExpression.class);
    final String defaultValue = info.getDefaultValue();
    return callExpression != null ? (PsiExpression)info.getActualValue(callExpression, substitutor)
                                  : !StringUtil.isEmpty(defaultValue) ? factory.createExpressionFromText(defaultValue, list) : null;
  }

  @Override
  public boolean processPrimaryMethod(ChangeInfo changeInfo) {
    if (!JavaLanguage.INSTANCE.equals(changeInfo.getLanguage()) || !(changeInfo instanceof JavaChangeInfo javaChangeInfo)) return false;
    PsiMethod method = javaChangeInfo.getMethod();
    if (!JavaLanguage.INSTANCE.equals(method.getLanguage())) {
      return false;
    }
    if (!method.isDefaultConstructor()) {
      if (!(method.isPhysical() || method instanceof LightRecordMethod || method instanceof LightRecordCanonicalConstructor)) {
        return false;
      }
    }
    else {
      PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject());
      PsiClass aClass = method.getContainingClass();
