// 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.java.codeserver.highlighting.errors;

import com.intellij.codeInsight.AnnotationTargetUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.codeserver.core.JavaPreviewFeatureUtil;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Parameterized;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Simple;
import com.intellij.java.syntax.parser.JavaKeywords;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.HtmlChunk.Element;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.*;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.VisibilityUtil;
import org.jetbrains.annotations.*;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle.message;
import static com.intellij.java.codeserver.highlighting.errors.JavaErrorFormatUtil.*;
import static com.intellij.util.ObjectUtils.tryCast;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;

/**
 * All possible Java error kinds.
 * <p>
 * The constants generally should be named as the corresponding properties from {@link JavaCompilationErrorBundle}, 
 * uppercased and having dots replaced with underscores.
 * <p>
 * While not all the names follow the convention described below, it's preferred to follow. In general, they
 * should not read like an English sentence. Instead, the first part should denote a group of errors (like statement,
 * or construct the error relates to), optionally followed by a more specific subgroup, 
 * and the rest should describe the situation shortly (no prepositions, etc.). 
 * Avoid abbreviating words (e.g., IMPL instead of IMPLEMENTATION, etc.)
 * <p>
 * An example of a good name: ANNOTATION_ATTRIBUTE_NAME_MISSING. Here, ANNOTATION is a group, ATTRIBUTE is a subgroup,
 * and NAME_MISSING is the description.
 */
public final class JavaErrorKinds {
  private JavaErrorKinds() {}

  public static final Parameterized<PsiElement, @NotNull JavaFeature> UNSUPPORTED_FEATURE =
    parameterized(PsiElement.class, JavaFeature.class, "insufficient.language.level")
      .withDescription((element, feature) -> {
        String name = feature.getFeatureName();
        String version = JavaSdkVersion.fromLanguageLevel(PsiUtil.getLanguageLevel(element)).getDescription();
        return message("insufficient.language.level", name, version);
      })
      .withRange((element, feature) -> {
        if (feature == JavaFeature.TEXT_BLOCK_ESCAPES && element instanceof PsiLiteralExpression literalExpression) {
          return PsiLiteralUtil.findSlashS(literalExpression.getText());
        }
        return null;
      });
  public static final Parameterized<PsiElement, @NotNull TextRange> ILLEGAL_UNICODE_ESCAPE =
    parameterized(PsiElement.class, TextRange.class, "illegal.unicode.escape")
      .withRange((psi, range) -> range);
  public static final Parameterized<PsiElement, @NotNull Character> ILLEGAL_CHARACTER =
    parameterized(PsiElement.class, Character.class, "illegal.character")
      .withDescription((e, c) -> {
        boolean printable = StringUtil.isPrintableUnicode(c) && !Character.isSpaceChar(c);
        @NlsSafe String hex = String.format("U+%04X", (int)c);
        return message("illegal.character", printable ? c + " (" + hex + ")" : hex);
      });
  public static final Simple<PsiErrorElement> SYNTAX_ERROR =
    error(PsiErrorElement.class, "syntax.error")
      .withRange(e -> {
        TextRange range = e.getTextRange();
        if (range.getLength() == 0) {
          PsiFile file = e.getContainingFile();
          int endOffset = range.getEndOffset();
          if (endOffset < file.getTextLength() && file.getFileDocument().getCharsSequence().charAt(endOffset) != '\n') {
            return TextRange.from(0, 1);
          }
        }
        return null;
      })
      .withDescription(e -> message("syntax.error", e.getErrorDescription()));
  public static final Parameterized<PsiElement, JavaPreviewFeatureUtil.PreviewFeatureUsage> PREVIEW_API_USAGE =
    parameterized(PsiElement.class, JavaPreviewFeatureUtil.PreviewFeatureUsage.class, "preview.api.usage")
      .withDescription((e, c) -> message("preview.api.usage", c.targetName()));

  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_HERE = error("annotation.not.allowed.here");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_IN_PERMIT_LIST = error("annotation.not.allowed.in.permit.list");
  public static final Simple<PsiPackageStatement> ANNOTATION_NOT_ALLOWED_ON_PACKAGE =
    error(PsiPackageStatement.class, "annotation.not.allowed.on.package")
      .withAnchor(statement -> requireNonNull(statement.getAnnotationList()));
  public static final Simple<PsiReferenceList> ANNOTATION_MEMBER_THROWS_NOT_ALLOWED =
    error(PsiReferenceList.class, "annotation.member.may.not.have.throws.list").withAnchor(list -> requireNonNull(list.getFirstChild()));
  public static final Simple<PsiParameterList> ANNOTATION_MEMBER_MAY_NOT_HAVE_PARAMETERS =
    error(PsiParameterList.class, "annotation.member.may.not.have.parameters");
  public static final Simple<PsiReferenceList> ANNOTATION_NOT_ALLOWED_EXTENDS =
    error(PsiReferenceList.class, "annotation.may.not.have.extends.list").withAnchor(list -> requireNonNull(list.getFirstChild()));
  public static final Simple<PsiElement> ANNOTATION_LOCAL = error("annotation.cannot.be.local");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_VAR = error("annotation.not.allowed.var");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_VOID = error("annotation.not.allowed.void");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_CLASS = error("annotation.not.allowed.class");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_REF = error("annotation.not.allowed.ref");
  public static final Simple<PsiAnnotation> ANNOTATION_NOT_ALLOWED_STATIC = error("annotation.not.allowed.static");
  public static final Simple<PsiJavaCodeReferenceElement> ANNOTATION_TYPE_EXPECTED = error("annotation.type.expected");
  public static final Simple<PsiReferenceExpression> ANNOTATION_REPEATED_TARGET = error("annotation.repeated.target");
  public static final Simple<PsiNameValuePair> ANNOTATION_ATTRIBUTE_NAME_MISSING =
    error("annotation.attribute.name.missing");
  public static final Simple<PsiAnnotationMemberValue> ANNOTATION_ATTRIBUTE_NON_CLASS_LITERAL =
    error("annotation.attribute.non.class.literal");
  public static final Simple<PsiExpression> ANNOTATION_ATTRIBUTE_NON_ENUM_CONSTANT = error("annotation.attribute.non.enum.constant");
  public static final Simple<PsiExpression> ANNOTATION_ATTRIBUTE_NON_CONSTANT = error("annotation.attribute.non.constant");
  public static final Simple<PsiTypeElement> ANNOTATION_CYCLIC_TYPE = error("annotation.cyclic.element.type");
  public static final Parameterized<PsiMethod, PsiMethod> ANNOTATION_MEMBER_CLASH =
    error(PsiMethod.class, "annotation.member.clash")
      .withAnchor(curMethod -> requireNonNull(curMethod.getNameIdentifier()))
      .<PsiMethod>parameterized()
      .withDescription((curMethod, clashMethod) -> {
        PsiClass containingClass = requireNonNull(clashMethod.getContainingClass());
        return message("annotation.member.clash", formatMethod(clashMethod), formatClass(containingClass));
      });
  public static final Parameterized<PsiTypeElement, PsiType> ANNOTATION_METHOD_INVALID_TYPE =
    parameterized(PsiTypeElement.class, PsiType.class, "annotation.member.invalid.type")
      .withDescription((element, type) ->
                            message("annotation.member.invalid.type", type == null ? null : type.getPresentableText()));
  public static final Parameterized<PsiAnnotationMemberValue, AnnotationValueErrorContext> ANNOTATION_ATTRIBUTE_INCOMPATIBLE_TYPE =
    parameterized(PsiAnnotationMemberValue.class, AnnotationValueErrorContext.class, 
                  "annotation.attribute.incompatible.type").withDescription((value, context) -> {
      String text;
      if (value instanceof PsiAnnotation annotation) {
        text = requireNonNull(annotation.getNameReferenceElement()).getText();
      }
      else {
        PsiType type = ((PsiExpression)value).getType();
        text = type == null ? JavaKeywords.NULL : PsiTypesUtil.removeExternalAnnotations(type).getInternalCanonicalText();
      }
      return message("annotation.attribute.incompatible.type", context.typeText(), text);
    });
  public static final Parameterized<PsiArrayInitializerMemberValue, AnnotationValueErrorContext> ANNOTATION_ATTRIBUTE_ILLEGAL_ARRAY_INITIALIZER =
    parameterized(PsiArrayInitializerMemberValue.class, AnnotationValueErrorContext.class, 
                  "annotation.attribute.illegal.array.initializer").withDescription((element, context) -> {
      return message("annotation.attribute.illegal.array.initializer", context.typeText());
    });
  public static final Parameterized<PsiNameValuePair, String> ANNOTATION_ATTRIBUTE_DUPLICATE =
    parameterized(PsiNameValuePair.class, String.class, "annotation.attribute.duplicate")
      .withDescription((pair, attribute) -> message("annotation.attribute.duplicate", attribute));
  public static final Parameterized<PsiNameValuePair, String> ANNOTATION_ATTRIBUTE_UNKNOWN_METHOD =
    error(PsiNameValuePair.class, "annotation.attribute.unknown.method")
      .withAnchor(pair -> requireNonNull(pair.getReference()).getElement())
      .withHighlightType(pair -> pair.getName() == null ? JavaErrorHighlightType.ERROR : JavaErrorHighlightType.WRONG_REF)
      .<String>parameterized()
      .withDescription((pair, methodName) -> message("annotation.attribute.unknown.method", methodName));
  public static final Simple<PsiReferenceList> ANNOTATION_PERMITS = error(PsiReferenceList.class, "annotation.permits")
    .withAnchor(PsiReferenceList::getFirstChild);

