@NotNull
  @Override
  public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
    return new JavaElementVisitor() {
      @Override
      public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
        if (!PsiUtil.isAvailable(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS, holder.getFile())) return;
        PsiTypeCastExpression qualifier = getQualifierReferenceExpression(call);
        if (qualifier == null) return;
        PsiInstanceOfExpression candidate = InstanceOfUtils.findPatternCandidate(qualifier);
        if (candidate == null) return;
        PsiPrimaryPattern pattern = candidate.getPattern();
        if (pattern instanceof PsiDeconstructionPattern deconstruction) {
          PsiPatternVariable existingPatternVariable = findExistingPatternVariable(qualifier, deconstruction, call);
          if (existingPatternVariable == null) return;
          if (!isFinalOrEffectivelyFinal(existingPatternVariable)) return;
          String patternName = existingPatternVariable.getName();
          if (PsiUtil.skipParenthesizedExprUp(call.getParent()) instanceof PsiLocalVariable localVariable &&
              canReplaceLocalVariableWithPatternVariable(localVariable, existingPatternVariable)) {
            String name = localVariable.getName();
            LocalQuickFix fix = new ExistingPatternVariableCanBeUsedFix(name, existingPatternVariable);
            holder.registerProblem(call, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message",
                                                                         patternName, name), fix);
          }
          else {
            String callText = call.getText();
            LocalQuickFix fix = new ExistingPatternVariableCanBeUsedFix(callText, existingPatternVariable);
            holder.registerProblem(call, InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message",
                                                                         patternName, callText), fix);
          }
        }
      }

      private static boolean isFinalOrEffectivelyFinal(@NotNull PsiPatternVariable variable) {
        return (!(variable.getPattern() instanceof PsiDeconstructionPattern) && variable.hasModifierProperty(PsiModifier.FINAL)) ||
               !VariableAccessUtils.variableIsAssigned(variable, variable.getDeclarationScope());
      }

      private static boolean canReplaceLocalVariableWithPatternVariable(@NotNull PsiLocalVariable localVariable,
                                                                        @NotNull PsiPatternVariable patternVariable) {
        PsiElement scope = PsiUtil.getVariableCodeBlock(localVariable, null);
        if (scope == null) return false;
        return localVariable.hasModifierProperty(PsiModifier.FINAL) ||
               !patternVariable.hasModifierProperty(PsiModifier.FINAL) ||
               HighlightControlFlowUtil.isEffectivelyFinal(localVariable, scope, null);
      }

      @Nullable
      private static PsiTypeCastExpression getQualifierReferenceExpression(@NotNull PsiMethodCallExpression call) {
        while (true) {
          if (!call.getArgumentList().isEmpty()) return null;
          PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(call.getMethodExpression().getQualifierExpression());
          PsiMethodCallExpression qualifierMethodCall = ObjectUtils.tryCast(qualifier, PsiMethodCallExpression.class);
          if (qualifierMethodCall == null) return ObjectUtils.tryCast(qualifier, PsiTypeCastExpression.class);
          call = qualifierMethodCall;
        }
      }

      private static PsiPatternVariable findExistingPatternVariable(@NotNull PsiExpression currentQualifier,
                                                                    @NotNull PsiDeconstructionPattern currentDeconstruction,
                                                                    @NotNull PsiMethodCallExpression call) {
        PsiElement parent = PsiUtil.skipParenthesizedExprUp(currentQualifier.getParent());
        if (!(parent instanceof PsiReferenceExpression)) return null;
        PsiElement grandParent = parent.getParent();
        if (grandParent instanceof PsiMethodCallExpression currentCall &&
            currentCall.resolveMethod() instanceof LightRecordMethod recordMethod) {
          PsiClass aClass = recordMethod.getContainingClass();
          PsiRecordComponent[] recordComponents = aClass.getRecordComponents();
          PsiRecordComponent recordComponent = recordMethod.getRecordComponent();
          int index = ArrayUtil.find(recordComponents, recordComponent);
          if (index < 0) return null;
          PsiPattern[] deconstructionComponents = currentDeconstruction.getDeconstructionList().getDeconstructionComponents();
          if (index >= deconstructionComponents.length) return null;
          PsiPattern deconstructionComponent = deconstructionComponents[index];
          PsiType componentType = JavaPsiPatternUtil.getPatternType(deconstructionComponent);
          if (componentType == null || !componentType.equals(recordComponent.getType())) return null;
          PsiPatternVariable variable = JavaPsiPatternUtil.getPatternVariable(deconstructionComponent);
          if (currentCall.equals(call)) return variable;
          if (deconstructionComponent instanceof PsiDeconstructionPattern deconstruction) {
            return findExistingPatternVariable(currentCall, deconstruction, call);
          }
        }
        return null;
      }

      @Override
      public void visitTypeCastExpression(@NotNull PsiTypeCastExpression expression) {
        InstanceOfCandidateResult result = findInstanceOfCandidateResult(expression);
        if (result == null) return;
        if (result.instanceOf() != null) {
          PsiPattern pattern = result.instanceOf().getPattern();
          PsiPatternVariable existingPatternVariable = JavaPsiPatternUtil.getPatternVariable(pattern);
          if (pattern != null && existingPatternVariable == null) {
            return;
          }
          if (existingPatternVariable != null) {
            if (!isFinalOrEffectivelyFinal(existingPatternVariable)) {
              return;
            }
            holder.registerProblem(result.castTypeElement(),
                                   InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.cast.message",
                                                                   existingPatternVariable.getName()),
                                   new ExistingPatternVariableCanBeUsedFix(null, existingPatternVariable));
          }
          else {
            if (!reportAlsoCastWithIntroducingNewVariable) {
              if (isOnTheFly) {
                holder.registerProblem(expression,
                                       InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.instead.of.cast.message"),
                                       ProblemHighlightType.INFORMATION,
                                       new CastExpressionsCanBeReplacedWithPatternVariableFix(result.instanceOf()));
              }
              return;
            }
            holder.registerProblem(result.castTypeElement(),
                                   InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.instead.of.cast.message"),
                                   new CastExpressionsCanBeReplacedWithPatternVariableFix(result.instanceOf()));
          }
        }
      }

      @Override
      public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
        PsiIdentifier identifier = variable.getNameIdentifier();
        if (identifier == null) return;
        PsiTypeCastExpression cast = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(variable.getInitializer()),
                                                         PsiTypeCastExpression.class);
        if (cast == null || cast.getCastType() == null) return;
        PsiExpression operand = cast.getOperand();
        if (operand == null) return;
        PsiType castType = cast.getCastType().getType();
        if (castType instanceof PsiPrimitiveType &&
            !PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, operand)) return;
        if (!variable.getType().equals(castType)) return;
        PsiType operandType = operand.getType();
        if (operandType == null || castType.isAssignableFrom(operandType)) return;
        PsiElement scope = PsiUtil.getVariableCodeBlock(variable, null);
        if (scope == null) return;
        PsiDeclarationStatement declaration = ObjectUtils.tryCast(variable.getParent(), PsiDeclarationStatement.class);
        if (declaration == null) return;
        PsiInstanceOfExpression instanceOf = InstanceOfUtils.findPatternCandidate(cast, variable);
        if (instanceOf != null) {
          PsiPattern pattern = instanceOf.getPattern();
          PsiPatternVariable existingPatternVariable = JavaPsiPatternUtil.getPatternVariable(pattern);
          //it is a deconstruction pattern and we can't add new variable here
          if (pattern != null && existingPatternVariable == null) {
            return;
          }
          String name = identifier.getText();
          if (existingPatternVariable != null) {
            if (!canReplaceLocalVariableWithPatternVariable(variable, existingPatternVariable) ||
                !isFinalOrEffectivelyFinal(existingPatternVariable)) {
              return;
            }
            holder.registerProblem(identifier,
                                   InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.existing.message",
                                                                   existingPatternVariable.getName(), name),
                                   new ExistingPatternVariableCanBeUsedFix(name, existingPatternVariable));
          } else {
            if (!isOnTheFly && InstanceOfUtils.hasConflictingDeclaredNames(variable, instanceOf)) {
              return;
            }
            holder.registerProblem(identifier,
                                   InspectionGadgetsBundle.message("inspection.pattern.variable.can.be.used.message", name),
                                   new PatternVariableCanBeUsedFix(name, instanceOf));
          }
        }
      }
    };
  }
@NotNull
  @Override
  public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly)