// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.groovy.intentions.style;

import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeStyle.GrReferenceAdjuster;
import org.jetbrains.plugins.groovy.intentions.base.Intention;
import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate;
import org.jetbrains.plugins.groovy.lang.psi.GrQualifiedReference;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;

/**
 * @author Maxim.Medvedev
 */
public final class ImportStaticIntention extends Intention {
  private static final Key<PsiElement> TEMP_REFERENT_USER_DATA = new Key<>("TEMP_REFERENT_USER_DATA");

  @Override
  protected void processIntention(@NotNull PsiElement element, final @NotNull Project project, final Editor editor) throws IncorrectOperationException {
    final PsiElement resolved = resolve(element);
    if (!(resolved instanceof PsiMember)) return;

    final PsiClass containingClass = ((PsiMember)resolved).getContainingClass();
    if (containingClass == null) return;

    String originalName = ((PsiMember)resolved).getName();
    final String name = resolved instanceof PsiMethod && GroovyPropertyUtils.isSimplePropertyAccessor((PsiMethod)resolved) ? GroovyPropertyUtils.getPropertyName((PsiMethod)resolved)
                                                                                                                           : originalName;
    final String qname = containingClass.getQualifiedName();
    if (name == null) return;

    final PsiFile containingFile = element.getContainingFile();
    if (!(containingFile instanceof GroovyFile file)) return;
    file.accept(new GroovyRecursiveElementVisitor() {
      @Override
      public void visitReferenceExpression(@NotNull GrReferenceExpression expression) {
        super.visitReferenceExpression(expression);
        if (name.equals(expression.getReferenceName())) {
          PsiElement resolved = expression.resolve();
          if (resolved != null) {
            expression.putUserData(TEMP_REFERENT_USER_DATA, resolved);
          }
        }
      }
    });

    final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(project);
    final GrImportStatement tempImport = factory.createImportStatementFromText(qname + "." + name, true, false, null);
    final GrImportStatement importStatement = file.addImport(tempImport);

    boolean isAnythingShortened = shortenUsages(resolved, containingFile);
    if (!isAnythingShortened) {
      importStatement.delete();
      return;
    }

    file.accept(new GroovyRecursiveElementVisitor() {
      @Override
      public void visitReferenceExpression(@NotNull GrReferenceExpression expression) {
        super.visitReferenceExpression(expression);

        GrTypeArgumentList typeArgumentList = expression.getTypeArgumentList();
        if (typeArgumentList != null && typeArgumentList.getFirstChild() != null) {
          expression.putUserData(TEMP_REFERENT_USER_DATA, null);

          return;
        }

        if (name.equals(expression.getReferenceName())) {
          if (expression.isQualified()) {
            GrExpression qualifier = expression.getQualifierExpression();
            if (qualifier instanceof GrReferenceExpression) {
              PsiElement aClass = ((GrReferenceExpression)qualifier).resolve();
              if (aClass == ((PsiMember)resolved).getContainingClass()) {
                GrReferenceAdjuster.shortenReference(expression);
              }
            }
          }
          else {
            PsiElement referent = expression.getUserData(TEMP_REFERENT_USER_DATA);
            if (referent instanceof PsiMember &&
                ((PsiMember)referent).hasModifierProperty(PsiModifier.STATIC) &&
                referent != expression.resolve()) {
              expression.bindToElement(referent);
            }
          }
        }
        expression.putUserData(TEMP_REFERENT_USER_DATA, null);
      }
    });
  }

  private static boolean shortenUsages(PsiElement resolved, PsiFile containingFile) {
    boolean isAnythingShortened = false;
    for (PsiReference reference : ReferencesSearch.search(resolved, new LocalSearchScope(containingFile)).asIterable()) {
      final PsiElement refElement = reference.getElement();
      if (refElement instanceof GrQualifiedReference<?>) {
        boolean shortened = GrReferenceAdjuster.shortenReference((GrQualifiedReference<?>)refElement);
        isAnythingShortened |= shortened;
      }
    }
    return isAnythingShortened;
  }

  @Override
  protected boolean isStopElement(PsiElement element) {
    return super.isStopElement(element) || element instanceof GrReferenceExpression;
  }

  @Override
  protected @NotNull PsiElementPredicate getElementPredicate() {
    return new PsiElementPredicate() {
      @Override
      public boolean satisfiedBy(@NotNull PsiElement element) {
        final PsiElement resolved = resolve(element);

          if (resolved == null) return false;
          return resolved instanceof PsiMember && !(resolved instanceof PsiClass) &&
                 ((PsiMember)resolved).hasModifierProperty(PsiModifier.STATIC) &&
                 ((PsiMember)resolved).getContainingClass() != null;
      }
    };
  }

  private static @Nullable PsiElement resolve(PsiElement element) {
    GrReferenceExpression ref = findRef(element);

    if (ref == null || ref.getQualifier() == null) return null;
    return ref.resolve();
  }

  private static @Nullable GrReferenceExpression findRef(PsiElement element) {
    if ((element instanceof GrReferenceExpression)) {
      return (GrReferenceExpression)element;

    }
    else if (element instanceof GrArgumentList) {
      PsiElement parent = element.getParent();
      if (parent instanceof GrMethodCall) {
        GrExpression invoked = ((GrMethodCall)parent).getInvokedExpression();
        if (invoked instanceof GrReferenceExpression) {
          return ((GrReferenceExpression)invoked);
        }
      }
    }
    return null;
  }
}
