// Copyright 2000-2024 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;

import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor;
import com.intellij.icons.AllIcons;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.FunctionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class RecursiveCallLineMarkerProvider extends LineMarkerProviderDescriptor {

  @Override
  public LineMarkerInfo<?> getLineMarkerInfo(@NotNull PsiElement element) {
    return null; //do nothing
  }

  @Override
  public void collectSlowLineMarkers(@NotNull List<? extends PsiElement> elements,
                                     @NotNull Collection<? super LineMarkerInfo<?>> result) {
    final Set<PsiStatement> statements = new HashSet<>();

    for (PsiElement element : elements) {
      ProgressManager.checkCanceled();
      if (element instanceof PsiMethodCallExpression methodCall) {
        final PsiStatement statement = PsiTreeUtil.getParentOfType(methodCall, PsiStatement.class, true, PsiMethod.class);
        if (!statements.contains(statement) && isRecursiveMethodCall(methodCall)) {
          statements.add(statement);
          ContainerUtil.addIfNotNull(result, RecursiveMethodCallMarkerInfo.create(methodCall));
        }
      }
    }
  }

  public static boolean isRecursiveMethodCall(@NotNull PsiMethodCallExpression methodCall) {
    final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
    if (qualifier != null && !(qualifier instanceof PsiThisExpression) && 
        !(qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).resolve() instanceof PsiClass)) {
      return false;
    }

    final PsiMethod method = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class, true, PsiLambdaExpression.class, PsiClass.class);
    if (method == null || !method.getName().equals(methodCall.getMethodExpression().getReferenceName())) {
      return false;
    }

    return Comparing.equal(method, methodCall.resolveMethod());
  }

  @Override
  public @NotNull String getName() {
    return JavaBundle.message("line.marker.recursive.call");
  }

  @Override
  public @Nullable Icon getIcon() {
    return AllIcons.Gutter.RecursiveMethod;
  }

  private static final class RecursiveMethodCallMarkerInfo extends LineMarkerInfo<PsiElement> {
    private static RecursiveMethodCallMarkerInfo create(@NotNull PsiMethodCallExpression methodCall) {
      PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement();
      if (nameElement != null) {
        return new RecursiveMethodCallMarkerInfo(nameElement);
      }
      return null;
    }

    private RecursiveMethodCallMarkerInfo(@NotNull PsiElement name) {
      super(name, name.getTextRange(), AllIcons.Gutter.RecursiveMethod, FunctionUtil.constant(JavaBundle.message("tooltip.recursive.call")), null,
            GutterIconRenderer.Alignment.RIGHT);
    }

    @Override
    public GutterIconRenderer createGutterRenderer() {
      if (myIcon == null) return null;
      return new LineMarkerGutterIconRenderer<>(this) {
        @Override
        public AnAction getClickAction() {
          return null; // to place breakpoint on mouse click
        }
      };
    }
  }
}

