// 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.groovy.lang.psi.impl;

import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;

import java.util.Collections;
import java.util.List;

import static org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtilKt.findDeclaredDetachedValue;
import static org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtilKt.getArrayValue;

/**
 * @author Max Medvedev
 */
public final class GrAnnotationUtil {
  public static @Nullable String inferStringAttribute(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
    return getString(targetValue);
  }

  public static @Nullable GrClosableBlock inferClosureAttribute(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
    if (targetValue instanceof GrClosableBlock) {
      return (GrClosableBlock)targetValue;
    }
    return null;
  }

  public static @Nullable String getString(@Nullable PsiAnnotationMemberValue targetValue) {
    if (targetValue instanceof PsiLiteral) {
      final Object value = ((PsiLiteral)targetValue).getValue();
      if (value instanceof String) return (String)value;
    }
    return null;
  }

  public static @Nullable Integer inferIntegerAttribute(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
    if (targetValue instanceof PsiLiteral) {
      final Object value = ((PsiLiteral)targetValue).getValue();
      if (value instanceof Integer) return (Integer)value;
    }
    return null;
  }

  public static @Nullable Boolean inferBooleanAttribute(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
    if (targetValue instanceof PsiLiteral) {
      final Object value = ((PsiLiteral)targetValue).getValue();
      if (value instanceof Boolean) return (Boolean)value;
    }
    return null;
  }

  public static boolean inferBooleanAttributeNotNull(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    Boolean result = inferBooleanAttribute(annotation, attributeName);
    return result != null && result;
  }

  public static @Nullable PsiClass inferClassAttribute(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName) {
    final PsiAnnotationMemberValue targetValue = annotation.findAttributeValue(attributeName);
    return getPsiClass(targetValue);
  }

  public static @Nullable PsiClass getPsiClass(@Nullable PsiAnnotationMemberValue targetValue) {
    if (targetValue instanceof PsiClassObjectAccessExpression) {
      PsiType type = ((PsiClassObjectAccessExpression)targetValue).getOperand().getType();
      if (type instanceof PsiClassType) {
        return ((PsiClassType)type).resolve();
      }
    }
    else if (targetValue instanceof GrReferenceExpression) {
      if ("class".equals(((GrReferenceExpression)targetValue).getReferenceName())) {
        GrExpression qualifier = ((GrReferenceExpression)targetValue).getQualifier();
        if (qualifier instanceof GrReferenceExpression) {
          PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
          if (resolved instanceof PsiClass) {
            return (PsiClass)resolved;
          }
        }
      }
      PsiElement resolved = ((GrReferenceExpression)targetValue).resolve();
      if (resolved instanceof PsiClass) return (PsiClass)resolved;
    }
    return null;
  }

  public static @Nullable PsiType extractClassTypeFromClassAttributeValue(PsiAnnotationMemberValue targetValue) {
    if (targetValue instanceof PsiClassObjectAccessExpression) {
      return ((PsiClassObjectAccessExpression)targetValue).getOperand().getType();
    }
    else if (targetValue instanceof GrReferenceExpression) {
      if ("class".equals(((GrReferenceExpression)targetValue).getReferenceName())) {
        GrExpression qualifier = ((GrReferenceExpression)targetValue).getQualifier();
        if (qualifier instanceof GrReferenceExpression) {
          PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
          if (resolved instanceof PsiClass) {
            return qualifier.getType();
          }
        }
      }
      PsiElement resolved = ((GrReferenceExpression)targetValue).resolve();
      if (resolved instanceof PsiClass) {
        return ((GrReferenceExpression)targetValue).getType();
      }
    }
    return null;
  }

  public static PsiElement getActualOwner(GrAnnotation annotation) {
    PsiAnnotationOwner owner = annotation.getOwner();
    if (owner instanceof PsiModifierList) return ((PsiModifierList)owner).getParent();

    return (PsiElement)owner;
  }

  public static List<PsiClass> getClassArrayValue(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName, boolean declared) {
    PsiAnnotationMemberValue value =
      declared ? annotation.findDeclaredAttributeValue(attributeName) : annotation.findAttributeValue(attributeName);
    return ContainerUtil.mapNotNull(AnnotationUtil.arrayAttributeValues(value), GrAnnotationUtil::getPsiClass);
  }

  public static List<String> getStringArrayValue(@NotNull PsiAnnotation annotation, @NlsSafe @NotNull String attributeName, boolean declared) {
    PsiAnnotationMemberValue value = findDetachedAttributeValue(annotation, attributeName, declared);
    if (value == null) return Collections.emptyList();
    return getArrayValue(value, AnnotationUtil::getStringAttributeValue);
  }

  private static @Nullable PsiAnnotationMemberValue findDetachedAttributeValue(@NotNull PsiAnnotation annotation,
                                                                               @NlsSafe @Nullable String attributeName,
                                                                               boolean declared) {
    PsiAnnotationMemberValue declaredValue = findDeclaredDetachedValue(annotation, attributeName);
    if (declaredValue != null) return declaredValue;
    if (declared) return null;
    return annotation.findAttributeValue(attributeName);
  }
}
