@Override
  public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
    return new JavaElementVisitor() {
      @Override
      public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
        if (LIST_GET_REMOVE.matches(call)) {
          processListGetRemove(call);
        }
        if (LIST_ADD.matches(call)) {
          processListAdd(call);
        }
        if (ITERATOR_NEXT.matches(call)) {
          processIteratorNext(call);
        }
      }

      private void processIteratorNext(@NotNull PsiMethodCallExpression call) {
        PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
        if (!COLLECTION_ITERATOR.matches(qualifierCall)) return;
        PsiExpression collection = qualifierCall.getMethodExpression().getQualifierExpression();
        if (collection == null || collection instanceof PsiThisExpression) return;
        if (!InheritanceUtil.isInheritor(collection.getType(), "java.util.SequencedCollection")) return;
        String name = "getFirst";
        report(call, name);
      }

      private void report(@NotNull PsiMethodCallExpression call, String name) {
        holder.registerProblem(
          Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()),
          JavaBundle.message("inspection.stream.api.migration.can.be.replaced.with.call", name + "()"),
          new ReplaceWithCallFix(name));
      }

      private void processListAdd(@NotNull PsiMethodCallExpression call) {
        PsiReferenceExpression methodExpr = call.getMethodExpression();
        PsiExpression list = PsiUtil.skipParenthesizedExprDown(methodExpr.getQualifierExpression());
        if (list == null || list instanceof PsiThisExpression) return;
        PsiExpression arg = PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]);
        if (ExpressionUtils.isZero(arg) && !hasDifferentIndexNearby(call)) {
          report(call, "addFirst");
        }
      }
      
      // Do not warn if we have series of calls with different constant indices like {@code list.get(0); list.get(1); ...}
      private static boolean hasDifferentIndexNearby(@NotNull PsiMethodCallExpression call) {
        PsiMethod method = call.resolveMethod();
        if (method == null) return false;
        PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
        if (qualifier == null) return false;
        PsiCodeBlock block = PsiTreeUtil.getParentOfType(call, PsiCodeBlock.class, true, PsiLambdaExpression.class, PsiMember.class);
        if (block == null) return false;
        PsiStatement[] statements = block.getStatements();
        int index = (int) StreamEx.of(statements).indexOf(s -> PsiTreeUtil.isAncestor(s, call, true)).orElse(-1);
        if (index == -1) return false;
        for (int i = Math.max(0, index - 2); i <= Math.min(statements.length - 1, index + 2); i++) {
          Integer otherIndex = SyntaxTraverser.psiTraverser(statements[i]).filter(PsiMethodCallExpression.class)
            .filter(c -> c != call)
            .filter(c -> method.isEquivalentTo(c.resolveMethod()) &&
                         EquivalenceChecker.getCanonicalPsiEquivalence()
                           .expressionsAreEquivalent(qualifier, c.getMethodExpression().getQualifierExpression()))
            .map(c -> PsiUtil.skipParenthesizedExprDown(ArrayUtil.getFirstElement(c.getArgumentList().getExpressions())))
            .filter(PsiLiteralValue.class)
            .map(PsiLiteralValue::getValue)
            .filter(Integer.class)
            .find(idx -> idx > 0 && idx < 10);
          if (otherIndex != null) {
            return true;
          }
        }
        return false;
      }

      private void processListGetRemove(@NotNull PsiMethodCallExpression call) {
        PsiReferenceExpression methodExpr = call.getMethodExpression();
        String name = methodExpr.getReferenceName();
        PsiExpression list = PsiUtil.skipParenthesizedExprDown(methodExpr.getQualifierExpression());
        if (list == null || list instanceof PsiThisExpression) return;
        PsiExpression arg = PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]);
        if (ExpressionUtils.isZero(arg) && !hasDifferentIndexNearby(call)) {
          report(call, name + "First");
        }
        if (arg instanceof PsiBinaryExpression binOp && binOp.getOperationTokenType().equals(JavaTokenType.MINUS) &&
            ExpressionUtils.isOne(binOp.getROperand())) {
          IndexedContainer container = IndexedContainer.fromLengthExpression(binOp.getLOperand());
          if (container != null && container.isQualifierEquivalent(list)) {
            report(call, name + "Last");
          }
        }
      }
    };
  }
@Override
  public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly)