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

import com.intellij.codeInsight.ClassUtil;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.quickfix.*;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
import com.intellij.core.JavaPsiBundle;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.java.codeserver.core.JavaPsiModifierUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind;
import com.intellij.java.codeserver.highlighting.errors.JavaMismatchedCallContext;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.jvm.JvmModifier;
import com.intellij.lang.jvm.JvmModifiersOwner;
import com.intellij.lang.jvm.actions.JvmElementActionFactories;
import com.intellij.lang.jvm.actions.MemberRequestsKt;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.light.LightRecordField;
import com.intellij.psi.impl.light.LightRecordMethod;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.fixes.MakeDefaultLastCaseFix;
import com.siyeh.ig.psiutils.InstanceOfUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import com.siyeh.ig.psiutils.VariableNameGenerator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.stream.Stream;

import static com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds.*;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;

/**
 * Fixes attached to error messages provided by {@link JavaErrorCollector}.
 * To add new fixes use {@link #fix(JavaErrorKind, JavaFixProvider)},
 * {@link #fixes(JavaErrorKind, JavaFixesPusher)}, or {@link #multi(JavaErrorKind, JavaFixesProvider)}
 * methods and return a fix or a list of fixes from lambda.
 */
public final class DefaultJavaErrorFixProvider extends AbstractJavaErrorFixProvider {
  private final QuickFixFactory myFactory = QuickFixFactory.getInstance();

  DefaultJavaErrorFixProvider() {
    multi(UNSUPPORTED_FEATURE, error -> HighlightFixUtil.getIncreaseLanguageLevelFixes(error.psi(), error.context()));
    multi(PREVIEW_API_USAGE, error -> HighlightFixUtil.getIncreaseLanguageLevelFixes(error.psi(), error.context().feature()));
    JavaFixProvider<PsiElement, Object> genericRemover = error -> myFactory.createDeleteFix(error.psi());
    for (JavaErrorKind<?, ?> kind : List.of(ANNOTATION_MEMBER_THROWS_NOT_ALLOWED, ANNOTATION_ATTRIBUTE_DUPLICATE,
                                            ANNOTATION_NOT_ALLOWED_EXTENDS, ANNOTATION_NOT_ALLOWED_IN_PERMIT_LIST, 
                                            RECEIVER_STATIC_CONTEXT, RECEIVER_WRONG_POSITION, RECORD_HEADER_REGULAR_CLASS, 
                                            INTERFACE_CLASS_INITIALIZER, INTERFACE_CONSTRUCTOR, CLASS_IMPLICIT_INITIALIZER, 
                                            CLASS_IMPLICIT_PACKAGE, RECORD_EXTENDS, ENUM_EXTENDS, RECORD_PERMITS, ENUM_PERMITS, 
                                            ANNOTATION_PERMITS, NEW_EXPRESSION_DIAMOND_NOT_ALLOWED, REFERENCE_TYPE_ARGUMENT_STATIC_CLASS,
                                            STATEMENT_CASE_OUTSIDE_SWITCH, NEW_EXPRESSION_DIAMOND_NOT_APPLICABLE,
                                            NEW_EXPRESSION_ANONYMOUS_IMPLEMENTS_INTERFACE_WITH_TYPE_ARGUMENTS,
                                            CALL_DIRECT_ABSTRACT_METHOD_ACCESS, RECORD_SPECIAL_METHOD_TYPE_PARAMETERS,
                                            RECORD_SPECIAL_METHOD_THROWS, ARRAY_TYPE_ARGUMENTS, ARRAY_EMPTY_DIAMOND,
                                            IMPORT_LIST_EXTRA_SEMICOLON, ENUM_CONSTANT_MODIFIER, METHOD_REFERENCE_PARAMETERIZED_QUALIFIER,
                                            CONSTRUCTOR_IN_IMPLICIT_CLASS, TYPE_ARGUMENT_IN_PERMITS_LIST, MODULE_NO_PACKAGE,
                                            MODULE_DUPLICATE_REQUIRES, MODULE_DUPLICATE_EXPORTS, MODULE_DUPLICATE_OPENS, 
                                            MODULE_DUPLICATE_USES, MODULE_DUPLICATE_PROVIDES, MODULE_OPENS_IN_WEAK_MODULE)) {
      fix(kind, genericRemover);
    }

    createModifierFixes();
    createClassFixes();
    createConstructorFixes();
    createMethodFixes();
    createExpressionFixes();
    createVariableFixes();
    createPatternFixes();
    createStatementFixes();
    createExceptionFixes();
    createGenericFixes();
    createRecordFixes();
    createTypeFixes();
    createAccessFixes();
    createAnnotationFixes();
    createReceiverParameterFixes();
    createModuleFixes();
  }

  private void createModuleFixes() {
    fix(MODULE_FILE_WRONG_NAME, error -> myFactory.createRenameFileFix(PsiJavaModule.MODULE_INFO_FILE));
    fix(MODULE_FILE_DUPLICATE, error ->
      error.context() != null ? new GoToSymbolFix(error.context(), JavaErrorBundle.message("module.open.duplicate.text")) : null);
    JavaFixProvider<PsiStatement, String> mergeModuleFix = error -> MergeModuleStatementsFix.createFix(error.psi());
    fix(MODULE_DUPLICATE_REQUIRES, mergeModuleFix);
    fix(MODULE_DUPLICATE_PROVIDES, mergeModuleFix);
    fix(MODULE_DUPLICATE_OPENS, mergeModuleFix);
    fix(MODULE_DUPLICATE_USES, mergeModuleFix);
    fix(MODULE_DUPLICATE_EXPORTS, mergeModuleFix);
    fix(MODULE_FILE_WRONG_LOCATION, error -> new MoveFileFix(error.psi().getContainingFile().getVirtualFile(),
                                                             error.context(), QuickFixBundle.message("move.file.to.source.root.text")));
    fix(MODULE_OPENS_IN_WEAK_MODULE, error -> removeModifierFix(error.context(), PsiModifier.OPEN));
    fix(MODULE_DUPLICATE_EXPORTS_TARGET,
        error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.reference.fix.text")));
    fix(MODULE_DUPLICATE_OPENS_TARGET,
        error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.reference.fix.text")));
    fix(IMPORT_MODULE_NOT_ALLOWED, error -> myFactory.createReplaceOnDemandImport(error.psi(), QuickFixBundle.message("replace.import.module.fix.text")));
    fix(MODULE_DUPLICATE_IMPLEMENTATION,
        error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.reference.fix.text")));
    multi(MODULE_NOT_ON_PATH, error -> {
      PsiJavaModuleReference ref = error.psi().getReference();
      if (ref != null) {
        List<IntentionAction> registrar = new ArrayList<>();
        myFactory.registerOrderEntryFixes(ref, registrar);
        return registrar;
      }
      return List.of();
    });
    fix(MODULE_SERVICE_IMPLEMENTATION_TYPE, error -> {
      PsiClassType type = JavaPsiFacade.getElementFactory(error.project()).createType(error.context().superClass());
      return myFactory.createExtendsListFix(error.context().subClass(), type, true);
    });
    JavaFixProvider<PsiPackageAccessibilityStatement, Void> createClassInPackage = error -> {
      String packageName = error.psi().getPackageName();
      Module module = ModuleUtilCore.findModuleForFile(error.psi().getContainingFile());
      return module == null ? null : myFactory.createCreateClassInPackageInModuleFix(module, packageName);
    };
    fix(MODULE_REFERENCE_PACKAGE_NOT_FOUND, createClassInPackage);
    fix(MODULE_REFERENCE_PACKAGE_EMPTY, createClassInPackage);

    JavaFixProvider<PsiElement, JpmsModuleAccessInfo> fixExports = error -> {
      if (error.context().getTarget().getPackageName().isEmpty()) return null;
      Module jpsModule = error.context().getCurrent().getJpsModule();
      PsiJavaModule targetModule = error.context().getTarget().getModule();
      if (targetModule instanceof PsiCompiledElement && jpsModule != null) {
        return new AddExportsOptionFix(jpsModule, targetModule.getName(), error.context().getTarget().getPackageName(),
                                       error.context().getCurrent().getName());
      }
      if (!(targetModule instanceof PsiCompiledElement) && error.context().getCurrent().getModule() != null) {
        return new AddExportsDirectiveFix(requireNonNull(targetModule), error.context().getTarget().getPackageName(),
                                          error.context().getCurrent().getName());
      }
      return null;
    };
    fix(MODULE_ACCESS_FROM_UNNAMED, fixExports);
    fix(MODULE_ACCESS_FROM_NAMED, fixExports);
    JavaFixProvider<PsiElement, JpmsModuleAccessInfo> fixModuleOptions =
      error -> new AddModulesOptionFix(requireNonNull(error.context().getCurrent().getJpsModule()), 
                                       requireNonNull(error.context().getTarget().getModule()).getName());
    fix(MODULE_ACCESS_PACKAGE_NOT_IN_GRAPH, fixModuleOptions);
    fix(MODULE_ACCESS_NOT_IN_GRAPH, fixModuleOptions);
    JavaFixProvider<PsiElement, JpmsModuleAccessInfo> fixRequires =
      error -> new AddRequiresDirectiveFix(requireNonNull(error.context().getCurrent().getModule()),
                                           requireNonNull(error.context().getTarget().getModule()).getName());
    fix(MODULE_ACCESS_PACKAGE_DOES_NOT_READ, fixRequires);
    fix(MODULE_ACCESS_DOES_NOT_READ, fixRequires);
    fixes(MODULE_ACCESS_JPS_DEPENDENCY_PROBLEM, (error, sink) -> {
      if (error.psi() instanceof PsiJavaModuleReferenceElement ref) {
        PsiJavaModuleReference reference = ref.getReference();
        if (reference != null) {
          List<CommonIntentionAction> list = new ArrayList<>();
          myFactory.registerOrderEntryFixes(reference, list);
          list.forEach(sink);
        }
      }
    });
  }

  private void createStatementFixes() {
    fix(RETURN_OUTSIDE_SWITCH_EXPRESSION, error -> error.psi().getReturnValue() != null ? new ReplaceWithYieldFix(error.psi()) : null);
    fixes(RETURN_VALUE_MISSING, (error, sink) -> {
      PsiMethod method = error.context();
      sink.accept(myFactory.createMethodReturnFix(method, PsiTypes.voidType(), true));
      PsiType expectedType = HighlightFixUtil.determineReturnType(method);
      if (expectedType != null && !PsiTypes.voidType().equals(expectedType)) {
        sink.accept(myFactory.createMethodReturnFix(method, expectedType, true, true));
      }
    });
    JavaFixesPusher<PsiReturnStatement, PsiMethod> fixReturnFromVoid = (error, sink) -> {
      PsiMethod method = error.context();
      if (method != null && method.getBody() != null) {
        PsiType valueType = RefactoringChangeUtil.getTypeByExpression(requireNonNull(error.psi().getReturnValue()));
        if (valueType != null) {
          sink.accept(myFactory.createDeleteReturnFix(method, error.psi()));
          sink.accept(myFactory.createMethodReturnFix(method, valueType, true));
        }
        PsiType expectedType = HighlightFixUtil.determineReturnType(method);
        if (expectedType != null && !PsiTypes.voidType().equals(expectedType) && !expectedType.equals(valueType)) {
          sink.accept(myFactory.createMethodReturnFix(method, expectedType, true, true));
        }
      }
    };
    fixes(RETURN_FROM_CONSTRUCTOR, fixReturnFromVoid);
    fixes(RETURN_FROM_VOID_METHOD, fixReturnFromVoid);
    fix(RETURN_MISSING, error -> myFactory.createAddReturnFix(error.context()));
    fix(RETURN_MISSING, error -> error.context() instanceof PsiMethod method ?
                                 myFactory.createMethodReturnFix(method, PsiTypes.voidType(), true) : null);
    fixes(STATEMENT_BAD_EXPRESSION, (error, sink) -> {
      if (error.psi() instanceof PsiExpressionStatement expressionStatement) {
        HighlightFixUtil.registerFixesForExpressionStatement(sink, expressionStatement);
        PsiElement parent = expressionStatement.getParent();
        if (parent instanceof PsiCodeBlock ||
            parent instanceof PsiIfStatement ||
            parent instanceof PsiLoopStatement loop && loop.getBody() == expressionStatement) {
          sink.accept(PriorityIntentionActionWrapper.lowPriority(myFactory.createDeleteSideEffectAwareFix(expressionStatement)));
        }
      }
    });
    fix(STATEMENT_BAD_EXPRESSION, error -> HighlightFixTypoUtil.createKeywordTypoFix(error.psi()));
    fix(STATEMENT_UNREACHABLE,
        error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("delete.unreachable.statement.fix.text")));
    fix(STATEMENT_UNREACHABLE_LOOP_BODY, error -> myFactory.createSimplifyBooleanFix(error.psi(), false));
    fix(FOREACH_NOT_APPLICABLE, error -> myFactory.createNotIterableForEachLoopFix(error.psi()));
    fix(LABEL_WITHOUT_STATEMENT, error -> HighlightFixTypoUtil.createKeywordTypoFix(error.psi()));
    fix(SWITCH_LABEL_EXPECTED, error -> {
      PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(error.psi(), PsiSwitchLabeledRuleStatement.class);
      return previousRule == null ? null : myFactory.createWrapSwitchRuleStatementsIntoBlockFix(previousRule);
    });
    fixes(SWITCH_SELECTOR_TYPE_INVALID, (error, sink) -> {
      HighlightFixUtil.registerFixesOnInvalidSelector(sink, error.psi());
      JavaFeature feature = error.context().getFeature();
      if (feature != null) {
        HighlightFixUtil.getIncreaseLanguageLevelFixes(error.psi(), feature).forEach(sink);
      }
    });
    fix(SWITCH_LABEL_DUPLICATE, error -> {
      if (error.psi() instanceof PsiCaseLabelElement caseLabel) {
        return myFactory.createDeleteSwitchLabelFix(caseLabel);
      }
      else if (error.context() == JavaPsiSwitchUtil.SwitchSpecialValue.DEFAULT_VALUE) {
        return myFactory.createDeleteDefaultFix(null, error.psi());
      }
      return null;
    });
    fix(SWITCH_EXPRESSION_NO_RESULT, error -> {
      return myFactory.createLiftThrowOutOfSwitchExpression(error.psi());
    });
    fix(SYNTAX_ERROR, error -> error.psi().getParent() instanceof PsiSwitchLabeledRuleStatement rule &&
                               error.psi().getErrorDescription().equals(JavaPsiBundle.message("expected.switch.rule"))
                               ? myFactory.createWrapSwitchRuleStatementsIntoBlockFix(rule)
                               : null);
    fix(SYNTAX_ERROR, error -> HighlightFixTypoUtil.createKeywordTypoFix(error.psi()));
  }

  private void createMethodFixes() {
    JavaFixProvider<PsiMethod, Void> addBody = error -> myFactory.createAddMethodBodyFix(error.psi());
    fix(METHOD_DEFAULT_SHOULD_HAVE_BODY, addBody);
    fix(METHOD_STATIC_IN_INTERFACE_SHOULD_HAVE_BODY, addBody);
    fix(METHOD_PRIVATE_IN_INTERFACE_SHOULD_HAVE_BODY, addBody);
    fix(METHOD_SHOULD_HAVE_BODY, addBody);
    fix(METHOD_SHOULD_HAVE_BODY_OR_ABSTRACT, addBody);
    fix(METHOD_DEFAULT_IN_CLASS, error -> removeModifierFix(error.psi(), PsiModifier.DEFAULT));
    fix(METHOD_ABSTRACT_BODY, error -> removeModifierFix(error.psi(), PsiModifier.ABSTRACT));
    fix(METHOD_SHOULD_HAVE_BODY_OR_ABSTRACT, error -> maybeAddModifierFix(error.psi(), PsiModifier.ABSTRACT));
    JavaFixProvider<PsiMethod, Void> deleteBody = error -> myFactory.createDeleteMethodBodyFix(error.psi());
    fix(METHOD_ABSTRACT_BODY, deleteBody);
    fix(METHOD_INTERFACE_BODY, deleteBody);
    fix(METHOD_NATIVE_BODY, deleteBody);
    fix(METHOD_NO_PARAMETER_LIST, error -> myFactory.createAddParameterListFix(error.psi()));
    fixes(METHOD_INTERFACE_BODY, (error, sink) -> {
      PsiMethod method = error.psi();
      if (PsiUtil.isAvailable(JavaFeature.EXTENSION_METHODS, method) && Stream.of(method.findDeepestSuperMethods())
        .map(PsiMethod::getContainingClass)
        .filter(Objects::nonNull)
        .map(PsiClass::getQualifiedName)
        .noneMatch(CommonClassNames.JAVA_LANG_OBJECT::equals)) {
        sink.accept(PriorityIntentionActionWrapper.highPriority(addModifierFix(method, PsiModifier.DEFAULT)));
        sink.accept(addModifierFix(method, PsiModifier.STATIC));
      }
    });
    fix(METHOD_ABSTRACT_BODY, error -> myFactory.createPushDownMethodFix());
    fix(METHOD_NATIVE_BODY, error -> myFactory.createPushDownMethodFix());
    fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> removeModifierFix(error.context().method(), PsiModifier.STATIC));
    fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> maybeAddModifierFix(error.context().method(), PsiModifier.STATIC));
    fix(METHOD_STATIC_OVERRIDES_INSTANCE, error -> maybeAddModifierFix(error.context().superMethod(), PsiModifier.STATIC));
    fix(METHOD_INSTANCE_OVERRIDES_STATIC, error -> removeModifierFix(error.context().superMethod(), PsiModifier.STATIC));
    fix(METHOD_OVERRIDES_FINAL, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(METHOD_INHERITANCE_WEAKER_PRIVILEGES,
        error -> error.psi() instanceof PsiMethod ? myFactory.createChangeModifierFix() :
                 error.psi() instanceof PsiClass cls ? myFactory.createImplementMethodsFix(cls) : null);
    multi(METHOD_INHERITANCE_CLASH_DOES_NOT_THROW, error -> List.of(
      myFactory.createMethodThrowsFix(error.context().method(), error.context().exceptionType(), false, false),
      myFactory.createMethodThrowsFix(error.context().superMethod(), error.context().exceptionType(), true, true)
    ));
    fix(VARARG_NOT_LAST_PARAMETER, error -> myFactory.createMakeVarargParameterLastFix(error.psi()));
    fixes(METHOD_INHERITANCE_CLASH_INCOMPATIBLE_RETURN_TYPES, (error, sink) -> {
      IncompatibleOverrideReturnTypeContext context = error.context();
      PsiMethod method = context.method();
      if (method instanceof LightRecordMethod recordMethod) {
        HighlightFixUtil.getChangeVariableTypeFixes(recordMethod.getRecordComponent(), context.superMethodReturnType()).forEach(sink);
      }
      else {
        sink.accept(myFactory.createMethodReturnFix(method, context.superMethodReturnType(), false));
      }
      sink.accept(myFactory.createSuperMethodReturnFix(context.superMethod(), context.methodReturnType()));
      PsiClass returnClass = PsiUtil.resolveClassInClassTypeOnly(context.methodReturnType());
      if (returnClass != null && context.superMethodReturnType() instanceof PsiClassType classType) {
        sink.accept(myFactory.createChangeParameterClassFix(returnClass, classType));
      }
    });
    fix(VARARG_CSTYLE_DECLARATION, error -> new NormalizeBracketsFix(error.psi()));
    fix(METHOD_MISSING_RETURN_TYPE_NOT_CONSTRUCTOR, error -> {
      PsiMethod method = error.psi();
      PsiType expectedType = HighlightFixUtil.determineReturnType(method);
      return expectedType != null ? myFactory.createMethodReturnFix(method, expectedType, true, true) : null;
    });
    fixes(METHOD_MISSING_RETURN_TYPE, (error, sink) -> {
      String className = error.context();
      PsiMethod method = error.psi();
      sink.accept(myFactory.createRenameElementFix(method, className));
      PsiType expectedType = HighlightFixUtil.determineReturnType(method);
      if (expectedType != null) {
        sink.accept(myFactory.createMethodReturnFix(method, expectedType, true, true));
      }
    });
    fixes(METHOD_ABSTRACT_IN_NON_ABSTRACT_CLASS, (error, sink) -> {
      PsiMethod method = error.context();
      sink.accept(method.getBody() != null ? removeModifierFix(method, PsiModifier.ABSTRACT) : myFactory.createAddMethodBodyFix(method));
      PsiClass aClass = method.getContainingClass();
      if (aClass != null && !aClass.isEnum() && !aClass.isRecord()) {
        sink.accept(addModifierFix(aClass, PsiModifier.ABSTRACT));
      }
    });
    fix(METHOD_GENERIC_CLASH, error ->
      error.context().method() instanceof SyntheticElement ?
      null : myFactory.createSameErasureButDifferentMethodsFix(error.context().method(), error.context().superMethod()));
    fixes(METHOD_DUPLICATE, (error, sink) -> {
      // filters out synthetic methods, such as Enum#values()
      var duplicates = ContainerUtil.filter(error.context().methods(), m -> !(m instanceof SyntheticElement));
      if (duplicates.size() > 1) {
        sink.accept(myFactory.createShowDuplicateElementsFix(duplicates));
      }
    });
  }

  private void createExceptionFixes() {
    fix(EXCEPTION_MUST_BE_DISJOINT, error -> myFactory.createDeleteMultiCatchFix(error.psi()));
    fix(EXCEPTION_NEVER_THROWN_TRY_MULTI, error -> myFactory.createDeleteMultiCatchFix(error.psi()));
    fixes(EXCEPTION_ALREADY_CAUGHT, (error, sink) -> {
      PsiParameter parameter = PsiTreeUtil.getParentOfType(error.psi(), PsiParameter.class, false);
      if (parameter == null) return;
      boolean multiCatch = PsiUtil.getParameterTypeElements(parameter).size() > 1;
      sink.accept(multiCatch ? myFactory.createDeleteMultiCatchFix(error.psi()) : myFactory.createDeleteCatchFix(parameter));
      PsiCatchSection catchSection = (PsiCatchSection)requireNonNull(parameter.getDeclarationScope());
      PsiCatchSection upperCatchSection = error.context();
      sink.accept(myFactory.createMoveCatchUpFix(catchSection, upperCatchSection));
    });
    fix(EXCEPTION_NEVER_THROWN_TRY, error -> myFactory.createDeleteCatchFix(error.psi()));
    fixes(EXCEPTION_UNHANDLED, (error, sink) -> {
      PsiElement element = error.psi();
      HighlightFixUtil.registerUnhandledExceptionFixes(element, sink);
      if (element instanceof PsiMethod method) {
        sink.accept(myFactory.createAddExceptionToThrowsFix(method, error.context()));
        PsiClass aClass = method.getContainingClass();
        if (aClass != null) {
          sink.accept(myFactory.createCreateConstructorMatchingSuperFix(aClass));
        }
      }
      else if (element instanceof PsiClass cls) {
        sink.accept(myFactory.createCreateConstructorMatchingSuperFix(cls));
      }
    });
    fixes(EXCEPTION_UNHANDLED_CLOSE, (error, sink) -> HighlightFixUtil.registerUnhandledExceptionFixes(error.psi(), sink));
  }

  private void createConstructorFixes() {
    JavaFixesPusher<PsiMember, Object> constructorCallFixes = (error, sink) -> {
        if (error.psi() instanceof PsiClass cls) {
          sink.accept(myFactory.createCreateConstructorMatchingSuperFix(cls));
        }
        else if (error.psi() instanceof PsiMethod method) {
          sink.accept(myFactory.createInsertSuperFix(method));
          sink.accept(myFactory.createInsertThisFix(method));
        }
      };
    fixes(CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL, constructorCallFixes);
    fixes(CONSTRUCTOR_NO_DEFAULT, constructorCallFixes);
    fix(CONSTRUCTOR_AMBIGUOUS_IMPLICIT_CALL, error -> myFactory.createAddDefaultConstructorFix(
      requireNonNull(error.context().psiClass().getSuperClass())));
    fix(CONSTRUCTOR_NO_DEFAULT, error -> myFactory.createAddDefaultConstructorFix(error.context()));
  }

  private void createModifierFixes() {
    fix(MODIFIER_NOT_ALLOWED, error -> {
      @SuppressWarnings("MagicConstant") @PsiModifier.ModifierConstant String modifier = error.context();
      PsiModifierList list = (PsiModifierList)error.psi().getParent();
      PsiElement parent = list.getParent();
      if (parent instanceof PsiClass aClass && !aClass.isInterface()
          && (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier))) {
        return myFactory.createChangeModifierFix();
      }
      if (parent instanceof PsiModifierListOwner owner) {
        return removeModifierFix(owner, modifier);
      }
      return myFactory.createModifierListFix(list, modifier, false, false);
    });
    JavaFixProvider<PsiKeyword, Object> removeModifier = error -> {
      @SuppressWarnings("MagicConstant") @PsiModifier.ModifierConstant String modifier = error.psi().getText();
      PsiModifierList list = (PsiModifierList)error.psi().getParent();
      return removeModifierFix((PsiModifierListOwner)list.getParent(), modifier);
    };
    fix(MODIFIER_NOT_ALLOWED_LOCAL_CLASS, removeModifier);
    fix(MODIFIER_REPEATED, removeModifier);
    fix(MODIFIER_INCOMPATIBLE, removeModifier);
    fix(MODIFIER_NOT_ALLOWED_NON_SEALED, removeModifier);
  }
  
  private void createPatternFixes() {
    fixes(PATTERN_DECONSTRUCTION_COUNT_MISMATCH, (error, sink) -> {
      if (error.context().hasMismatch()) return;
      PsiRecordComponent[] recordComponents = error.context().recordComponents();
      PsiPattern[] patternComponents = error.context().patternComponents();
      PsiDeconstructionList list = error.psi();
      if (patternComponents.length < recordComponents.length) {
        PsiRecordComponent[] missingRecordComponents =
          Arrays.copyOfRange(recordComponents, patternComponents.length, recordComponents.length);
        List<AddMissingDeconstructionComponentsFix.Pattern> missingPatterns =
          ContainerUtil.map(missingRecordComponents, component -> AddMissingDeconstructionComponentsFix.Pattern.create(component, list));
        sink.accept(new AddMissingDeconstructionComponentsFix(list, missingPatterns));
      }
      else {
        PsiPattern[] elementsToDelete = Arrays.copyOfRange(patternComponents, recordComponents.length, patternComponents.length);
        int diff = patternComponents.length - recordComponents.length;
        String text = QuickFixBundle.message("remove.redundant.nested.patterns.fix.text", diff);
        sink.accept(QuickFixFactory.getInstance().createDeleteFix(elementsToDelete, text));
      }
    });
    fix(UNSUPPORTED_FEATURE, error -> {
      if (error.context() == JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS) {
        return HighlightFixUtil.createPrimitiveToBoxedPatternFix(error.psi());
      }
      return null;
    });
    fix(CAST_INCONVERTIBLE, error -> {
      if (error.psi() instanceof PsiInstanceOfExpression instanceOfExpression &&
          TypeConversionUtil.isPrimitiveAndNotNull(error.context().rType())) {
        return myFactory.createReplacePrimitiveWithBoxedTypeAction(
          error.context().lType(), requireNonNull(InstanceOfUtils.findCheckTypeElement(instanceOfExpression)));
      }
      return null;
    });
    JavaFixProvider<PsiTypeTestPattern, Object> redundantInstanceOfFix = error -> {
      PsiPatternVariable variable = error.psi().getPatternVariable();
      if (variable != null && !VariableAccessUtils.variableIsUsed(variable, variable.getDeclarationScope())) {
        return new RedundantInstanceofFix(error.psi().getParent());
      }
      return null;
    };
    fix(PATTERN_INSTANCEOF_EQUALS, redundantInstanceOfFix);
    fix(PATTERN_INSTANCEOF_SUPERTYPE, redundantInstanceOfFix);
  }

  private void createVariableFixes() {
    fix(UNNAMED_VARIABLE_BRACKETS, error -> new NormalizeBracketsFix(error.psi()));
    fix(UNNAMED_VARIABLE_WITHOUT_INITIALIZER, error -> myFactory.createAddVariableInitializerFix(error.psi()));
    fixes(LVTI_NO_INITIALIZER, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink));
    fixes(LVTI_NULL, (error, sink) -> HighlightFixUtil.registerSpecifyVarTypeFix(error.psi(), sink));
    JavaFixProvider<PsiJavaCodeReferenceElement, PsiVariable> innerClassAccessFix = error -> {
      PsiVariable variable = error.context();
      PsiElement scope = requireNonNull(ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, error.psi()));
      return myFactory.createVariableAccessFromInnerClassFix(variable, scope);
    };
    fix(VARIABLE_MUST_BE_FINAL, innerClassAccessFix);
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, innerClassAccessFix);
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, innerClassAccessFix);
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, innerClassAccessFix);
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_LAMBDA, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
    fix(VARIABLE_MUST_BE_EFFECTIVELY_FINAL_GUARD, error -> myFactory.createMakeVariableEffectivelyFinalFix(error.context()));
    fix(VARIABLE_ALREADY_ASSIGNED, error -> myFactory.createDeferFinalAssignmentFix(error.context(), error.psi()));
    fix(VARIABLE_ALREADY_ASSIGNED, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(VARIABLE_ALREADY_ASSIGNED_FIELD, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(VARIABLE_ALREADY_ASSIGNED_CONSTRUCTOR, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(VARIABLE_ALREADY_ASSIGNED_INITIALIZER, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(VARIABLE_ASSIGNED_IN_LOOP, error -> removeModifierFix(error.context(), PsiModifier.FINAL));
    fix(VARIABLE_ALREADY_DEFINED, error -> myFactory.createNavigateToAlreadyDeclaredVariableFix(error.context()));
    fix(VARIABLE_ALREADY_DEFINED,
        error -> error.psi() instanceof PsiLocalVariable local ? myFactory.createReuseVariableDeclarationFix(local) : null);

    fixes(FIELD_NOT_INITIALIZED, (error, sink) -> {
      PsiField field = error.psi();
      sink.accept(myFactory.createCreateConstructorParameterFromFieldFix(field));
      sink.accept(myFactory.createInitializeFinalFieldInConstructorFix(field));
      sink.accept(myFactory.createAddVariableInitializerFix(field));
      PsiClass containingClass = field.getContainingClass();
      if (containingClass != null && !containingClass.isInterface()) {
        sink.accept(removeModifierFix(field, PsiModifier.FINAL));
      }
    });
    fixes(VARIABLE_NOT_INITIALIZED, (error, sink) -> {
      PsiVariable variable = error.context();
      if (!(variable instanceof LightRecordField)) {
        sink.accept(myFactory.createAddVariableInitializerFix(variable));
      }
      if (variable instanceof PsiLocalVariable) {
        PsiElement topBlock = PsiUtil.getVariableCodeBlock(variable, null);
        if (topBlock != null) {
          sink.accept(HighlightFixUtil.createInsertSwitchDefaultFix(variable, topBlock, error.psi()));
        }
      }
      if (variable instanceof PsiField field) {
        sink.accept(removeModifierFix(field, PsiModifier.FINAL));
      }
    });
  }

  private void createExpressionFixes() {
    fix(NEW_EXPRESSION_QUALIFIED_MALFORMED, error -> myFactory.createRemoveNewQualifierFix(error.psi(), null));
    fix(NEW_EXPRESSION_QUALIFIED_STATIC_CLASS, 
        error -> error.context().isEnum() ? null : removeModifierFix(error.context(), PsiModifier.STATIC));
    fix(NEW_EXPRESSION_QUALIFIED_STATIC_CLASS, error -> myFactory.createRemoveNewQualifierFix(error.psi(), error.context()));
    fix(NEW_EXPRESSION_QUALIFIED_ANONYMOUS_IMPLEMENTS_INTERFACE, error -> myFactory.createRemoveNewQualifierFix(error.psi(), null));
    fix(NEW_EXPRESSION_QUALIFIED_QUALIFIED_CLASS_REFERENCE,
        error -> myFactory.createDeleteFix(error.psi(), QuickFixBundle.message("remove.qualifier.fix")));
    fix(NEW_EXPRESSION_DIAMOND_INFERENCE_FAILURE,
        error -> {
          if (error.context() == PsiDiamondType.DiamondInferenceResult.ANONYMOUS_INNER_RESULT &&
              !PsiUtil.isLanguageLevel9OrHigher(error.psi())) {
            return myFactory.createIncreaseLanguageLevelFix(LanguageLevel.JDK_1_9);
          }
          return null;
        });
    fixes(NEW_EXPRESSION_UNRESOLVED_CONSTRUCTOR, (error, sink) -> {
      PsiConstructorCall constructorCall = error.psi();
      PsiExpressionList list = constructorCall.getArgumentList();
      if (list != null) {
        JavaResolveResult[] results = error.context().results();
        WrapExpressionFix.registerWrapAction(results, list.getExpressions(), sink);
