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

import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.java.refactoring.JavaRefactoringBundle;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.ConflictsDialogBase;
import com.intellij.refactoring.inheritanceToDelegation.usageInfo.*;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.ConflictsUtil;
import com.intellij.refactoring.util.RefactoringUIUtil;
import com.intellij.refactoring.util.classMembers.ClassMemberReferencesVisitor;
import com.intellij.refactoring.util.classRefs.ClassInstanceScanner;
import com.intellij.refactoring.util.classRefs.ClassReferenceScanner;
import com.intellij.refactoring.util.classRefs.ClassReferenceSearchingScanner;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usages.UsageInfoToUsageConverter;
import com.intellij.usages.UsageTarget;
import com.intellij.usages.UsageViewManager;
import com.intellij.usages.UsageViewPresentation;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.psiutils.SealedUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public final class InheritanceToDelegationProcessor extends BaseRefactoringProcessor {
  private static final Logger LOG = Logger.getInstance(InheritanceToDelegationProcessor.class);
  private final PsiClass myClass;
  private final String myInnerClassName;
  private final boolean myIsDelegateOtherMembers;
  private final Set<PsiClass> myDelegatedInterfaces;
  private final Set<PsiMethod> myDelegatedMethods;
  private final HashMap<PsiMethod,String> myDelegatedMethodsVisibility;
  private final Set<PsiMethod> myOverriddenMethods;

  private final PsiClass myBaseClass;
  private final Set<PsiMember> myBaseClassMembers;
  private final String myFieldName;
  private final String myGetterName;
  private final boolean myGenerateGetter;
  private final Set<PsiClass> myBaseClassBases;
  private Set<PsiClass> myClassImplementedInterfaces;
  private final PsiElementFactory myFactory;
  private final PsiClassType myBaseClassType;
  private final PsiManager myManager;
  private final boolean myIsInnerClassNeeded;
  private Set<PsiClass> myClassInheritors;
  private HashSet<PsiMethod> myAbstractDelegatedMethods;
  private final Map<PsiClass, PsiSubstitutor> mySuperClassesToSubstitutors = new HashMap<>();


  public InheritanceToDelegationProcessor(Project project,
                                          PsiClass aClass,
                                          @NotNull PsiClass targetBaseClass,
                                          String fieldName,
                                          String innerClassName,
                                          PsiClass[] delegatedInterfaces,
                                          PsiMethod[] delegatedMethods,
                                          boolean delegateOtherMembers,
                                          boolean generateGetter) {
    super(project);

    myClass = aClass;
    myInnerClassName = innerClassName;
    myIsDelegateOtherMembers = delegateOtherMembers;
    myManager = myClass.getManager();
    myFactory = JavaPsiFacade.getElementFactory(myManager.getProject());

    myBaseClass = targetBaseClass;
    LOG.assertTrue(
             // && !myBaseClass.isInterface()
            myBaseClass.getQualifiedName() == null || !myBaseClass.getQualifiedName().equals(CommonClassNames.JAVA_LANG_OBJECT), myBaseClass);
    myBaseClassMembers = getAllBaseClassMembers();
    myBaseClassBases = getAllBases();
    myBaseClassType = myFactory.createType(myBaseClass, getSuperSubstitutor (myBaseClass));

    myIsInnerClassNeeded = InheritanceToDelegationUtil.isInnerClassNeeded(myClass, myBaseClass);


    myFieldName = fieldName;
    final String propertyName = JavaCodeStyleManager.getInstance(myProject).variableNameToPropertyName(myFieldName, VariableKind.FIELD);
    myGetterName = GenerateMembersUtil.suggestGetterName(propertyName, myBaseClassType, myProject);
    myGenerateGetter = generateGetter;

    myDelegatedInterfaces = new LinkedHashSet<>();
    Collections.addAll(myDelegatedInterfaces, delegatedInterfaces);
    myDelegatedMethods = new LinkedHashSet<>();
    Collections.addAll(myDelegatedMethods, delegatedMethods);
    myDelegatedMethodsVisibility = new HashMap<>();
    for (PsiMethod method : myDelegatedMethods) {
      MethodSignature signature = method.getSignature(getSuperSubstitutor(method.getContainingClass()));
      PsiMethod overridingMethod = MethodSignatureUtil.findMethodBySignature(myClass, signature, false);
      if (overridingMethod != null) {
        myDelegatedMethodsVisibility.put(method,
                                         VisibilityUtil.getVisibilityModifier(overridingMethod.getModifierList()));
      }
    }

    myOverriddenMethods = getOverriddenMethods();
  }

  private PsiSubstitutor getSuperSubstitutor(final PsiClass superClass) {
    PsiSubstitutor result = mySuperClassesToSubstitutors.get(superClass);
    if (result == null) {
      result = TypeConversionUtil.getSuperClassSubstitutor(superClass, myClass, PsiSubstitutor.EMPTY);
      mySuperClassesToSubstitutors.put(superClass, result);
    }
    return result;
  }

  @Override
  protected @NotNull UsageViewDescriptor createUsageViewDescriptor(UsageInfo @NotNull [] usages) {
    return new InheritanceToDelegationViewDescriptor(myClass);
  }

  @Override
  protected UsageInfo @NotNull [] findUsages() {
    ArrayList<UsageInfo> usages = new ArrayList<>();
    final PsiClass[] inheritors = ClassInheritorsSearch.search(myClass).toArray(PsiClass.EMPTY_ARRAY);
    myClassInheritors = new HashSet<>();
    myClassInheritors.add(myClass);
    Collections.addAll(myClassInheritors, inheritors);

    {
      ClassReferenceScanner scanner = new ClassReferenceSearchingScanner(myClass);
      final MyClassInstanceReferenceVisitor instanceReferenceVisitor = new MyClassInstanceReferenceVisitor(myClass, usages);
      scanner.processReferences(new ClassInstanceScanner(myClass, instanceReferenceVisitor));

      MyClassMemberReferencesVisitor visitor = new MyClassMemberReferencesVisitor(usages, instanceReferenceVisitor);
      myClass.accept(visitor);

      myClassImplementedInterfaces = instanceReferenceVisitor.getImplementedInterfaces();
    }
    for (PsiClass inheritor : inheritors) {
      processClass(inheritor, usages);
    }

    return usages.toArray(UsageInfo.EMPTY_ARRAY);
  }

  private FieldAccessibility getFieldAccessibility(PsiElement element) {
    for (PsiClass aClass : myClassInheritors) {
      if (PsiTreeUtil.isAncestor(aClass, element, false)) {
        return new FieldAccessibility(true, aClass);
      }
    }
    return FieldAccessibility.INVISIBLE;
  }

  @Override
  protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
    final UsageInfo[] usagesIn = refUsages.get();
    ArrayList<UsageInfo> oldUsages = new ArrayList<>();
    Collections.addAll(oldUsages, usagesIn);
    final ObjectUpcastedUsageInfo[] objectUpcastedUsageInfos = objectUpcastedUsages(usagesIn);
    if (myPrepareSuccessfulSwingThreadCallback != null) {
      MultiMap<PsiElement, String> conflicts = new MultiMap<>();
      if (objectUpcastedUsageInfos.length > 0) {
        final String message = JavaRefactoringBundle.message("instances.of.0.upcasted.to.1.were.found",
                                                         RefactoringUIUtil.getDescription(myClass, true), CommonRefactoringUtil.htmlEmphasize(
          CommonClassNames.JAVA_LANG_OBJECT));

        conflicts.putValue(myClass, message);
      }

      analyzeConflicts(usagesIn, conflicts);
      if (!conflicts.isEmpty()) {
        ConflictsDialogBase conflictsDialog = prepareConflictsDialog(conflicts, usagesIn);
        if (!conflictsDialog.showAndGet()) {
          if (conflictsDialog.isShowConflicts()) prepareSuccessful();
          return false;
        }
      }

      if (objectUpcastedUsageInfos.length > 0) {
        showObjectUpcastedUsageView(objectUpcastedUsageInfos);
        setPreviewUsages(true);
      }
    }
    ArrayList<UsageInfo> filteredUsages = filterUsages(oldUsages);
    refUsages.set(filteredUsages.toArray(UsageInfo.EMPTY_ARRAY));
    prepareSuccessful();
    return true;
  }

  private void analyzeConflicts(UsageInfo[] usage, MultiMap<PsiElement, String> conflicts) {
    HashMap<PsiElement,HashSet<PsiElement>> reportedNonDelegatedUsages = new HashMap<>();
    HashMap<PsiClass,HashSet<PsiElement>> reportedUpcasts = new HashMap<>();
    final String classDescription = RefactoringUIUtil.getDescription(myClass, false);

    for (UsageInfo aUsage : usage) {
      final PsiElement element = aUsage.getElement();
      if (aUsage instanceof InheritanceToDelegationUsageInfo usageInfo) {
        /*if (usageInfo instanceof ObjectUpcastedUsageInfo) {
         PsiElement container = ConflictsUtil.getContainer(usageInfo.element);
         if (!reportedObjectUpcasts.contains(container)) {
           String message = "An instance of " + classDescription + " is upcasted to "
                   + nameJavaLangObject + " in " + ConflictsUtil.getDescription(container, true) + ".";
           conflicts.add(message);
           reportedObjectUpcasts.add(container);
         }
       } else*/
        if (!myIsDelegateOtherMembers && !usageInfo.getDelegateFieldAccessible().isAccessible()) {
          if (usageInfo instanceof NonDelegatedMemberUsageInfo) {
            final PsiElement nonDelegatedMember = ((NonDelegatedMemberUsageInfo)usageInfo).nonDelegatedMember;
            HashSet<PsiElement> reportedContainers = reportedNonDelegatedUsages.get(nonDelegatedMember);
            if (reportedContainers == null) {
              reportedContainers = new HashSet<>();
              reportedNonDelegatedUsages.put(nonDelegatedMember, reportedContainers);
            }
            final PsiElement container = ConflictsUtil.getContainer(element);
            if (!reportedContainers.contains(container)) {
              String message = JavaRefactoringBundle.message("0.uses.1.of.an.instance.of.a.2", RefactoringUIUtil.getDescription(container, true),
                                                         RefactoringUIUtil.getDescription(nonDelegatedMember, true), classDescription);
              conflicts.putValue(container, StringUtil.capitalize(message));
              reportedContainers.add(container);
            }
          }
          else if (usageInfo instanceof UpcastedUsageInfo) {
            final PsiClass upcastedTo = ((UpcastedUsageInfo)usageInfo).upcastedTo;
            HashSet<PsiElement> reportedContainers = reportedUpcasts.get(upcastedTo);
            if (reportedContainers == null) {
              reportedContainers = new HashSet<>();
              reportedUpcasts.put(upcastedTo, reportedContainers);
            }
            final PsiElement container = ConflictsUtil.getContainer(element);
            if (!reportedContainers.contains(container)) {
              String message = JavaRefactoringBundle.message("0.upcasts.an.instance.of.1.to.2",
                                                         RefactoringUIUtil.getDescription(container, true), classDescription,
                                                         RefactoringUIUtil.getDescription(upcastedTo, false));
              conflicts.putValue(container, StringUtil.capitalize(message));
              reportedContainers.add(container);
            }
          }
        }
      }
      else if (aUsage instanceof NoLongerOverridingSubClassMethodUsageInfo info) {
        String message = JavaRefactoringBundle.message("0.will.no.longer.override.1",
                                                   RefactoringUIUtil.getDescription(info.getSubClassMethod(), true),
                                                   RefactoringUIUtil.getDescription(info.getOverridenMethod(), true));
        conflicts.putValue(info.getSubClassMethod(), message);
      }
    }
  }

  private static ObjectUpcastedUsageInfo[] objectUpcastedUsages(UsageInfo[] usages) {
    ArrayList<ObjectUpcastedUsageInfo> result = new ArrayList<>();
    for (UsageInfo usage : usages) {
      if (usage instanceof ObjectUpcastedUsageInfo) {
        result.add(((ObjectUpcastedUsageInfo)usage));
      }
    }
    return result.toArray(new ObjectUpcastedUsageInfo[0]);
  }

  private ArrayList<UsageInfo> filterUsages(ArrayList<? extends UsageInfo> usages) {
    ArrayList<UsageInfo> result = new ArrayList<>();

    for (UsageInfo usageInfo : usages) {
      if (!(usageInfo instanceof InheritanceToDelegationUsageInfo)) {
        continue;
      }
      if (usageInfo instanceof ObjectUpcastedUsageInfo) {
        continue;
      }

      if (!myIsDelegateOtherMembers) {
        final FieldAccessibility delegateFieldAccessible = ((InheritanceToDelegationUsageInfo)usageInfo).getDelegateFieldAccessible();
        if (!delegateFieldAccessible.isAccessible()) continue;
      }

      result.add(usageInfo);
    }
    return result;
  }

  private void processClass(PsiClass inheritor, ArrayList<? super UsageInfo> usages) {
    ClassReferenceScanner scanner = new ClassReferenceSearchingScanner(inheritor);
    final MyClassInstanceReferenceVisitor instanceVisitor = new MyClassInstanceReferenceVisitor(inheritor, usages);
    scanner.processReferences(
            new ClassInstanceScanner(inheritor,
                                     instanceVisitor)
    );
    MyClassInheritorMemberReferencesVisitor classMemberVisitor = new MyClassInheritorMemberReferencesVisitor(inheritor, usages, instanceVisitor);
    inheritor.accept(classMemberVisitor);
    PsiSubstitutor inheritorSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(myClass, inheritor, PsiSubstitutor.EMPTY);

    PsiMethod[] methods = inheritor.getMethods();
    for (PsiMethod method : methods) {
      final PsiMethod baseMethod = findSuperMethodInBaseClass(method);

      if (baseMethod != null) {
        if (!baseMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
          usages.add(new NoLongerOverridingSubClassMethodUsageInfo(method, baseMethod));
        }
        else {
          final PsiMethod[] methodsByName = myClass.findMethodsByName(method.getName(), false);
          for (final PsiMethod classMethod : methodsByName) {
            final MethodSignature signature = classMethod.getSignature(inheritorSubstitutor);
            if (signature.equals(method.getSignature(PsiSubstitutor.EMPTY))) {
              if (!classMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
                usages.add(new NoLongerOverridingSubClassMethodUsageInfo(method, baseMethod));
                break;
              }
            }
          }
        }
      }
    }
  }

  @Override
  protected void performRefactoring(UsageInfo @NotNull [] usages) {
    try {
      for (UsageInfo aUsage : usages) {
        InheritanceToDelegationUsageInfo usage = (InheritanceToDelegationUsageInfo)aUsage;


        if (usage instanceof UnqualifiedNonDelegatedMemberUsageInfo) {
          delegateUsageFromClass(usage.getElement(), ((NonDelegatedMemberUsageInfo)usage).nonDelegatedMember,
                                 usage.getDelegateFieldAccessible());
        }
        else {
          upcastToDelegation(usage.getElement(), usage.getDelegateFieldAccessible());
        }
      }

      myAbstractDelegatedMethods = new HashSet<>();
      addInnerClass();
      addField(usages);
      delegateMethods();
      addImplementingInterfaces();
      updateSealedHierarchy();
    } catch (IncorrectOperationException e) {
      LOG.error(e);
    }
  }

  private void addInnerClass() throws IncorrectOperationException {
    if (!myIsInnerClassNeeded) return;

    PsiClass innerClass = myFactory.createClass(myInnerClassName);
    final PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(myBaseClass, myClass, PsiSubstitutor.EMPTY);
    final PsiClassType superClassType = myFactory.createType(myBaseClass, superClassSubstitutor);
    final PsiJavaCodeReferenceElement baseClassReferenceElement = myFactory.createReferenceElementByType(superClassType);
    if (!myBaseClass.isInterface()) {
      innerClass.getExtendsList().add(baseClassReferenceElement);
    } else {
      innerClass.getImplementsList().add(baseClassReferenceElement);
    }
    PsiUtil.setModifierProperty(innerClass, PsiModifier.PRIVATE, true);
    innerClass = (PsiClass) myClass.add(innerClass);

    List<InnerClassMethod> innerClassMethods = getInnerClassMethods();
    for (InnerClassMethod innerClassMethod : innerClassMethods) {
      innerClassMethod.createMethod(innerClass);
    }
  }

  private void delegateUsageFromClass(PsiElement element, PsiElement nonDelegatedMember,
                                      FieldAccessibility fieldAccessibility) throws IncorrectOperationException {
    if (element instanceof PsiReferenceExpression referenceExpression) {
      if (referenceExpression.getQualifierExpression() != null) {
        upcastToDelegation(referenceExpression.getQualifierExpression(), fieldAccessibility);
      } else {
        final String name = ((PsiNamedElement) nonDelegatedMember).getName();
        final String qualifier;
        if (isStatic (nonDelegatedMember)) {
          qualifier = myBaseClass.getName();
        }
        else if (!fieldAccessibility.isAccessible() && myGenerateGetter) {
          qualifier = myGetterName + "()";
        }
        else {
          qualifier = myFieldName;
        }

        PsiExpression newExpr = myFactory.createExpressionFromText(qualifier + "." + name, element);
        newExpr = (PsiExpression) CodeStyleManager.getInstance(myProject).reformat(newExpr);
        element.replace(newExpr);
      }
    }
    else if (element instanceof PsiJavaCodeReferenceElement) {
        final String name = ((PsiNamedElement) nonDelegatedMember).getName();

      PsiElement parent = element.getParent ();
      if (!isStatic (nonDelegatedMember) && parent instanceof PsiNewExpression newExpr) {
        if (newExpr.getQualifier() != null) {
          upcastToDelegation(newExpr.getQualifier(), fieldAccessibility);
        } else {
          final String qualifier;
          if (!fieldAccessibility.isAccessible() && myGenerateGetter) {
            qualifier = myGetterName + "()";
          }
          else {
            qualifier = myFieldName;
          }
          newExpr.replace(myFactory.createExpressionFromText(qualifier + "." + newExpr.getText(), parent));
        }
      }
      else {
        final String qualifier = myBaseClass.getName();
        PsiJavaCodeReferenceElement newRef = myFactory.createFQClassNameReferenceElement(qualifier + "." + name, element.getResolveScope ());
        //newRef = (PsiJavaCodeReferenceElement) CodeStyleManager.getInstance(myProject).reformat(newRef);
        element.replace(newRef);
      }
    } else {
      LOG.assertTrue(false);
    }
  }

  private static boolean isStatic(PsiElement member) {
    return member instanceof PsiModifierListOwner method && method.hasModifierProperty(PsiModifier.STATIC);
  }

  private void upcastToDelegation(PsiElement element, FieldAccessibility fieldAccessibility) throws IncorrectOperationException {
    final PsiExpression expression = (PsiExpression) element;

    final PsiExpression newExpr;
    final PsiReferenceExpression ref;
    final String delegateQualifier;
    if (!(expression instanceof PsiQualifiedExpression)) {
      delegateQualifier = "a.";
    } else {
      PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(myProject).getResolveHelper();
      final PsiVariable psiVariable = resolveHelper.resolveReferencedVariable(myFieldName, element);
      if (psiVariable == null) {
        delegateQualifier = "";
      } else {
        delegateQualifier = "a.";
      }
    }
    if (!fieldAccessibility.isAccessible() && myGenerateGetter) {
      newExpr = myFactory.createExpressionFromText(delegateQualifier + myGetterName + "()", expression);
      ref = (PsiReferenceExpression) ((PsiMethodCallExpression) newExpr).getMethodExpression().getQualifierExpression();
    } else {
      newExpr = myFactory.createExpressionFromText(delegateQualifier + myFieldName, expression);
      ref = (PsiReferenceExpression) ((PsiReferenceExpression) newExpr).getQualifierExpression();
    }
//    LOG.debug("upcastToDelegation:" + element + ":newExpr = " + newExpr);
//    LOG.debug("upcastToDelegation:" + element + ":ref = " + ref);
    if (ref != null) {
      ref.replace(expression);
    }
    expression.replace(newExpr);
//    LOG.debug("upcastToDelegation:" + element + ":replaced = " + replaced);
  }

  private void delegateMethods() throws IncorrectOperationException {
    for (PsiMethod method : myDelegatedMethods) {
      if (!myAbstractDelegatedMethods.contains(method)) {
        PsiMethod methodToAdd = delegateMethod(myFieldName, method, getSuperSubstitutor(method.getContainingClass()));

        @PsiModifier.ModifierConstant String visibility = myDelegatedMethodsVisibility.get(method);
        if (visibility != null) {
          PsiUtil.setModifierProperty(methodToAdd, visibility, true);
        }

        myClass.add(methodToAdd);
      }
    }
  }

  private PsiMethod delegateMethod(String delegationTarget,
                                   PsiMethod method,
                                   PsiSubstitutor substitutor) throws IncorrectOperationException {
    substitutor = OverrideImplementExploreUtil.correctSubstitutor(method, substitutor);
    PsiMethod methodToAdd = GenerateMembersUtil.substituteGenericMethod(method, substitutor);

    methodToAdd.getModifierList().setModifierProperty(PsiModifier.ABSTRACT, false);

    NullableNotNullManager.getInstance(myProject).copyNullableOrNotNullAnnotation(method, methodToAdd);

    final String delegationBody = getDelegationBody(methodToAdd, delegationTarget);
    PsiCodeBlock newBody = myFactory.createCodeBlockFromText(delegationBody, method);

    PsiCodeBlock oldBody = methodToAdd.getBody();
    if (oldBody != null) {
      oldBody.replace(newBody);
    }
    else {
      methodToAdd.addBefore(newBody, null);
    }

    if (methodToAdd.getDocComment() != null) methodToAdd.getDocComment().delete();
    methodToAdd = (PsiMethod)CodeStyleManager.getInstance(myProject).reformat(methodToAdd);
    methodToAdd = (PsiMethod)JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(methodToAdd);
    return methodToAdd;
  }

  private static String getDelegationBody(PsiMethod methodToAdd, String delegationTarget) {
    StringBuilder buffer = new StringBuilder();
    buffer.append("{\n");

    if (!PsiTypes.voidType().equals(methodToAdd.getReturnType())) {
      buffer.append("return ");
    }

    buffer.append(delegationTarget);
    buffer.append(".");
    buffer.append(methodToAdd.getName());
    buffer.append("(");
    PsiParameter[] params = methodToAdd.getParameterList().getParameters();
    for (int i = 0; i < params.length; i++) {
      PsiParameter param = params[i];
      if (i > 0) {
        buffer.append(",");
      }
      buffer.append(param.getName());
    }
    buffer.append(");\n}");
    return buffer.toString();
  }

  private void addImplementingInterfaces() throws IncorrectOperationException {
    final PsiReferenceList implementsList = myClass.getImplementsList();
    LOG.assertTrue(implementsList != null);
    for (PsiClass delegatedInterface : myDelegatedInterfaces) {
      if (!myClassImplementedInterfaces.contains(delegatedInterface)) {
        implementsList.add(myFactory.createClassReferenceElement(delegatedInterface));
      }
    }

    if (!myBaseClass.isInterface()) {
      final PsiReferenceList extendsList = myClass.getExtendsList();
      LOG.assertTrue(extendsList != null);
      extendsList.getReferenceElements()[0].delete();
    } else {
      final PsiJavaCodeReferenceElement[] interfaceRefs = implementsList.getReferenceElements();
      for (PsiJavaCodeReferenceElement interfaceRef : interfaceRefs) {
        final PsiElement resolved = interfaceRef.resolve();
        if (myManager.areElementsEquivalent(myBaseClass, resolved)) {
          interfaceRef.delete();
          break;
        }
      }
    }
  }

  private void updateSealedHierarchy() {
    if (!myBaseClass.hasModifierProperty(PsiModifier.SEALED)) return;
    SealedUtils.removeFromPermitsList(myBaseClass, myClass);
    PsiModifierList modifiers = myClass.getModifierList();
    if (modifiers == null) return;
    if (!modifiers.hasExplicitModifier(PsiModifier.NON_SEALED) || SealedUtils.hasSealedParent(myClass)) return;
    modifiers.setModifierProperty(PsiModifier.NON_SEALED, false);
  }

  private void addField(UsageInfo[] usages) throws IncorrectOperationException {
    final String fieldVisibility = getFieldVisibility(usages);

    final boolean fieldInitializerNeeded = isFieldInitializerNeeded();

    PsiField field = createField(fieldVisibility, fieldInitializerNeeded, defaultClassFieldType());

    if (!myIsInnerClassNeeded) {
      field.getTypeElement().replace(myFactory.createTypeElement(myBaseClassType));
      if (fieldInitializerNeeded) {
        final PsiJavaCodeReferenceElement classReferenceElement = myFactory.createReferenceElementByType(myBaseClassType);
        PsiNewExpression newExpression = (PsiNewExpression) field.getInitializer();
        newExpression.getClassReference().replace(classReferenceElement);
      }
    }

    field = (PsiField) CodeStyleManager.getInstance(myProject).reformat(field);
    myClass.add(field);
    if (!fieldInitializerNeeded) {
      fixConstructors();
    }

    if (myGenerateGetter) {
      String getterText = PsiModifier.PUBLIC + " Object " + myGetterName + "() {\n" +
                          " return " + myFieldName + ";\n" +
                          "}";
      PsiMethod getter = myFactory.createMethodFromText(getterText, myClass);
      getter.getReturnTypeElement().replace(myFactory.createTypeElement(myBaseClassType));
      getter = (PsiMethod) CodeStyleManager.getInstance(myProject).reformat(getter);
      myClass.add(getter);
    }
  }

  private String getFieldVisibility(UsageInfo[] usages) {
    if (myIsDelegateOtherMembers && !myGenerateGetter) {
      return PsiModifier.PUBLIC;
    }

    for (UsageInfo aUsage : usages) {
      InheritanceToDelegationUsageInfo usage = (InheritanceToDelegationUsageInfo)aUsage;
      final FieldAccessibility delegateFieldAccessible = usage.getDelegateFieldAccessible();
      if (delegateFieldAccessible.isAccessible() && delegateFieldAccessible.getContainingClass() != myClass) {
        return PsiModifier.PROTECTED;
      }
    }
    return PsiModifier.PRIVATE;
  }

  private String defaultClassFieldType() {
    return (myIsInnerClassNeeded ? myInnerClassName : "Object");
  }

  private PsiField createField(final String fieldVisibility, final boolean fieldInitializerNeeded, String defaultTypeName) throws IncorrectOperationException {
    StringBuilder buffer = new StringBuilder();
    buffer.append(fieldVisibility);
    buffer.append(" final ").append(defaultTypeName).append("  ");
    buffer.append(myFieldName);
    if (fieldInitializerNeeded) {
      buffer.append(" = new ").append(defaultTypeName).append("()");
    }
    buffer.append(";");
    return myFactory.createFieldFromText(buffer.toString(), myClass);
  }

  private void fixConstructors() throws IncorrectOperationException {
    if (myBaseClass.isInterface()) return;
    final PsiJavaCodeReferenceElement baseClassReference = myFactory.createClassReferenceElement(myBaseClass);

    PsiMethod[] constructors = myClass.getConstructors();
    for (PsiMethod constructor : constructors) {
      PsiCodeBlock body = constructor.getBody();
      final PsiStatement[] statements = body.getStatements();
      String fieldQualifier = "";
      PsiParameter[] constructorParams = constructor.getParameterList().getParameters();
      for (PsiParameter constructorParam : constructorParams) {
        if (myFieldName.equals(constructorParam.getName())) {
          fieldQualifier = "this.";
          break;
        }
      }
      final String assignmentText = fieldQualifier + myFieldName + "= new " + defaultClassFieldType() + "()";
      if (statements.length < 1 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true) || myBaseClass.isInterface()) {
        PsiExpressionStatement assignmentStatement =
          (PsiExpressionStatement)myFactory.createStatementFromText(
            assignmentText, body
          );
        if (!myIsInnerClassNeeded) {
          final PsiAssignmentExpression assignmentExpr = (PsiAssignmentExpression)assignmentStatement.getExpression();
          final PsiNewExpression newExpression = (PsiNewExpression)assignmentExpr.getRExpression();
          assert newExpression != null;
          final PsiJavaCodeReferenceElement classRef = newExpression.getClassReference();
          assert classRef != null;
          classRef.replace(baseClassReference);
        }

        assignmentStatement = (PsiExpressionStatement)CodeStyleManager.getInstance(myProject).reformat(assignmentStatement);
        if (statements.length > 0) {
          if (!JavaHighlightUtil.isSuperOrThisCall(statements[0], true, false)) {
            body.addBefore(assignmentStatement, statements[0]);
          }
          else {
            body.addAfter(assignmentStatement, statements[0]);
          }
        }
        else {
          body.add(assignmentStatement);
        }
      }
      else {
        final PsiExpressionStatement callStatement = ((PsiExpressionStatement)statements[0]);
        if (!JavaHighlightUtil.isSuperOrThisCall(callStatement, false, true)) {
          final PsiMethodCallExpression superConstructorCall =
            (PsiMethodCallExpression)callStatement.getExpression();
          PsiAssignmentExpression assignmentExpression =
            (PsiAssignmentExpression)myFactory.createExpressionFromText(
              assignmentText, superConstructorCall
            );
          PsiNewExpression newExpression =
            (PsiNewExpression)assignmentExpression.getRExpression();
          if (!myIsInnerClassNeeded) {
            newExpression.getClassReference().replace(baseClassReference);
          }
          assignmentExpression = (PsiAssignmentExpression)CodeStyleManager.getInstance(myProject).reformat(assignmentExpression);
          newExpression.getArgumentList().replace(superConstructorCall.getArgumentList());
          superConstructorCall.replace(assignmentExpression);
        }
      }
    }
  }

  private boolean isFieldInitializerNeeded() {
    if (myBaseClass.isInterface()) return true;
    PsiMethod[] constructors = myClass.getConstructors();
    for (PsiMethod constructor : constructors) {
      final PsiStatement[] statements = constructor.getBody().getStatements();
      if (statements.length > 0 && JavaHighlightUtil.isSuperOrThisCall(statements[0], true, false)) return false;
    }
    return true;
  }

  private List<InnerClassMethod> getInnerClassMethods() {
    ArrayList<InnerClassMethod> result = new ArrayList<>();

    // find all neccessary constructors
    if (!myBaseClass.isInterface()) {
      PsiMethod[] constructors = myClass.getConstructors();
      for (PsiMethod constructor : constructors) {
        final PsiStatement[] statements = constructor.getBody().getStatements();
        if (statements.length > 0 && JavaHighlightUtil.isSuperOrThisCall(statements[0], true, false)) {
