// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.plugins.gradle.service.resolve;

import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.util.GradleLog;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightParameter;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;

import java.util.Arrays;

/**
 * @author Vladislav.Soroka
 */
public final class GradleResolverUtil {

  public static int getGrMethodArumentsCount(@NotNull GrArgumentList args) {
    int argsCount = 0;
    boolean namedArgProcessed = false;
    for (GroovyPsiElement arg : args.getAllArguments()) {
      if (arg instanceof GrNamedArgument) {
        if (!namedArgProcessed) {
          namedArgProcessed = true;
          argsCount++;
        }
      }
      else {
        argsCount++;
      }
    }
    return argsCount;
  }

  public static @Nullable GrLightMethodBuilder createMethodWithClosure(@NotNull String name,
                                                                       @Nullable String returnType,
                                                                       @Nullable String closureTypeParameter,
                                                                       @NotNull PsiElement place) {
    PsiClassType closureType;
    PsiClass closureClass =
      JavaPsiFacade.getInstance(place.getProject()).findClass(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, place.getResolveScope());
    if (closureClass == null) return null;

    if (closureClass.getTypeParameters().length != 1) {
      GradleLog.LOG.debug(String.format("Unexpected type parameters found for closureClass(%s) : (%s)",
                                        closureClass, Arrays.toString(closureClass.getTypeParameters())));
      return null;
    }

    PsiElementFactory factory = JavaPsiFacade.getElementFactory(place.getManager().getProject());

    if (closureTypeParameter != null) {
      PsiClassType closureClassTypeParameter =
        factory.createTypeByFQClassName(closureTypeParameter, place.getResolveScope());
      closureType = factory.createType(closureClass, closureClassTypeParameter);
    }
    else {
      PsiClassType closureClassTypeParameter =
        factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, place.getResolveScope());
      closureType = factory.createType(closureClass, closureClassTypeParameter);
    }

    GrLightMethodBuilder methodWithClosure = new GrLightMethodBuilder(place.getManager(), name);
    GrLightParameter closureParameter = new GrLightParameter("closure", closureType, methodWithClosure);
    methodWithClosure.addParameter(closureParameter);
    PsiClassType retType = factory.createTypeByFQClassName(
      returnType != null ? returnType : CommonClassNames.JAVA_LANG_OBJECT, place.getResolveScope());
    methodWithClosure.setReturnType(retType);
    methodWithClosure.setContainingClass(retType.resolve());
    return methodWithClosure;
  }

  public static @Nullable PsiElement findParent(@NotNull PsiElement element, int level) {
    PsiElement parent = element;
    do {
      parent = parent.getParent();
    }
    while (parent != null && --level > 0);
    return parent;
  }

  public static @Nullable <T extends PsiElement> T findParent(@NotNull PsiElement element, Class<T> clazz) {
    PsiElement parent = element;
    do {
      parent = parent.getParent();
      if (clazz.isInstance(parent)) {
        //noinspection unchecked
        return (T)parent;
      }
    }
    while (parent != null && !(parent instanceof GroovyFile));
    return null;
  }

  public static boolean canBeMethodOf(@Nullable String methodName, @Nullable PsiClass aClass) {
    return methodName != null && aClass != null && aClass.findMethodsByName(methodName, true).length != 0;
  }

  public static @Nullable PsiType getTypeOf(final @Nullable GrExpression expression) {
    if (expression == null) return null;
    return RecursionManager.doPreventingRecursion(expression, true, () -> expression.getNominalType());
  }

  public static boolean isLShiftElement(@Nullable PsiElement psiElement) {
    return (psiElement instanceof GrBinaryExpression &&
            GroovyElementTypes.COMPOSITE_LSHIFT_SIGN.equals(((GrBinaryExpression)psiElement).getOperationTokenType()));
  }
}