  // Can be anchored on @FunctionalInterface annotation or at a call site
  public static final Parameterized<PsiElement, PsiType> LAMBDA_NOT_FUNCTIONAL_INTERFACE =
    parameterized(PsiElement.class, PsiType.class, "lambda.not.a.functional.interface")
      .withDescription((element, type) -> message("lambda.not.a.functional.interface", formatType(type)));
  // Can be anchored on @FunctionalInterface annotation or at a call site
  public static final Parameterized<PsiElement, PsiType> LAMBDA_NO_TARGET_METHOD =
    parameterized("lambda.no.target.method.found");
  // Can be anchored on @FunctionalInterface annotation or at a call site
  public static final Parameterized<PsiElement, PsiType> LAMBDA_MULTIPLE_TARGET_METHODS =
    parameterized(PsiElement.class, PsiType.class, "lambda.multiple.sam.candidates")
      .withDescription((psi, type) -> message("lambda.multiple.sam.candidates",
                                              (type instanceof PsiClassType cls ? cls.rawType() : type).getPresentableText()));
  public static final Parameterized<PsiAnnotation, PsiClass> FUNCTIONAL_INTERFACE_SEALED =
    parameterized("lambda.sealed.functional.interface");
  public static final Simple<PsiLambdaExpression> LAMBDA_NOT_EXPECTED = error("lambda.not.expected");
  public static final Simple<PsiParameterList> LAMBDA_PARAMETERS_INCONSISTENT_VAR = error("lambda.parameters.inconsistent.var");
  public static final Simple<PsiLambdaExpression> LAMBDA_SEALED = error("lambda.sealed");
  public static final Simple<PsiFunctionalExpression> LAMBDA_TYPE_INFERENCE_FAILURE = error("lambda.type.inference.failure");
  public static final Simple<PsiFunctionalExpression> LAMBDA_SAM_GENERIC = error("lambda.sam.generic");
  public static final Parameterized<PsiFunctionalExpression, PsiType> LAMBDA_TARGET_NOT_INTERFACE = 
    parameterized("lambda.target.not.interface");
  public static final Parameterized<PsiLambdaExpression, MethodCandidateInfo> LAMBDA_INFERENCE_ERROR =
    parameterized(PsiLambdaExpression.class, MethodCandidateInfo.class, "lambda.inference.error")
      .withDescription((psi, candidate) -> message("lambda.inference.error", candidate.getInferenceErrorMessage()));
  public static final Parameterized<PsiElement, String> LAMBDA_RETURN_TYPE_ERROR =
    parameterized(PsiElement.class, String.class, "lambda.return.type.error")
      .withDescription((psi, message) -> message("lambda.return.type.error", message));
  public static final Parameterized<PsiLambdaExpression, PsiMethod> LAMBDA_WRONG_NUMBER_OF_PARAMETERS =
    parameterized(PsiLambdaExpression.class, PsiMethod.class, "lambda.wrong.number.of.parameters")
      .withAnchor(PsiLambdaExpression::getParameterList)
      .withDescription((lambda, method) -> message("lambda.wrong.number.of.parameters",
                                                   method.getParameterList().getParametersCount(),
                                                   lambda.getParameterList().getParametersCount()));
  public static final Parameterized<PsiParameter, PsiType> LAMBDA_INCOMPATIBLE_PARAMETER_TYPES =
    parameterized(PsiParameter.class, PsiType.class, "lambda.incompatible.parameter.types")
      .withDescription((parameter, expectedType) -> message("lambda.incompatible.parameter.types",
                                                            expectedType.getPresentableText(), parameter.getType().getPresentableText()));
  
    
  public static final Simple<PsiMethodReferenceExpression> METHOD_REFERENCE_SEALED = error("method.reference.sealed");
  public static final Simple<PsiMethodReferenceExpression> METHOD_REFERENCE_NOT_EXPECTED = error("method.reference.not.expected");
  public static final Simple<PsiMethodReferenceExpression> METHOD_REFERENCE_RAW_CONSTRUCTOR = error("method.reference.raw.constructor");
  public static final Simple<PsiElement> METHOD_REFERENCE_QUALIFIER_CLASS_UNRESOLVED = 
    error(PsiElement.class, "method.reference.qualifier.class.unresolved")
      .withDescription(qualifier -> message("method.reference.qualifier.class.unresolved", qualifier.getText()));
  public static final Simple<PsiTypeElement> METHOD_REFERENCE_QUALIFIER_WILDCARD = error("method.reference.qualifier.wildcard");
  public static final Parameterized<PsiMethodReferenceExpression, String> METHOD_REFERENCE_RETURN_TYPE_ERROR =
    parameterized(PsiMethodReferenceExpression.class, String.class, "method.reference.return.type.error")
      .withDescription((psi, message) -> message("method.reference.return.type.error", message));
  public static final Parameterized<PsiMethodReferenceExpression, MethodCandidateInfo> METHOD_REFERENCE_INFERENCE_ERROR =
    parameterized(PsiMethodReferenceExpression.class, MethodCandidateInfo.class, "method.reference.inference.error")
      .withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref))
      .withDescription((ref, candidate) -> message("method.reference.inference.error", candidate.getInferenceErrorMessage()));
  public static final Parameterized<PsiMethodReferenceExpression, PsiClass> METHOD_REFERENCE_UNRESOLVED_CONSTRUCTOR =
    parameterized(PsiMethodReferenceExpression.class, PsiClass.class, "method.reference.unresolved.constructor")
      .withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref))
      .withDescription((ref, cls) -> message("method.reference.unresolved.constructor", cls.getName()));
  public static final Simple<PsiMethodReferenceExpression> METHOD_REFERENCE_UNRESOLVED_METHOD =
    error(PsiMethodReferenceExpression.class, "method.reference.unresolved.method")
      .withAnchor(ref -> requireNonNullElse(ref.getReferenceNameElement(), ref))
      .withHighlightType(JavaErrorHighlightType.WRONG_REF)
      .withDescription(ref -> message("method.reference.unresolved.method", ref.getReferenceName()));
  public static final Parameterized<PsiMethodReferenceExpression, PsiMethod> METHOD_REFERENCE_ABSTRACT_METHOD =
    parameterized(PsiMethodReferenceExpression.class, PsiMethod.class, "method.reference.abstract.method")
      .withDescription((psi, method) -> message("method.reference.abstract.method", method.getName()));
  public static final Parameterized<PsiMethodReferenceExpression, PsiMethod> METHOD_REFERENCE_NON_STATIC_METHOD_IN_STATIC_CONTEXT =
    parameterized(PsiMethodReferenceExpression.class, PsiMethod.class, "method.reference.non.static.method.in.static.context");
  public static final Parameterized<PsiMethodReferenceExpression, PsiMethod> METHOD_REFERENCE_STATIC_METHOD_NON_STATIC_QUALIFIER =
    parameterized(PsiMethodReferenceExpression.class, PsiMethod.class, "method.reference.static.method.non.static.qualifier");
  public static final Parameterized<PsiMethodReferenceExpression, PsiMethod> METHOD_REFERENCE_STATIC_METHOD_RECEIVER =
    parameterized(PsiMethodReferenceExpression.class, PsiMethod.class, "method.reference.static.method.receiver");
  public static final Parameterized<PsiMethodReferenceExpression, PsiClass> METHOD_REFERENCE_ENCLOSING_INSTANCE_NOT_IN_SCOPE =
    parameterized(PsiMethodReferenceExpression.class, PsiClass.class, "method.reference.enclosing.instance.not.in.scope")
      .withDescription((psi, cls) -> message("method.reference.enclosing.instance.not.in.scope", formatClass(cls)));
  public static final Simple<PsiReferenceParameterList> METHOD_REFERENCE_PARAMETERIZED_QUALIFIER =
    error("method.reference.parameterized.qualifier");
  
  public static final Parameterized<PsiAnnotation, @NotNull List<PsiAnnotation.@NotNull TargetType>> ANNOTATION_NOT_APPLICABLE =
    error(PsiAnnotation.class, "annotation.not.applicable").<@NotNull List<PsiAnnotation.@NotNull TargetType>>parameterized()
      .withDescription((annotation, types) -> {
        String target = JavaPsiBundle.message("annotation.target." + types.get(0));
        PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
        return message("annotation.not.applicable", nameRef != null ? nameRef.getText() : annotation.getText(), target);
      });
  public static final Parameterized<PsiAnnotation, @NotNull List<String>> ANNOTATION_MISSING_ATTRIBUTE =
    error(PsiAnnotation.class, "annotation.missing.attribute")
      .withAnchor(annotation -> annotation.getNameReferenceElement())
      .<@NotNull List<String>>parameterized()
      .withDescription((annotation, attributeNames) -> message(
          "annotation.missing.attribute", attributeNames.stream().map(attr -> "'" + attr + "'").collect(Collectors.joining(", "))));
  public static final Simple<PsiAnnotation> ANNOTATION_CONTAINER_WRONG_PLACE =
    error(PsiAnnotation.class, "annotation.container.wrong.place")
      .withDescription(annotation ->
                            message("annotation.container.wrong.place",
                                    requireNonNull(annotation.resolveAnnotationType()).getQualifiedName()));
  public static final Parameterized<PsiAnnotation, PsiClass> ANNOTATION_CONTAINER_NOT_APPLICABLE =
    parameterized(PsiAnnotation.class, PsiClass.class, "annotation.container.not.applicable")
      .withDescription((annotation, containerClass) -> {
        PsiAnnotation.TargetType[] targets = AnnotationTargetUtil.getTargetsForLocation(annotation.getOwner());
        String target = JavaPsiBundle.message("annotation.target." + targets[0]);
        return message("annotation.container.not.applicable", containerClass.getName(), target);
      });
  public static final Simple<PsiAnnotation> ANNOTATION_DUPLICATE =
    error(PsiAnnotation.class, "annotation.duplicate").withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()));
  public static final Simple<PsiAnnotation> ANNOTATION_DUPLICATE_NON_REPEATABLE =
    error(PsiAnnotation.class, "annotation.duplicate.non.repeatable")
      .withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()))
      .withDescription(annotation -> message(
        "annotation.duplicate.non.repeatable", requireNonNull(annotation.resolveAnnotationType()).getQualifiedName()));
  public static final Parameterized<PsiAnnotation, String> ANNOTATION_DUPLICATE_EXPLAINED =
    error(PsiAnnotation.class, "annotation.duplicate.explained")
      .withAnchor(annotation -> requireNonNull(annotation.getNameReferenceElement()))
      .<String>parameterized()
      .withDescription((annotation, message) -> message("annotation.duplicate.explained", message));
  public static final Parameterized<PsiAnnotationMemberValue, String> ANNOTATION_MALFORMED_REPEATABLE_EXPLAINED =
    parameterized(PsiAnnotationMemberValue.class, String.class, "annotation.malformed.repeatable.explained")
      .withDescription((containerRef, message) -> message("annotation.malformed.repeatable.explained", message));

  public static final Simple<PsiAnnotation> SAFE_VARARGS_ON_RECORD_COMPONENT =
    error("safe.varargs.on.record.component");
  public static final Parameterized<PsiAnnotation, PsiMethod> SAFE_VARARGS_ON_FIXED_ARITY = parameterized("safe.varargs.on.fixed.arity");
  public static final Parameterized<PsiAnnotation, PsiMethod> SAFE_VARARGS_ON_NON_FINAL_METHOD =
    parameterized("safe.varargs.on.non.final.method");
  public static final Parameterized<PsiAnnotation, PsiMethod> OVERRIDE_ON_STATIC_METHOD = parameterized("override.on.static.method");
  public static final Parameterized<PsiAnnotation, PsiMethod> OVERRIDE_ON_NON_OVERRIDING_METHOD =
    parameterized("override.on.non-overriding.method");

  public static final Simple<PsiReceiverParameter> RECEIVER_WRONG_CONTEXT =
    error(PsiReceiverParameter.class, "receiver.wrong.context").withAnchor(PsiReceiverParameter::getIdentifier);
  public static final Simple<PsiReceiverParameter> RECEIVER_STATIC_CONTEXT =
    error(PsiReceiverParameter.class, "receiver.static.context").withAnchor(PsiReceiverParameter::getIdentifier);
  public static final Simple<PsiReceiverParameter> RECEIVER_WRONG_POSITION =
    error(PsiReceiverParameter.class, "receiver.wrong.position").withAnchor(PsiReceiverParameter::getIdentifier);
  public static final Parameterized<PsiReceiverParameter, PsiType> RECEIVER_TYPE_MISMATCH =
    error(PsiReceiverParameter.class, "receiver.type.mismatch")
      .withAnchor(parameter -> requireNonNullElse(parameter.getTypeElement(), parameter)).parameterized();
  public static final Parameterized<PsiReceiverParameter, @Nullable String> RECEIVER_NAME_MISMATCH =
    error(PsiReceiverParameter.class, "receiver.name.mismatch").withAnchor(PsiReceiverParameter::getIdentifier).parameterized();
  
  // PsiMember = PsiClass | PsiEnumConstant
  public static final Parameterized<PsiMember, PsiMethod> CLASS_NO_ABSTRACT_METHOD =
    error(PsiMember.class, "class.must.implement.method")
      .withRange(member ->
                   member instanceof PsiEnumConstant enumConstant ? enumConstant.getNameIdentifier().getTextRangeInParent() :
                   member instanceof PsiClass aClass ? getClassDeclarationTextRange(aClass) : null)
      .<PsiMethod>parameterized()
      .withDescription((member, abstractMethod) -> {
        PsiClass aClass = member instanceof PsiEnumConstant enumConstant ?
                          requireNonNullElse(enumConstant.getInitializingClass(), member.getContainingClass()) : (PsiClass)member;
        @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String messageKey;
        String referenceName;
        if (member instanceof PsiEnumConstant enumConstant) {
          messageKey = "class.must.implement.method.enum.constant";
          referenceName = enumConstant.getName();
        }
        else {
          messageKey = aClass.isEnum() || aClass.isRecord() || aClass instanceof PsiAnonymousClass
                       ? "class.must.implement.method"
                       : "class.must.implement.method.or.abstract";
          referenceName = formatClass(aClass, false);
        }
        return message(messageKey, referenceName, formatMethod(abstractMethod),
                       formatClass(requireNonNull(abstractMethod.getContainingClass()), false));
      });
  public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_ABSTRACT_AND_DEFAULT =
    parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.abstract.and.default")
      .withAnchor(PsiClass::getNameIdentifier)
      .withDescription((cls, ctx) -> {
        return message("class.inherits.abstract.and.default", formatClass(cls),
                formatMethod(ctx.method()),
                formatClass(requireNonNull(ctx.method().getContainingClass())),
                formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
      });
  public static final Parameterized<PsiClass, OverrideClashContext> CLASS_INHERITS_UNRELATED_DEFAULTS =
    parameterized(PsiClass.class, OverrideClashContext.class, "class.inherits.unrelated.defaults")
      .withAnchor(PsiClass::getNameIdentifier)
      .withDescription((cls, ctx) -> {
        return message("class.inherits.unrelated.defaults", formatClass(cls),
                formatMethod(ctx.method()),
                formatClass(requireNonNull(ctx.method().getContainingClass())),
                formatClass(requireNonNull(ctx.superMethod().getContainingClass())));
      });
  public static final Simple<PsiClass> CLASS_ALREADY_IMPORTED =
    error(PsiClass.class, "class.already.imported").withAnchor(PsiClass::getNameIdentifier)
      .withDescription(cls -> message("class.already.imported", formatClass(cls, false)));
  public static final Parameterized<PsiClass, PsiClass> CLASS_DUPLICATE =
    error(PsiClass.class, "class.duplicate")
      .withAnchor(cls -> requireNonNullElse(cls.getNameIdentifier(), cls))
      .withHighlightType(cls -> cls instanceof PsiImplicitClass ? JavaErrorHighlightType.FILE_LEVEL_ERROR : JavaErrorHighlightType.ERROR)
      .withDescription(cls -> message("class.duplicate", cls.getName()))
      .parameterized();
  public static final Parameterized<PsiClass, PsiClass> CLASS_DUPLICATE_IN_OTHER_FILE =
    error(PsiClass.class, "class.duplicate")
      .withAnchor(cls -> requireNonNullElse(cls.getNameIdentifier(), cls))
      .withHighlightType(cls -> cls instanceof PsiImplicitClass ? JavaErrorHighlightType.FILE_LEVEL_ERROR : JavaErrorHighlightType.ERROR)
      .<PsiClass>parameterized()
      .withDescription((cls, dupCls) -> message("class.duplicate.in.other.file",
                                                FileUtil.toSystemDependentName(dupCls.getContainingFile().getVirtualFile().getPath())));
  public static final Simple<PsiClass> CLASS_CLASHES_WITH_PACKAGE =
    error(PsiClass.class, "class.clashes.with.package")
      .withAnchor(cls -> requireNonNullElse(cls.getNameIdentifier(), cls))
      .withHighlightType(cls -> cls instanceof PsiImplicitClass ? JavaErrorHighlightType.FILE_LEVEL_ERROR : JavaErrorHighlightType.ERROR)
      .withDescription(cls -> message("class.clashes.with.package", cls.getQualifiedName()));
  public static final Simple<PsiJavaCodeReferenceElement> PACKAGE_CLASHES_WITH_CLASS =
    error(PsiJavaCodeReferenceElement.class, "package.clashes.with.class")
      .withDescription(ref -> message("package.clashes.with.class", ref.getText()));
  public static final Simple<PsiClass> CLASS_WRONG_FILE_NAME =
    error(PsiClass.class, "class.wrong.filename")
      .withRange(JavaErrorFormatUtil::getClassDeclarationTextRange)
      .withDescription(cls -> message("class.wrong.filename", cls.getName()));
  public static final Parameterized<PsiClass, PsiClass> CLASS_CYCLIC_INHERITANCE =
    error(PsiClass.class, "class.cyclic.inheritance")
      .withRange(JavaErrorFormatUtil::getClassDeclarationTextRange).<PsiClass>parameterized()
      .withDescription((aClass, circularClass) -> message("class.cyclic.inheritance", formatClass(circularClass)));
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_DUPLICATE =
    parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.duplicate")
      .withDescription(
        (ref, target) -> message("class.reference.list.duplicate", formatClass(target), ref.getParent().getFirstChild().getText()));
  public static final Simple<PsiJavaCodeReferenceElement> CLASS_REFERENCE_LIST_NAME_EXPECTED =
    error("class.reference.list.name.expected");
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_INNER_PRIVATE =
    parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.inner.private")
      .withDescription((ref, target) -> message("class.reference.list.inner.private",
                                                formatClass(target), formatClass(requireNonNull(target.getContainingClass()))));
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_REFERENCE_LIST_NO_ENCLOSING_INSTANCE =
    parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.reference.list.no.enclosing.instance")
      .withDescription((ref, target) -> message("class.reference.list.no.enclosing.instance", formatClass(target)));
  public static final Simple<PsiReferenceList> CLASS_CANNOT_EXTEND_MULTIPLE_CLASSES =
    error("class.cannot.extend.multiple.classes");
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_EXTENDS_INTERFACE = 
    parameterized("class.extends.interface");
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_EXTENDS_SEALED_LOCAL = 
    parameterized("class.extends.sealed.local");
  public static final Parameterized<PsiJavaCodeReferenceElement, SuperclassSubclassContext> CLASS_EXTENDS_SEALED_NOT_PERMITTED =
    parameterized(PsiJavaCodeReferenceElement.class, SuperclassSubclassContext.class, "class.extends.sealed.not.permitted")
      .withDescription((ref, cls) -> message("class.extends.sealed.not.permitted", formatClass(cls.subClass())));
  public static final Parameterized<PsiJavaCodeReferenceElement, SuperclassSubclassContext> CLASS_EXTENDS_SEALED_ANOTHER_MODULE =
    parameterized(PsiJavaCodeReferenceElement.class, SuperclassSubclassContext.class, "class.extends.sealed.another.module");
  public static final Parameterized<PsiJavaCodeReferenceElement, SuperclassSubclassContext> CLASS_EXTENDS_SEALED_ANOTHER_PACKAGE =
    parameterized(PsiJavaCodeReferenceElement.class, SuperclassSubclassContext.class, "class.extends.sealed.another.package")
      .withDescription((ref, cls) -> {
        return StringUtil.capitalize(message("class.extends.sealed.another.package",
                                             JavaElementKind.fromElement(cls.subClass()).subject(), formatClass(cls.subClass()),
                                             JavaElementKind.fromElement(cls.superClass()).subject(), formatClass(cls.superClass())));
      });
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_EXTENDS_PROHIBITED_CLASS = 
    parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.extends.prohibited.class")
      .withDescription((ref, cls) -> message("class.extends.prohibited.class", cls.getQualifiedName()));
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_INHERITS_TYPE_PARAMETER =
    parameterized("class.inherits.type.parameter");  
  public static final Parameterized<PsiJavaCodeReferenceElement, PsiClass> CLASS_EXTENDS_FINAL = 
    parameterized(PsiJavaCodeReferenceElement.class, PsiClass.class, "class.extends.final").withDescription((ref, cls) -> {
      int choice = cls.isEnum() ? 2 : cls.isRecord() ? 3 : cls.isValueClass() ? 4 : 1;
      return message("class.extends.final", formatClass(cls), choice);
    });
  public static final Simple<PsiAnonymousClass> CLASS_ANONYMOUS_EXTENDS_SEALED =
